Java中哈希算法总结

Java中哈希算法总结Hash 算法 消息摘要算法 工具类 publicclassH 消息摘要对象 privatestati 构造方法 privateHashT

大家好,欢迎来到IT知识分享网。

 哈希算法(Hash Function)

哈希算法(Hash)又称摘要算法(Disgest),它的作用是:对任意一组输入数据进行计算,得到一个固定长度(16位)的输出摘要。

将任意长度的二进制值映射为固定长度的二进制值串,这个映射的规则就是哈希算法,而通过原始数据映射之后得到的二进制值串就是哈希值。

哈希算法的主要特点就是:

  • 相同的输入一定得到相同的输出
  • 不同的输入大概率得到不同的输出

所以,哈希算法的目的是为了验证原始数据是否被篡改。

 哈希碰撞

哈希碰撞是指两个不同的输入得到了相同的输出,例如:

Java中哈希算法总结

 哈希碰撞是不可避免的,因为输出的字节长度是固定的,String的hashcode()输出时4字节整数,最多只有种输出,但输入的数据长度时不固定的,有无数种输入。所以,哈希算法是把一个无线的输入集合映射到一个有限的输出集合,必然会产生碰撞。一个安全的哈希算法必须满足: 

  • 碰撞概率低;
  • 不能猜出输出

       不能猜出输出是指输入的任意一个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加密算法

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

 按照MD5算法对图片进行”加密”

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算法

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()); } } 

Hash算法(消息摘要算法)工具类

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算法

  • HmacMD5使用的是key长度是64字节,更安全
  • Hmac是标准算法,同样适用与SHA-1等其他哈希算法
  • Hmac输出和原有的哈希算法长度一致
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 的步骤是:

  1. 通过名称 HmacMD5 获取 KeyGenerator 实例
  2. 通过 KeyGenerator 创建一个 secretKey 实例
  3. 通过名称 HmacMD5 获取 Mac 实例
  4. 用 secretKey 初始化Mac实例
  5. 对 Mac 实例反复调用 update(byte[]) 输入数据
  6. 调用 Mac 实例的 dofinal() 获取最终的哈希值

 按照”字节数组” “字符串”,恢复Hmac秘钥

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 = "2990fea992a358e76c169f8bc7d415d38d0ea393e456dfc13dc3e8ed17f62ba06f36aeaed1d7ebaa67f7edbc743dd883a5954d361e841ba1cc"; // //用于保存秘钥:秘钥长度为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()); //c674d77c826a8cbf0c594a8368 System.out.println(result); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } } } 

BouncyCastle

首先我们要先在project中导入bcprov-jdk15on-1.70.jar

Java中哈希算法总结

 ,其次,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); } } 

小结

  • 哈希算法可用于验证数据完整性,具有防篡改检测的功能
  • 常用的哈希算法有 MD5、SHA-1 等
  • 用哈希存储口令时要考虑彩虹表攻击
  • Hmac 算法是一种标准的基于密钥的哈希算法,可以配合 MD5 、 SHA-1 等哈希算法,计算的摘要长度和原摘要算法长度相同
  • BouncyCastle 是一个开源的第三方算法提供商
  • BouncyCastle 提供了很多 Java 标准库没有提供的哈希算法和加密算法

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/110260.html

(0)
上一篇 2026-02-01 19:15
下一篇 2026-02-01 19:26

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信