哈希算法
概述
哈希算法是将任意长度的数据映射为固定长度字符串的数学函数。在Node.js应用中,哈希算法主要用于密码存储、数据完整性验证、数字签名等安全场景。
哈希算法特性
1. 确定性
- 相同输入总是产生相同输出
- 便于验证和比较
2. 单向性
- 从哈希值无法推导出原始数据
- 保证数据安全性
3. 雪崩效应
- 输入微小变化导致输出巨大变化
- 增强安全性
4. 固定长度
- 无论输入多长,输出长度固定
- 便于存储和处理
常用哈希算法
1. MD5
javascript
const crypto = require('crypto');
// MD5哈希
function md5Hash(data) {
return crypto.createHash('md5').update(data).digest('hex');
}
// 使用示例
const password = 'mypassword123';
const hash = md5Hash(password);
console.log('MD5哈希:', hash); // 输出: 482c811da5d5b4bc6d497ffa98491e38
// ❌ 注意:MD5已不安全,不推荐用于密码存储
2. SHA-1
javascript
const crypto = require('crypto');
// SHA-1哈希
function sha1Hash(data) {
return crypto.createHash('sha1').update(data).digest('hex');
}
// 使用示例
const data = 'Hello World';
const hash = sha1Hash(data);
console.log('SHA-1哈希:', hash); // 输出: 0a0a9f2a6772942557ab5355d76af442f8f65e01
// ❌ 注意:SHA-1也已不安全,不推荐用于安全场景
3. SHA-256
javascript
const crypto = require('crypto');
// SHA-256哈希
function sha256Hash(data) {
return crypto.createHash('sha256').update(data).digest('hex');
}
// 使用示例
const message = 'Hello World';
const hash = sha256Hash(message);
console.log('SHA-256哈希:', hash);
// 带盐值的SHA-256
function sha256WithSalt(data, salt) {
return crypto.createHash('sha256').update(data + salt).digest('hex');
}
4. SHA-512
javascript
const crypto = require('crypto');
// SHA-512哈希
function sha512Hash(data) {
return crypto.createHash('sha512').update(data).digest('hex');
}
// 使用示例
const data = 'Sensitive Data';
const hash = sha512Hash(data);
console.log('SHA-512哈希:', hash);
密码专用哈希算法
1. bcrypt
javascript
const bcrypt = require('bcrypt');
// bcrypt哈希
async function bcryptHash(password) {
const saltRounds = 12;
const hash = await bcrypt.hash(password, saltRounds);
return hash;
}
// bcrypt验证
async function bcryptVerify(password, hash) {
return await bcrypt.compare(password, hash);
}
// 使用示例
async function userRegistration() {
const password = 'userpassword123';
const hashedPassword = await bcryptHash(password);
console.log('bcrypt哈希:', hashedPassword);
// 输出类似: $2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewdBPj4J/8.5.2
// 验证密码
const isValid = await bcryptVerify(password, hashedPassword);
console.log('密码验证:', isValid); // true
}
2. scrypt
javascript
const crypto = require('crypto');
// scrypt哈希
function scryptHash(password, salt) {
const keylen = 64;
const options = {
N: 16384, // CPU/内存成本参数
r: 8, // 块大小参数
p: 1 // 并行化参数
};
return crypto.scryptSync(password, salt, keylen, options);
}
// 完整的scrypt实现
function scryptHashWithSalt(password) {
const salt = crypto.randomBytes(16);
const hash = scryptHash(password, salt);
// 返回盐值+哈希值的组合
return {
salt: salt.toString('hex'),
hash: hash.toString('hex'),
combined: salt.toString('hex') + ':' + hash.toString('hex')
};
}
// scrypt验证
function scryptVerify(password, saltHex, hashHex) {
const salt = Buffer.from(saltHex, 'hex');
const storedHash = Buffer.from(hashHex, 'hex');
const computedHash = scryptHash(password, salt);
return crypto.timingSafeEqual(storedHash, computedHash);
}
3. Argon2
javascript
const argon2 = require('argon2');
// Argon2哈希
async function argon2Hash(password) {
const options = {
type: argon2.argon2id, // 推荐使用argon2id
memoryCost: 2 ** 16, // 64 MB内存
timeCost: 3, // 3次迭代
parallelism: 1, // 1个线程
hashLength: 32 // 32字节哈希长度
};
return await argon2.hash(password, options);
}
// Argon2验证
async function argon2Verify(password, hash) {
try {
return await argon2.verify(hash, password);
} catch (err) {
console.error('验证失败:', err);
return false;
}
}
// 使用示例
async function argon2Example() {
const password = 'securepassword123';
const hash = await argon2Hash(password);
console.log('Argon2哈希:', hash);
const isValid = await argon2Verify(password, hash);
console.log('验证结果:', isValid);
}
哈希算法比较
安全性对比
javascript
// 算法安全性等级
const algorithmSecurity = {
// ❌ 不安全,不推荐使用
'MD5': '已破解,不推荐',
'SHA-1': '已破解,不推荐',
// ⚠️ 一般安全,需要加盐
'SHA-256': '安全,但需要加盐和多次迭代',
'SHA-512': '安全,但需要加盐和多次迭代',
// ✅ 推荐用于密码存储
'bcrypt': '专门为密码设计,推荐使用',
'scrypt': '抗ASIC攻击,推荐使用',
'Argon2': '最新标准,最推荐使用'
};
性能对比
javascript
const crypto = require('crypto');
// 性能测试函数
function performanceTest() {
const testData = 'testpassword123';
const iterations = 1000;
console.log('=== 哈希算法性能测试 ===');
// MD5测试
console.time('MD5');
for (let i = 0; i < iterations; i++) {
crypto.createHash('md5').update(testData).digest('hex');
}
console.timeEnd('MD5');
// SHA-256测试
console.time('SHA-256');
for (let i = 0; i < iterations; i++) {
crypto.createHash('sha256').update(testData).digest('hex');
}
console.timeEnd('SHA-256');
// bcrypt测试(单次)
console.time('bcrypt (单次)');
bcrypt.hashSync(testData, 10);
console.timeEnd('bcrypt (单次)');
}
实际应用场景
1. 密码存储
javascript
// 用户注册
async function registerUser(username, password) {
// 使用Argon2哈希密码
const hashedPassword = await argon2Hash(password);
const user = {
username,
password: hashedPassword,
createdAt: new Date()
};
// 保存到数据库
await User.create(user);
console.log('用户注册成功');
}
// 用户登录
async function loginUser(username, password) {
const user = await User.findOne({ username });
if (!user) {
throw new Error('用户不存在');
}
// 验证密码
const isValid = await argon2Verify(password, user.password);
if (!isValid) {
throw new Error('密码错误');
}
console.log('登录成功');
return user;
}
2. 数据完整性验证
javascript
// 文件完整性检查
function verifyFileIntegrity(filePath, expectedHash) {
const fs = require('fs');
const crypto = require('crypto');
const fileBuffer = fs.readFileSync(filePath);
const actualHash = crypto.createHash('sha256').update(fileBuffer).digest('hex');
return actualHash === expectedHash;
}
// API数据完整性
function createDataHash(data) {
const crypto = require('crypto');
const dataString = JSON.stringify(data);
return crypto.createHash('sha256').update(dataString).digest('hex');
}
function verifyDataIntegrity(data, expectedHash) {
const actualHash = createDataHash(data);
return actualHash === expectedHash;
}
3. 数字签名
javascript
const crypto = require('crypto');
// 生成密钥对
function generateKeyPair() {
return crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
});
}
// 创建数字签名
function createSignature(data, privateKey) {
const sign = crypto.createSign('SHA256');
sign.update(data);
sign.end();
return sign.sign(privateKey, 'hex');
}
// 验证数字签名
function verifySignature(data, signature, publicKey) {
const verify = crypto.createVerify('SHA256');
verify.update(data);
verify.end();
return verify.verify(publicKey, signature, 'hex');
}
安全最佳实践
1. 选择合适的算法
javascript
// 根据用途选择算法
const algorithmSelection = {
// 密码存储
passwordStorage: 'argon2id', // 或 bcrypt
// 数据完整性
dataIntegrity: 'sha256',
// 数字签名
digitalSignature: 'rsa-sha256',
// 快速哈希(非安全场景)
fastHash: 'xxhash' // 需要安装xxhash包
};
2. 添加盐值
javascript
// 生成随机盐值
function generateSalt(length = 32) {
return crypto.randomBytes(length).toString('hex');
}
// 带盐值的哈希
function hashWithSalt(data, salt) {
return crypto.createHash('sha256').update(data + salt).digest('hex');
}
// 使用示例
function secureHash(data) {
const salt = generateSalt();
const hash = hashWithSalt(data, salt);
return {
salt,
hash,
combined: salt + ':' + hash
};
}
3. 防止时序攻击
javascript
// 使用timingSafeEqual防止时序攻击
function safeCompare(a, b) {
if (a.length !== b.length) {
return false;
}
return crypto.timingSafeEqual(
Buffer.from(a, 'hex'),
Buffer.from(b, 'hex')
);
}
// 密码验证示例
function verifyPasswordSafely(inputPassword, storedHash) {
const inputHash = crypto.createHash('sha256').update(inputPassword).digest('hex');
return safeCompare(inputHash, storedHash);
}
总结
选择合适的哈希算法是确保应用安全的关键:
- 密码存储:使用bcrypt、scrypt或Argon2
- 数据完整性:使用SHA-256或SHA-512
- 数字签名:使用RSA-SHA256或ECDSA
- 避免使用:MD5、SHA-1(已不安全)
- 始终加盐:增加安全性
- 考虑性能:平衡安全性和性能
- 防止攻击:使用timingSafeEqual防止时序攻击
通过正确选择和实现哈希算法,可以大大提升应用的安全性。