spring项目中使用MD5加密方式
百度百科中这样解释到MD5加密:
MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。MD5由美国密码学家罗纳德·李维斯特(Ronald Linn Rivest)设计,于1992年公开,用以取代MD4算法。这套算法的程序在 RFC 1321 标准中被加以规范。1996年后该算法被证实存在弱点,可以被加以破解,对于需要高度安全性的数据,专家一般建议改用其他算法,如SHA-2。2004年,证实MD5算法无法防止碰撞(collision),因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途
说起到MD5加密方式,我们先来了解下HASH算法:
哈希算法(Hash)又称摘要算法(Digest),它的作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要
哈希算法最重要的特点就是:
- 相同的输入一定得到相同的输出;
- 不同的输入大概率得到不同的输出。
哈希算法的目的就是为了验证原始数据是否被篡改。
Java字符串的hashCode()
就是一个哈希算法,它的输入是任意字符串,输出是固定的4字节int
整数:
"hello".hashCode(); // 0x5e918d2 "hello, java".hashCode(); // 0x7a9d88e8 "hello, bob".hashCode(); // 0xa0dbae2f
两个相同的字符串永远会计算出相同的hashCode
,否则基于hashCode
定位的HashMap
就无法正常工作。这也是为什么当我们自定义一个class时,覆写equals()
方法时我们必须正确覆写hashCode()
方法。
哈希碰撞:
哈希碰撞是指,两个不同的输入得到了相同的输出:
1 2 | "AaAaAa" .hashCode(); // 0x7460e8c0 "BBAaBB" .hashCode(); // 0x7460e8c0 |
那么有人会问哈希碰撞是否可以避免?答案是否定的。碰撞是一定会出现的,因为输出的字节长度是固定的,String
的hashCode()
输出是4字节整数,最多只有4294967296种输出,但输入的数据长度是不固定的,有无数种输入。所以,哈希算法是把一个无限的输入集合映射到一个有限的输出集合,必然会产生碰撞。
碰撞不可怕,我们担心的不是碰撞,而是碰撞的概率,因为碰撞概率的高低关系到哈希算法的安全性。一个安全的哈希算法必须满足:
- 碰撞概率低;
- 不能猜测输出。
不能猜测输出是指,输入的任意一个bit的变化会造成输出完全不同,这样就很难从输出反推输入(只能依靠暴力穷举)。假设一种哈希算法有如下规律:
1 2 3 | hashA( "java001" ) = "123456" hashA( "java002" ) = "123457" hashA( "java003" ) = "123458" |
那么很容易从输出123459
反推输入,这种哈希算法就不安全。安全的哈希算法从输出是看不出任何规律的:
1 2 3 | hashB( "java001" ) = "123456" hashB( "java002" ) = "580271" hashB( "java003" ) = ??? |
常用的哈希算法有:
算法 | 输出长度(位) | 输出长度(字节) |
---|---|---|
MD5 | 128 bits | 16 bytes |
SHA-1 | 160 bits | 20 bytes |
RipeMD-160 | 160 bits | 20 bytes |
SHA-256 | 256 bits | 32 bytes |
SHA-512 | 512 bits | 64 bytes |
根据碰撞概率,哈希算法的输出长度越长,就越难产生碰撞,也就越安全。
Java标准库提供了常用的哈希算法,并且有一套统一的接口。我们以MD5算法为例,看看如何对输入计算哈希:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import java.math.BigInteger; import java.security.MessageDigest; public class Main { public static void main(String[] args) throws Exception { // 创建一个MessageDigest实例: MessageDigest md = MessageDigest.getInstance( "MD5" ); // 反复调用update输入数据: md.update( "Hello" .getBytes( "UTF-8" )); md.update( "World" .getBytes( "UTF-8" )); byte [] result = md.digest(); // 16 bytes: 68e109f0f40ca72a15e05cc22786f8e6 System.out.println( new BigInteger( 1 , result).toString( 16 )); } } |
用MessageDigest
时,我们首先根据哈希算法获取一个MessageDigest
实例,然后,反复调用update(byte[])
输入数据。当输入结束后,调用digest()
方法获得byte[]数组表示的摘要,最后,把它转换为十六进制的字符串。
运行上述代码,可以得到输入HelloWorld
的MD5是68e109f0f40ca72a15e05cc22786f8e6
。
哈希算法的另一个重要用途是存储用户口令。如果直接将用户的原始口令存放到数据库中,会产生极大的安全风险:
- 数据库管理员能够看到用户明文口令;
- 数据库数据一旦泄漏,黑客即可获取用户明文口令。
不存储用户的原始口令,那么如何对用户进行认证?
方法是存储用户口令的哈希,例如,MD5。
在用户输入原始口令后,系统计算用户输入的原始口令的MD5并与数据库存储的MD5对比,如果一致,说明口令正确,否则,口令错误。
因此,数据库存储用户名和口令的表内容应该像下面这样:
username | password |
---|---|
bob | f30aa7a662c728b7407c54ae6bfd27d1 |
alice | 25d55ad283aa400af464c76d713c07ad |
tim | bed128365216c019988915ed3add75fb |
这样一来,数据库管理员看不到用户的原始口令。即使数据库泄漏,黑客也无法拿到用户的原始口令。想要拿到用户的原始口令,必须用暴力穷举的方法,一个口令一个口令地试,直到某个口令计算的MD5恰好等于指定值。使用哈希口令时,还要注意防止彩虹表攻击。
在了解了Hash算法后我们再次回到MD5算法加密
在spring中是自带了MD5加密的工具类的:
在项目中搜索DigestUtils
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | // // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.util; import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public abstract class DigestUtils { private static final String MD5_ALGORITHM_NAME = "MD5" ; private static final char [] HEX_CHARS = new char []{ '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' }; public DigestUtils() { } public static byte [] md5Digest( byte [] bytes) { return digest( "MD5" , bytes); } public static byte [] md5Digest(InputStream inputStream) throws IOException { return digest( "MD5" , inputStream); } public static String md5DigestAsHex( byte [] bytes) { return digestAsHexString( "MD5" , bytes); } public static String md5DigestAsHex(InputStream inputStream) throws IOException { return digestAsHexString( "MD5" , inputStream); } public static StringBuilder appendMd5DigestAsHex( byte [] bytes, StringBuilder builder) { return appendDigestAsHex( "MD5" , bytes, builder); } public static StringBuilder appendMd5DigestAsHex(InputStream inputStream, StringBuilder builder) throws IOException { return appendDigestAsHex( "MD5" , inputStream, builder); } private static MessageDigest getDigest(String algorithm) { try { return MessageDigest.getInstance(algorithm); } catch (NoSuchAlgorithmException var2) { throw new IllegalStateException( "Could not find MessageDigest with algorithm \"" + algorithm + "\"" , var2); } } private static byte [] digest(String algorithm, byte [] bytes) { return getDigest(algorithm).digest(bytes); } private static byte [] digest(String algorithm, InputStream inputStream) throws IOException { MessageDigest messageDigest = getDigest(algorithm); if (inputStream instanceof UpdateMessageDigestInputStream) { ((UpdateMessageDigestInputStream)inputStream).updateMessageDigest(messageDigest); return messageDigest.digest(); } else { byte [] buffer = new byte [ 4096 ]; boolean var4 = true ; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != - 1 ) { messageDigest.update(buffer, 0 , bytesRead); } return messageDigest.digest(); } } private static String digestAsHexString(String algorithm, byte [] bytes) { char [] hexDigest = digestAsHexChars(algorithm, bytes); return new String(hexDigest); } private static String digestAsHexString(String algorithm, InputStream inputStream) throws IOException { char [] hexDigest = digestAsHexChars(algorithm, inputStream); return new String(hexDigest); } private static StringBuilder appendDigestAsHex(String algorithm, byte [] bytes, StringBuilder builder) { char [] hexDigest = digestAsHexChars(algorithm, bytes); return builder.append(hexDigest); } private static StringBuilder appendDigestAsHex(String algorithm, InputStream inputStream, StringBuilder builder) throws IOException { char [] hexDigest = digestAsHexChars(algorithm, inputStream); return builder.append(hexDigest); } private static char [] digestAsHexChars(String algorithm, byte [] bytes) { byte [] digest = digest(algorithm, bytes); return encodeHex(digest); } private static char [] digestAsHexChars(String algorithm, InputStream inputStream) throws IOException { byte [] digest = digest(algorithm, inputStream); return encodeHex(digest); } private static char [] encodeHex( byte [] bytes) { char [] chars = new char [ 32 ]; for ( int i = 0 ; i < chars.length; i += 2 ) { byte b = bytes[i / 2 ]; chars[i] = HEX_CHARS[b >>> 4 & 15 ]; chars[i + 1 ] = HEX_CHARS[b & 15 ]; } return chars; } } |
以上为源代码,看到有个静态final的常量MD5_ALGORITHM_NAME和许多静态方法
常用的是其中的md5DigestAsHex方法,它是个静态方法;
例如密码是123456,要获得其加密后的密码:
DigestUtils.md5DigestAsHex("123456");
为了使得产生更独特的结果,常常在密码之前后之后加上“盐”;
String slat = "luan_ma";
DigestUtils.md5DigestAsHex("123456" + slat);
以下是在一个项目中用到md5加密的方法:
1 2 3 4 5 6 7 8 9 10 11 12 | //获取当前时间和证书 public Map<String, String> getTime() { Map<String, String> map = new HashMap<String, String>(); String nowDate = DateUtil.format( new Date(), DateUtil.DATE_TIME_PATTERN); String date = nowDate.substring( 0 , 10 ); String time = nowDate.substring( 11 ); String certificationKey = key + date + time; String md5Password = DigestUtils.md5DigestAsHex(certificationKey.getBytes()); map.put( "md5Password" , md5Password); map.put( "time" , time); return map; } |
这样就对传递的参数按照规定的方式进行了md5加密
参考原文:https://www.liaoxuefeng.com/wiki/1252599548343744/1304227729113121
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步