赞
踩
哈希算法(Hash)又称摘要算法(Disgest),它的作用是:对任意一组输入数据进行计算,得到一个固定长度(16位)的输出摘要。
将任意长度的二进制值映射为固定长度的二进制值串,这个映射的规则就是哈希算法,而通过原始数据映射之后得到的二进制值串就是哈希值。
哈希算法的主要特点就是:
所以,哈希算法的目的是为了验证原始数据是否被篡改。
哈希碰撞是指两个不同的输入得到了相同的输出,例如:
哈希碰撞是不可避免的,因为输出的字节长度是固定的,String的hashcode()输出时4字节整数,最多只有4294967296种输出,但输入的数据长度时不固定的,有无数种输入。所以,哈希算法是把一个无线的输入集合映射到一个有限的输出集合,必然会产生碰撞。一个安全的哈希算法必须满足:
不能猜出输出是指输入的任意一个bit的变化会造成输出完全不同,这样就很难从输出反推输 入 (只能依靠暴力穷举)。
算法 | 输出长度(位) | 输出长度(字节) |
MD5 | 128 bits | 16 bytes |
SHA-1 | 160 bits | 20 bytes |
RipeMD-60 | 160 bits | 20 bytes |
SHA-256 | 256 bits | 32 bytes |
SHA-512 | 512 bits | 64 bytes |
MD5即Message-Digest Algorithm 5(信息-摘要算法5),用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一(又译摘要算法、哈希算法),主流编程语言普遍已有MD5实现。
MD5的作用是让大容量信息在用数字签名软件签署私人密钥前被"压缩"成一种保密的格式(就是把一个任意长度的字节串变换成一定长的16进制数字串)。
- package com.apesource.demo02;
-
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
- import java.util.Arrays;
- //MD5加密
- public class Demo01 {
- public static void main(String[] args) throws NoSuchAlgorithmException {
- //创建基于MD5算法的消息摘要对象
- MessageDigest md5 = MessageDigest.getInstance("MD5");
- //更新原始数据
- md5.update("天王盖地虎".getBytes());
- //获取加密后的结果
- byte[] disgetsBytes = md5.digest();
- System.out.println("加密后的结果"+Arrays.toString(disgetsBytes));
- System.out.println("加密结果长度"+disgetsBytes.length);
- }
- }
-
运行上述代码可以得出"天王盖地虎"的MD5加密后的结果[-54, 22, 32, 101, 73, -60, 77, 99, 77, -78, -48, -124, -125, 46, 113, 10],加密结果长度16
- package com.apesource.demo02;
-
- import java.io.IOException;
- import java.nio.file.Files;
- import java.nio.file.Paths;
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
- import java.util.Arrays;
-
- import com.apesource.demo.HashTools;
-
- //按照MD5算法对图片进行"加密"
- public class Demo02 {
- public static void main(String[] args) throws IOException, NoSuchAlgorithmException {
- //图片的原始字节内容
- byte[] imageBuf = Files.readAllBytes(Paths.get("C:\\Users\\Pictures\\xxx.jpg"));
- //创建基于MD5算法的消息摘要对象
- MessageDigest md5 = MessageDigest.getInstance("MD5");
- //原始字节内容(图片)
- md5.update(imageBuf);
- //获取加密摘要
- byte[] digestBytes = md5.digest();
- System.out.println("加密后的结果(字节数组):"+Arrays.toString(digestBytes));
- System.out.println("加密后的结果(16进制字符串):"+HashTools.bytesTohex(digestBytes));
- System.out.println("加密结果长度"+digestBytes.length);
- }
- }
SHA-1 也是一种哈希算法,它的输出是 160 bits,即 20 字节。 SHA-1 是由美国国家安全局开发的,SHA 算法实际上是一个系列,包括 SHA- 0(已废弃)、SHA-1 、SHA-256、SHA-512 等。
在 Java 中使用 SHA-1 ,和 MD5 完全一样,只需要把算法名称改为"SHA-1"
- package com.apesource.demo02;
-
- import java.io.UnsupportedEncodingException;
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
-
- public class Demo03 {
- public static void main(String[] args) throws NoSuchAlgorithmException, UnsupportedEncodingException {
- //创建一个MessageDigest实例
- MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
- //调用update输入数据:
- sha1.update("Hello".getBytes("UTF-8"));
- //f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0
- byte[] results = sha1.digest();
- StringBuilder ret = new StringBuilder();
- for(byte b : results) {
- //将字节数组转换为两位十六进制字符串
- ret.append(String.format("%02x", b));
- }
- System.out.println(ret.toString());
- }
- }
- package com.apesource.demo02;
-
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
-
- //Hash算法(消息摘要算法)工具类
- public class HashTools {
- //消息摘要对象
- private static MessageDigest digest;
- //构造方法
- private HashTools() {}
- //按照MD5进行消息摘要计算(哈希算法)
- public static String digestByMD5(String source) throws NoSuchAlgorithmException{
- digest = MessageDigest.getInstance("MD5");
- return handler(source);
- }
- //按照SHA-1进行消息摘要计算(哈希算法)
- public static String digestBySHA1(String source) throws NoSuchAlgorithmException{
- digest = MessageDigest.getInstance("SHA-1");
- return handler(source);
- }
- //通过消息摘要对象,处理加密内容
- public static String handler(String source) {
- digest.update(source.getBytes());
- byte[] bytes = digest.digest();
- String hash = bytesToHex(bytes);
- return hash;
- }
- //将字节数组转换为16进制字符串
- public static String bytesToHex(byte[] bytes) {
- StringBuilder ret = new StringBuilder();
- for(byte b : bytes) {
- //将字节数组转换为两位十六进制字符串
- ret.append(String.format("%02x", b));
- }
- return ret.toString();
- }
- }
使用哈希口令时,还要注意防止彩虹表攻击。
通过随机加盐,解决彩虹表攻击问题
什么是彩虹表呢?上面讲到了,如果只拿到 MD5 ,从 MD5反推明文口令,只能使用暴力穷举的方法。然而黑客并不笨,暴力穷举会消耗大量的算力和时间。但是,如果有一个预先计算好的常用口令和它们的 MD5 的对照表,这个表就是彩虹表。如果用户使用了常用口令,黑客从 MD5 一下就能反查到原始口令。当然,我们也可以采取特殊措施来抵御彩虹表攻击:对每个口令额外添加随机数,这个方法称之为加盐(salt)。这个salt可以看作是一个额外的"认证码",同样的 输入,不同的认证码,会产生不同的输出。因此要验证输出的哈希,必须同时提供"认证码",代码实现如下
- package com.apesource.demo02;
-
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
- import java.util.UUID;
-
- import org.omg.PortableInterceptor.DISCARDING;
-
- import com.apesource.demo.HashTools;
-
- //通过随机加盐,解决彩虹表攻击问题
- public class Demo04 {
- public static void main(String[] args) throws NoSuchAlgorithmException {
- //原始密码
- String password = "wbjxxmy";
- //属于即产生的盐值
- String salt = UUID.randomUUID().toString().substring(0,4);
- //创建基于SHA-1算法的消息摘要对象
- MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
- sha1.update(password.getBytes()); //原始密码
- sha1.update(salt.getBytes()); //盐值
- //计算加密结果,SHA-1的输出结果为20个字节(40个字符)
- String digestHex = HashTools.bytesTohex(sha1.digest());
- System.out.println(digestHex);
- }
- }
- package com.apesource.demo02;
-
- import java.security.NoSuchAlgorithmException;
-
- //通过自定义工具类,完成对应加密处理
- public class Demo05 {
- public static void main(String[] args) {
- try {
- //md5加密
- String md5 = HashTools.digestByMD5("wbjxxmy");
- //sha1加密
- String sha1 = HashTools.digestBySHA1("wbjxxmy");
-
- System.out.println("md5="+md5);
- System.out.println("sha1="+sha1);
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- }
- }
- }
Hmac 算法就是一种基于密钥的消息认证码算法,它的全称是 Hash-based Message Authentication Code ,是种更安全的消息摘要算法。
Hmac 算法总是和某种哈希算法配合起来用的。例如,我们使用 MD5 算法,对应的就是 Hmac MD5 算法,它相当于“加盐”的 MD5 :HmacMD5 ≈ md5(secure_random_key, input)
因此,HmacMD5 可以看带有一个安全的 key 的 MD使用 HmacMD5 而不是用 MD5 加 salt ,有如下好处:
可见,Hmac 本质上就是把 key 混入摘要的算法。验证此哈希时,除了原始的输入数据,还要提供 key 。为了保证安全,我们不会自己指定 key ,而是通过 Java 标准库的 KeyGenerator 生成一个安全的随机的 key 。
下面是使用 HmacMD5 的参考代码:
- package com.apesource.demo02;
-
- import java.security.InvalidKeyException;
- import java.security.NoSuchAlgorithmException;
- import java.util.Arrays;
-
- import javax.crypto.KeyGenerator;
- import javax.crypto.Mac;
- import javax.crypto.SecretKey;
-
- //Hmac算法
- public class Demo06 {
- public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
- String password = "wbjxxmy";
- //一:产生秘钥
- //获取HmacMD5秘钥生成器
- KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");
- //生成秘钥
- SecretKey key = keyGen.generateKey();
- System.out.println("秘钥:"+Arrays.toString(key.getEncoded()));
- System.out.println("秘钥长度(64字节):"+key.getEncoded().length);
- System.out.println("秘钥:"+HashTools.bytesToHex(key.getEncoded()));
- //二:使用秘钥,进行加密
- //获取HMac加密算法对象
- Mac mac = Mac.getInstance("HMacMD5");
- mac.init(key); //初始化秘钥
- mac.update(password.getBytes()); //更新原始加密内容
- byte[] bytes = mac.doFinal(); //加密处理,并获取加密结果
- String result = HashTools.bytesToHex(bytes); //加密结果处理成16进制字符串
- System.out.println("加密结果16进制字符串:"+result);
- System.out.println("加密结果(字节长度16字节):"+bytes.length);
- System.out.println("加密结果(字符长度32字符):"+result.length());
-
- }
- }
注:相同的秘钥产生的结果不同
和 MD5 相比,使用 HmacMD5 的步骤是:
- package com.apesource.demo02;
-
- import java.security.InvalidKeyException;
- import java.security.NoSuchAlgorithmException;
-
- import javax.crypto.Mac;
- import javax.crypto.SecretKey;
- import javax.crypto.spec.SecretKeySpec;
-
- //按照"字节数组" "字符串",恢复Hmac秘钥
- public class Demo07 {
- public static void main(String[] args) {
- //原始密码
- // String password = "wbjxxmy";
- // //秘钥(字节数组)
- // byte[] keybytes = {-97, 63, -18, -128, -127, 25, -55, -12, 76, 79, -66, -41, -17, -74, -109, -21, 125, -101, -72, 61, -43, -116, 87, -63, -42, 87, -78, 16, -16, -10, 105, 127, -57, 21, 117, -115, -39, 17, 116, -99, 121, -43, -22, 61, -74, 21, -12, 121, 24, 94, -80, -2, -100, 110, -125, 101, -7, 14, -16, -9, -47, 117, 90, -59};
- //
- //秘钥(字符串)
- String keystr = "2990f480743ea992a358e76c169f8bc7d415d38d0e10541282a393e456dfc13dc3e8ed17f62ba06f36aeaed1d7ebaa67f7edbc743dd883a5954d361e841ba1cc";
- // //用于保存秘钥:秘钥长度为64字节
- byte[] keybytes = new byte[64];
-
- for(int i = 0,k = 0;i<keystr.length();i+=2,k++) {
- String s = keystr.substring(i, i+2);
- keybytes[k] = (byte)Integer.parseInt(s,16); //转换成十六进制字节值
- }
-
- try {
- //恢复秘钥(字节数组)
- SecretKey key = new SecretKeySpec(keybytes,"HmacMD5");
- //创建Hmac加密算法对象
- Mac mac = Mac.getInstance("HmacMD5");
- mac.init(key); //初始化秘钥
- mac.update(password.getBytes());
- String result = HashTools.bytesToHex(mac.doFinal());
- //808956c674d77c826a8cbf0c594a8368
- System.out.println(result);
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- } catch (InvalidKeyException e) {
- e.printStackTrace();
- }
-
- }
- }
BouncyCastle就是一个提供了很多哈希算法和加密算法的第三方开源库。它提供了 Java 标准库没有的一些算法,例如,RipeMD169 哈希算法.
RIPEMD160是一种基于Merkle-Damgard 结构的加密哈希函数,它是比特币标准之一。RIPEMD-160是RIPEMD算法的增强版本RIPEMD-160算法可以产生出160位的的哈希摘要。
首先我们要先在project中导入bcprov-jdk15on-1.70.jar
,其次,Java标准库的java.security包提供了一种标准机制,允许第三方提供给上无缝接入。我们要使用BouncyCastle注册一下,实现代码如下:
- package com.apesource.demo02;
-
- import java.math.BigInteger;
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
- import java.security.Security;
- import java.util.Arrays;
-
- import javax.crypto.SecretKey;
-
- import org.bouncycastle.jce.provider.BouncyCastleProvider;
-
- //第三方开源库提供的RipeMD160消息摘要算法实现
- public class Demo08 {
- public static void main(String[] args) throws NoSuchAlgorithmException {
- //注册BouncyCastleProvider通知类
- //将提供的消息摘要算法注册至Security
- Security.addProvider(new BouncyCastleProvider());
- //获取RipeMD160算法的"消息摘要对象"(加密对象)
- MessageDigest ripeMd160 = MessageDigest.getInstance("RipeMD160");
- //更新原始数据
- ripeMd160.update("wbjxxmy".getBytes());
- //获取消息摘要(加密)
- byte[] result = ripeMd160.digest();
- //消息摘要的字节长度和内容
- System.out.println("加密结果(字节长度)"+result.length); //160位 = 20字节
- System.out.println("加密结果(字节内容)"+Arrays.toString(result));
- //十六进制字符串
- String hex = new BigInteger(1,result).toString(16);
- System.out.println("加密结果(字符串长度)"+hex.length()); //20字节=40个字符
- System.out.println("加密结果(字符串内容)"+hex);
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。