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

根据碰撞概率,哈希算法的输出长度越长,就越难产生碰撞,也就越安全。
散列算法比较
| 名称 | 安全性 | 速度 |
|---|---|---|
| MD5 | 中 | 快 |
| SHA | 高 | 慢 |
| 名称 | 优点 | 缺点 | 破解方式 | 使用场景 |
|---|---|---|---|---|
| MD5 | 简单、伪造难 | 潜在的冲突 | 暴力破解 | 1,登录密码保护;2,文件防止篡改;3,HTTP传输内容加密防篡改;4,数字签名; |
哈希算法最重要的特点就是:
- 固定输入一定得到固定输出
- 不同输入得到相同输出的概率极低。不同的输入大概率得到不同的输出。
- 单向性,就是根据算法,理论上不能从计算之后的散列值逆推出原始明文。
- 无论输入数据的长度是多少,得到的输出值是固定的。MD5(散列值进行十六进制编码之后长度为32,SHA256 则为 64)
单向散列函数,不管明文多长,散列后的密文定长。散列后的密文不可逆。
Hash攻击:彩虹表攻击
彩虹表攻击(Rainbow Table Attack)是一种密码破解技术,用于破解密码哈希的安全性。 它是一种预先计算密码哈希和其对应明文的巨大数据表的方法。虽然彩虹表攻击不是直接暴力破解密码,但它可以更快地破解加密密码,尤其是当使用弱密码时。
以防御者的视角,通过某种措施来抵御彩虹表破解或者使攻击者的破解难度和成本大大增加。
加盐: 加盐是在用户密码的基础上添加一个随机生成的字符串,然后再进行哈希运算。 这个随机字符串被称为盐,它使得相同的密码在加盐后得到的哈希值也是不同的。 彩虹表攻击的主要优势在于事先计算大量密码的哈希值,并将其存储在表中。 通过加盐,即使密码相同,由于每个用户都有唯一的盐,最终的哈希值也会有所不同,使得事先计算的表无法直接用于攻击。
迭代哈希: 迭代哈希是对密码进行多次迭代的哈希运算,增加破解密码所需的时间。 通常,哈希值会反复作为输入进行更多次的哈希运算,从而产生最终的哈希值。 彩虹表攻击的速度主要来自于事先计算大量的哈希值,而迭代哈希使得每个尝试的密码需要更多的计算时间。 适当选择迭代次数可以在保证用户登录时的合理响应时间的同时,有效地增加攻击者进行彩虹表攻击时所需的计算时间。
MD5
MD5消息摘要算法,属Hash算法一类。
MD5算法是一种广泛使用的哈希函数,用于生成128位(32个十六进制数字)的消息摘要。 它接受任意长度的输入,并输出固定长度的哈希值(一个128位的消息摘要,32位的数字字母混合码),通常用于验证数据完整性、数字签名、密码存储等领域。 MD5算法以其简洁高效的设计和快速计算速度而闻名, 但近年来由于其存在一些安全性弱点,逐渐被更安全的哈希算法所取代。
MD5主要特点
一个MD5理论上的确是可能对应无数多个原文的,因为MD5是有限多个的而原文可以是无数多个。 比如主流使用的MD5将任意长度的“字节串映射为一个128bit的大整数。 也就是一共有2^128^种可能 ,大概是3.4*10^38^,这个数字是有限多个的, 而但是世界上可以被用来加密的原文则会有无数的可能性。
MD5的性质
- 1、压缩性:任意长度的数据,算出的MD5值长度都是固定的(相当于超损压缩)。
- 2、容易计算:从原数据计算出MD5值很容易。
- 3、抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
- 4、弱抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
- 5、强抗碰撞:想找到两个不同的数据,使它们具有相同的MD5值,是非常困难的。
MD5算法的应用
数据完整性验证
MD5算法常用于数据完整性验证,即确保数据在传输或存储过程中没有被篡改。发送方会计算数据的MD5哈希值并将其附加在数据中一起传输,接收方收到数据后重新计算MD5哈希值,并与接收到的MD5哈希值进行比较,如果一致则说明数据完整性良好。
数字签名
MD5算法也可以用于数字签名,数字签名是一种用于验证数据来源和完整性的技术。发送方使用私钥对数据的MD5哈希值进行加密,生成数字签名并将其附加在数据中发送。接收方使用发送方的公钥解密数字签名,再计算数据的MD5哈希值并与解密后的数字签名进行比较,以验证数据的完整性和真实性。
密码存储
在密码存储方面,MD5算法可以用于加密密码并存储在数据库中。当用户登录时,系统会对用户输入的密码进行MD5哈希运算,然后与数据库中存储的MD5哈希值进行比较,以验证密码的正确性。然而,由于MD5算法存在碰撞攻击等安全漏洞,现在更推荐使用更安全的哈希算法如SHA-256来存储密码。
文件校验
MD5算法还常用于文件校验,例如下载文件后可以计算文件的MD5哈希值,与提供的MD5值进行比较,以确保文件在传输过程中没有被篡改或损坏。如果两个MD5值一致,则文件完整,否则可能存在问题。
问题解答
MD5算法有哪些应用场景?
MD5算法常用于验证数据完整性,文件校验,密码存储等场景。
MD5算法存在哪些安全性问题?
MD5算法存在碰撞攻击漏洞,不再安全可靠,容易被破解。
MD5算法与SHA算法有何区别?
MD5算法和SHA算法都是哈希算法,但SHA算法比MD5更安全,如SHA-256、SHA-512等。
MD5算法是否可逆?
MD5算法是单向哈希算法,不可逆,无法从摘要值还原出原始数据。
MD5算法在密码学中的作用是什么?
MD5算法在密码学中用于生成摘要值,验证数据完整性,密码存储等方面。
代码样例
Java
private static void md5()
throws NoSuchAlgorithmException, UnsupportedEncodingException {
String[] input = {"Hello", "World"};
// 创建一个MessageDigest实例
MessageDigest md = MessageDigest.getInstance("MD5");
// 反复调用update输入数据
for (String s : input) {
md.update(s.getBytes("UTF-8"));
}
// 当输入结束后,调用digest()方法获得byte[]数组表示的摘要
byte[] result = md.digest();
// 最后,把它转换为十六进制的字符串。
// 16 bytes: 68e109f0f40ca72a15e05cc22786f8e6
String rs = HexFormat.of().formatHex(result);
System.out.println(rs);
Preconditions.checkArgument(
StringUtils.equals(rs, "68e109f0f40ca72a15e05cc22786f8e6"), "不符合期望");
}
JavaScript(CryptoJS)
const hasher = CryptoJS.algo.MD5.create(); // [!code error]
hasher.reset();
hasher.update('abc');
hasher.update('def');
const hash = hasher.finalize();
return hash + '';
Python
import hashlib
def md5_test1():
md5 = hashlib.new('md5', 'I love python!'.encode('utf-8'))
print(md5.hexdigest())
def md5_test2():
md5 = hashlib.md5()
md5.update('I love '.encode('utf-8'))
md5.update('python!'.encode('utf-8'))
print(md5.hexdigest())
if __name__ == '__main__':
md5_test1() # 21169ee3acd4a24e1fcb4322cfd9a2b8
md5_test2() # 21169ee3acd4a24e1fcb4322cfd9a2b8
MD5 防止彩虹表攻击
使用哈希口令时,还要注意防止彩虹表攻击。
如果只拿到MD5,从MD5反推明文口令,只能使用暴力穷举的方法。 然而暴力穷举会消耗大量的算力和时间。但是,如果有一个预先计算好的常用口令和它们的MD5的对照表(对照表),就可以根据MD5直接反查。
上述代码样例, 经过加盐操作的改良,使黑客的彩虹表失效,即使用户使用常用口令,也无法从MD5反推原始口令。
/**
* 加盐的目的在于使黑客的彩虹表失效,即使用户使用常用口令,也无法从MD5反推原始口令。
*/
private static void md5WithSalt(String salt)
throws NoSuchAlgorithmException, UnsupportedEncodingException {
String[] input = {salt, "Hello", "World"};
// 创建一个MessageDigest实例
MessageDigest md = MessageDigest.getInstance("MD5");
// 反复调用update输入数据
for (String s : input) {
md.update(s.getBytes("UTF-8"));
}
// 当输入结束后,调用digest()方法获得byte[]数组表示的摘要
byte[] result = md.digest();
// 最后,把它转换为十六进制的字符串。
// 16 bytes: 68e109f0f40ca72a15e05cc22786f8e6
String rs = HexFormat.of().formatHex(result);
System.out.println(rs);
Preconditions.checkArgument(
StringUtils.equals(rs, "20fd53312bf2f702d26087a204e6e0b5"), "不符合期望");
}
SHA
安全散列算法(英语:Secure Hash Algorithm,缩写为SHA)是一个密码散列函数家族, 是FIPS所认证的安全散列算法。 能计算出一个数字消息所对应到的,长度固定的字符串(又称消息摘要)的算法。
且若输入的消息不同,它们对应到不同字符串的机率很高。
目前SHA有五个算法,分别是SHA-1、SHA-224、SHA-256、SHA-384,和SHA-512, 由美国安全局(NSA)所设计,并由美国国家标准与技术研究院(NIST)发布,也是美国的政府标准,后四者有时并称为SHA-2。
SHA-1也是一种哈希算法,它的输出是160 bits,即20字节。
SHA家族
SHA家族各个参数对比如下:

代码样例
SHA-1
20 bytes
private static void sha1() throws Exception {
String[] input = {"Hello", "World"};
// 创建一个MessageDigest实例:
MessageDigest md = MessageDigest.getInstance("SHA-1");
// 反复调用update输入数据
for (String s : input) {
md.update(s.getBytes("UTF-8"));
}
byte[] result = md.digest();
// 20 bytes
Preconditions.checkArgument(result.length == 20, "不符合期望");
String rs = HexFormat.of().formatHex(result);
System.out.println(rs);
Preconditions.checkArgument(
StringUtils.equals(rs, "db8ac1c259eb89d4a131b253bacfca5f319d54f2"), "不符合期望");
}
SHA-224
224 / 8 = 28 bytes
private static void sha224() throws Exception {
// 注册BouncyCastle.
Security.addProvider(new BouncyCastleProvider());
String[] input = {"Hello", "World"};
// 创建一个MessageDigest实例
MessageDigest md = MessageDigest.getInstance("SHA-224");
// 反复调用update输入数据
for (String s : input) {
md.update(s.getBytes("UTF-8"));
}
byte[] result = md.digest();
// 224 / 8 = 28 bytes
Preconditions.checkArgument(result.length == 28, "不符合期望");
String rs = HexFormat.of().formatHex(result);
System.out.println(rs);
Preconditions.checkArgument(
StringUtils.equals(rs, "b07a0b24d54879214f2361e0a1ac320442fa4e53a0f607d126fbfb8a"), "不符合期望");
}
SHA-256
256/8 = 32 bytes
private static void sha256() throws Exception {
String[] input = {"Hello", "World"};
// 创建一个MessageDigest实例:
MessageDigest md = MessageDigest.getInstance("SHA-256");
// 反复调用update输入数据
for (String s : input) {
md.update(s.getBytes("UTF-8"));
}
byte[] result = md.digest();
// 32 bytes
Preconditions.checkArgument(result.length == 32, "不符合期望");
String rs = HexFormat.of().formatHex(result);
System.out.println(rs);
Preconditions.checkArgument(
StringUtils.equals(rs, "872e4e50ce9990d8b041330c47c9ddd11bec6b503ae9386a99da8584e9bb12c4"), "不符合期望");
}
SHA-384
384/8 = 48 bytes
private static void sha384() throws Exception {
String[] input = {"Hello", "World"};
// 创建一个MessageDigest实例:
MessageDigest md = MessageDigest.getInstance("SHA-384");
// 反复调用update输入数据
for (String s : input) {
md.update(s.getBytes("UTF-8"));
}
byte[] result = md.digest();
// 48 bytes
Preconditions.checkArgument(result.length == 48, "不符合期望");
String rs = HexFormat.of().formatHex(result);
System.out.println(rs);
Preconditions.checkArgument(
StringUtils.equals(rs, "293cd96eb25228a6fb09bfa86b9148ab69940e68903cbc0527a4fb150eec1ebe0f1ffce0bc5e3df312377e0a68f1950a"), "不符合期望");
}
SHA-512
512/8 = 64 bytes
private static void sha512() throws Exception {
String[] input = {"Hello", "World"};
// 创建一个MessageDigest实例:
MessageDigest md = MessageDigest.getInstance("SHA-512");
// 反复调用update输入数据
for (String s : input) {
md.update(s.getBytes("UTF-8"));
}
byte[] result = md.digest();
// 64 bytes
Preconditions.checkArgument(result.length == 64, "不符合期望");
String rs = HexFormat.of().formatHex(result);
System.out.println(rs);
Preconditions.checkArgument(
StringUtils.equals(rs, "8ae6ae71a75d3fb2e0225deeb004faf95d816a0a58093eb4cb5a3aa0f197050d7a4dc0a2d5c6fbae5fb5b0d536a0a9e6b686369fa57a027687c3630321547596"), "不符合期望");
}
CryptoJS
return CryptoJS.SHA256('a12345678');
return CryptoJS.SHA384('a12345678');
return CryptoJS.SHA512('a12345678');
评论区