MD5与SHA哈希算法完全对比#
哈希算法是现代密码学的基石,广泛应用于数据完整性验证、密码存储、数字签名等场景。本文将深入对比MD5和SHA系列哈希算法,帮助你选择合适的方案。
什么是哈希算法#
哈希算法(Hash Function)将任意长度的输入数据映射为固定长度的输出值(哈希值或摘要)。
哈希算法的特性#
- 确定性:相同输入总是产生相同输出
- 快速计算:能快速计算出哈希值
- 单向性:从哈希值无法反推原始数据
- 雪崩效应:输入微小变化导致输出巨大变化
- 抗碰撞性:难以找到两个不同输入产生相同哈希值
输入: "Hello World"
MD5: 5eb63bbbe01eeed093cb22bb8f5acdc3
SHA1: d2a84f4b8b650937ec8f73cd8be2c74add5a911b
SHA256: a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e
MD5算法#
MD5(Message-Digest Algorithm 5)由Ronald Rivest于1991年设计,产生128位(16字节)的哈希值,通常用32位十六进制字符串表示。
优点:
缺点:
JavaScript实现#
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
| // MD5实现(简化版)
function md5(string) {
function rotateLeft(value, shift) {
return (value << shift) | (value >>> (32 - shift));
}
function addUnsigned(x, y) {
const lsw = (x & 0xFFFF) + (y & 0xFFFF);
const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
const k = [];
for (let i = 0; i < 64; i++) {
k[i] = Math.floor(Math.abs(Math.sin(i + 1)) * 0x100000000);
}
let msg = unescape(encodeURIComponent(string));
const msgLen = msg.length;
const bytes = [];
for (let i = 0; i < msgLen; i++) {
bytes[i] = msg.charCodeAt(i);
}
const byteLen = msgLen * 8;
bytes[byteLen >> 5] |= 0x80 << ((byteLen) % 32);
bytes[(((byteLen + 64) >>> 9) << 4) + 14] = byteLen;
let a0 = 1732584193;
let b0 = -271733879;
let c0 = -1732584194;
let d0 = 271733878;
for (let i = 0; i < bytes.length; i += 16) {
const a = a0, b = b0, c = c0, d = d0;
for (let j = 0; j < 64; j++) {
let f, g;
if (j < 16) {
f = (b & c) | ((~b) & d);
g = j;
} else if (j < 32) {
f = (d & b) | ((~d) & c);
g = (5 * j + 1) % 16;
} else if (j < 48) {
f = b ^ c ^ d;
g = (3 * j + 5) % 16;
} else {
f = c ^ (b | (~d));
g = (7 * j) % 16;
}
f = addUnsigned(f, a);
f = addUnsigned(f, k[j]);
f = addUnsigned(f, bytes[i + g]);
f = addUnsigned(rotateLeft(f, [7, 12, 17, 22][Math.floor(j / 16)]), b);
a = d;
d = c;
c = b;
b = addUnsigned(f, e);
}
a0 = addUnsigned(a0, a);
b0 = addUnsigned(b0, b);
c0 = addUnsigned(c0, c);
d0 = addUnsigned(d0, d);
}
return ((a0 >>> 0).toString(16) +
(b0 >>> 0).toString(16) +
(c0 >>> 0).toString(16) +
(d0 >>> 0).toString(16))
.replace(/^-/, '')
.padStart(32, '0');
}
// 使用示例
console.log(md5('Hello World'));
// 输出: 5eb63bbbe01eeed093cb22bb8f5acdc3
|
使用Crypto API(推荐)#
1
2
3
4
5
6
7
8
9
10
11
12
13
| async function md5Hash(message) {
const msgBuffer = new TextEncoder().encode(message);
const hashBuffer = await crypto.subtle.digest('MD5', msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
return hashHex;
}
// Node.js环境
const crypto = require('crypto');
function md5HashNode(message) {
return crypto.createHash('md5').update(message).digest('hex');
}
|
SHA系列算法#
SHA-1#
SHA-1产生160位(20字节)的哈希值。已被证明不安全,不应再使用。
1
2
3
4
5
6
| async function sha1Hash(message) {
const msgBuffer = new TextEncoder().encode(message);
const hashBuffer = await crypto.subtle.digest('SHA-1', msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
|
SHA-256#
SHA-256是SHA-2家族的一员,产生256位(32字节)的哈希值,是目前最推荐的哈希算法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| async function sha256Hash(message) {
const msgBuffer = new TextEncoder().encode(message);
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
// Node.js环境
const crypto = require('crypto');
function sha256HashNode(message) {
return crypto.createHash('sha256').update(message).digest('hex');
}
// 使用示例
console.log(await sha256Hash('Hello World'));
// 输出: a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e
|
SHA-512#
SHA-512产生512位(64字节)的哈希值,安全性更高,但哈希值更长。
1
2
3
4
5
6
| async function sha512Hash(message) {
const msgBuffer = new TextEncoder().encode(message);
const hashBuffer = await crypto.subtle.digest('SHA-512', msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
|
算法对比#
性能对比#
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
| async function benchmark() {
const testData = 'A'.repeat(1000000); // 1MB数据
console.time('MD5');
await md5Hash(testData);
console.timeEnd('MD5');
console.time('SHA-1');
await sha1Hash(testData);
console.timeEnd('SHA-1');
console.time('SHA-256');
await sha256Hash(testData);
console.timeEnd('SHA-256');
console.time('SHA-512');
await sha512Hash(testData);
console.timeEnd('SHA-512');
}
// 示例输出:
// MD5: 15ms
// SHA-1: 18ms
// SHA-256: 25ms
// SHA-512: 30ms
|
安全性对比#
| 算法 | 输出长度 | 碰撞攻击 | 彩虹表 | 推荐用途 |
|---|
| MD5 | 128位 | ✅ 容易 | ✅ 可行 | 非安全场景 |
| SHA-1 | 160位 | ✅ 可行 | ✅ 可行 | 不推荐使用 |
| SHA-256 | 256位 | ❌ 困难 | ✅ 需要盐 | 通用安全场景 |
| SHA-512 | 512位 | ❌ 非常困难 | ✅ 需要盐 | 高安全需求 |
应用场景#
1. 文件完整性校验#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| async function verifyFile(file, expectedHash) {
const arrayBuffer = await file.arrayBuffer();
const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
return hashHex === expectedHash;
}
// 使用示例
const file = new File(['Hello World'], 'test.txt');
const expectedHash = 'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e';
verifyFile(file, expectedHash).then(isValid => {
console.log('文件完整性:', isValid);
});
|
2. 密码存储(使用盐值)#
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
| // 生成盐值
function generateSalt(length = 16) {
const array = new Uint8Array(length);
crypto.getRandomValues(array);
return Array.from(array, b => b.toString(16).padStart(2, '0')).join('');
}
// 密码哈希(使用盐值)
async function hashPassword(password, salt) {
const saltedPassword = password + salt;
const encoder = new TextEncoder();
const data = encoder.encode(saltedPassword);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
// 验证密码
async function verifyPassword(password, hash, salt) {
const computedHash = await hashPassword(password, salt);
return computedHash === hash;
}
// 使用示例
async function registerUser(username, password) {
const salt = generateSalt();
const hash = await hashPassword(password, salt);
// 存储到数据库
await database.users.insert({
username,
passwordHash: hash,
salt
});
}
async function loginUser(username, password) {
const user = await database.users.findOne({ username });
if (!user) {
return false;
}
return await verifyPassword(password, user.passwordHash, user.salt);
}
|
3. 数字签名#
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
| // 生成签名
async function signMessage(message, privateKey) {
const encoder = new TextEncoder();
const data = encoder.encode(message);
const signature = await crypto.subtle.sign(
'RSASSA-PKCS1-v1_5',
privateKey,
data
);
return Array.from(new Uint8Array(signature))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
// 验证签名
async function verifySignature(message, signature, publicKey) {
const encoder = new TextEncoder();
const data = encoder.encode(message);
const signatureBytes = new Uint8Array(
signature.match(/.{1,2}/g).map(byte => parseInt(byte, 16))
);
return await crypto.subtle.verify(
'RSASSA-PKCS1-v1_5',
publicKey,
signatureBytes,
data
);
}
|
4. 哈希表数据结构#
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
| class HashTable {
constructor(size = 100) {
this.size = size;
this.buckets = new Array(size);
}
_hash(key) {
let hash = 0;
for (let i = 0; i < key.length; i++) {
const char = key.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash |= 0;
}
return Math.abs(hash) % this.size;
}
set(key, value) {
const index = this._hash(key);
if (!this.buckets[index]) {
this.buckets[index] = [];
}
this.buckets[index].push([key, value]);
}
get(key) {
const index = this._hash(key);
const bucket = this.buckets[index];
if (bucket) {
const item = bucket.find(item => item[0] === key);
return item ? item[1] : undefined;
}
return undefined;
}
}
|
最佳实践#
1. 选择合适的算法#
1
2
3
4
5
6
7
8
| // ❌ 不推荐:使用MD5处理敏感数据
const hash = md5Hash('password123');
// ✅ 推荐:使用SHA-256
const hash = await sha256Hash('password123');
// ✅ 更好:使用专门的密码哈希函数(如bcrypt、Argon2)
// bcrypt在下一节介绍
|
2. 添加盐值#
1
2
3
4
5
6
| // ❌ 不好:直接哈希
const hash = await sha256Hash(password);
// ✅ 好:使用随机盐值
const salt = generateSalt();
const hash = await sha256Hash(password + salt);
|
3. 多次迭代#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // 增加计算成本,防止暴力破解
async function hashWithIterations(password, salt, iterations = 10000) {
let hash = password + salt;
for (let i = 0; i < iterations; i++) {
const encoder = new TextEncoder();
const data = encoder.encode(hash);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
hash = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
return hash;
}
|
4. 使用HMAC#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| // HMAC(Hash-based Message Authentication Code)
async function hmac(message, key, algorithm = 'SHA-256') {
const encoder = new TextEncoder();
const keyData = encoder.encode(key);
const messageData = encoder.encode(message);
const cryptoKey = await crypto.subtle.importKey(
'raw',
keyData,
{ name: 'HMAC', hash: algorithm },
false,
['sign']
);
const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageData);
const signatureArray = Array.from(new Uint8Array(signature));
return signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
// 使用示例
const secretKey = 'my-secret-key';
const message = 'Important message';
const signature = await hmac(message, secretKey);
console.log(signature);
|
常见问题#
Q1: MD5还能用吗?#
MD5只能用于非安全场景,如:
不能用于:
Q2: SHA-256够安全吗?#
对于大多数应用场景,SHA-256足够安全。但对于需要极高安全性的场景,可以考虑:
- SHA-512(更高的安全性)
- BLAKE2/BLAKE3(更快的性能)
- SHA-3(不同的密码学基础)
Q3: 如何存储密码?#
不要直接使用哈希算法存储密码! 推荐使用:
这些算法专门为密码设计,具有:
哈希算法是数据安全的基石。选择合适的哈希算法至关重要:
- 非安全场景:MD5、SHA-1(如数据去重、缓存)
- 通用安全场景:SHA-256(如数据完整性、签名)
- 密码存储:bcrypt、Argon2、PBKDF2
- 高性能需求:BLAKE3
记住:安全是一个持续演进的过程,定期更新算法和最佳实践,保持对安全研究的关注。