2026-02-23•ToolBox Team
密码学与哈希 - 保护用户数据的科学
🔧 返回工具箱 | Back to Tools
浏览所有工具 | View All Toolssecuritycryptographyhashingdata-protection
密码学与哈希:保护用户数据的科学
2023 年,某社交平台发生数据泄露,300 万明文密码被公开——这本可以避免。问题不在于黑客多聪明,而在于开发者对密码保护理解不足。这篇文章将揭示密码学和哈希的核心概念,以及如何在实际项目中应用它们。
1. 哈希函数基础
什么是哈希?
哈希是一种单向加密:将任意长度的数据转换为固定长度的字符串,且无法反向解密。
原文: "password123" → 哈希函数 → 杂凑值:5f4dcc3b5aa765d61d8327deb882cf99
常见哈希算法
| 算法 | 输出长度 | 安全性 | 用途 |
|---|---|---|---|
| MD5 | 128 位 | ❌ 已破裂 | 仅用于校验和 |
| SHA-1 | 160 位 | ⚠️ 较弱 | 不推荐用于密码 |
| SHA-256 | 256 位 | ✅ 安全 | 哈希、签名 |
| bcrypt | 可变 | ✅✅ 最强 | 密码存储推荐 |
| Argon2 | 可变 | ✅✅✅ 最强 | 密码存储最佳选择 |
关键点:永远不要用 MD5 或 SHA-1 存储密码!
试试哈希
在线快速查看哈希结果,可以使用我们的 MD5 工具 和 SHA 工具。
// Node.js 计算 SHA-256
const crypto = require('crypto');
function sha256(text) {
return crypto.createHash('sha256').update(text).digest('hex');
}
console.log(sha256('password123'));
// 输出:482c811da27a622d6f98c8b82e46988e3eba3e57bbd3a4e9bb4f4a5eb1b8eb3
2. 密码存储的正确方式
❌ 错误做法 1:明文存储
// 这是最严重的安全漏洞!
const user = {
username: 'alice',
password: 'password123' // ❌ 明文密码
};
db.users.insert(user);
如果数据库被黑客入侵,所有用户密码暴露。
❌ 错误做法 2:简单哈希
// ❌ 看起来更安全,但仍然有问题
const hashedPassword = sha256('password123');
db.users.insert({ username: 'alice', password: hashedPassword });
// 问题:相同密码的哈希值相同
sha256('password123') === sha256('password123') // true
这样可以通过彩虹表攻击(预先计算常见密码的哈希值)快速破解。
❌ 错误做法 3:简单盐值
// ❌ 虽然加了盐,但盐不够强
const salt = 'fixed_salt_123';
const hashedPassword = sha256(salt + 'password123');
// 问题:固定盐值被破解后,所有用户都失守
✅ 正确做法:bcrypt
const bcrypt = require('bcrypt');
// 注册时:生成盐值和哈希
async function registerUser(username, password) {
const saltRounds = 10; // 计算成本,值越大越慢(安全性更高)
const hashedPassword = await bcrypt.hash(password, saltRounds);
await db.users.insert({
username,
password: hashedPassword // ✅ 存储哈希值
});
}
// 登录时:验证密码
async function loginUser(username, inputPassword) {
const user = await db.users.findOne({ username });
const isValid = await bcrypt.compare(inputPassword, user.password);
if (isValid) {
console.log('✓ 登录成功');
} else {
console.log('✗ 密码错误');
}
}
bcrypt 的优势:
- 自动生成随机盐值:每次哈希都不同
- 可调计算成本:可以根据硬件性能调整,即使 GPU 也无法快速破解
- 防时序攻击:对比操作耗时稳定,无法通过响应时间推断信息
✅✅ 最佳实践:Argon2
const argon2 = require('argon2');
async function registerUser(username, password) {
try {
const hashedPassword = await argon2.hash(password, {
type: argon2.argon2id, // 最强的 Argon2 变种
memoryCost: 19 * 1024, // 19MB 内存
timeCost: 2, // 2 次迭代
parallelism: 1 // 单线程
});
await db.users.insert({ username, password: hashedPassword });
} catch (err) {
console.error('哈希失败:', err);
}
}
async function loginUser(username, inputPassword) {
const user = await db.users.findOne({ username });
try {
const isValid = await argon2.verify(user.password, inputPassword);
return isValid;
} catch (err) {
console.error('验证失败:', err);
return false;
}
}
Argon2 为什么比 bcrypt 更强:
- 内存困难:需要大量内存,GPU 攻击无法加速
- 时间困难:需要多次迭代
- 防彩虹表:即使获得哈希,也无法批量验证
Google 和 OWASP 的官方建议:新项目优先使用 Argon2。
3. 其他加密应用场景
数据完整性校验
在文件传输或备份后验证完整性:
const crypto = require('crypto');
const fs = require('fs');
// 计算文件的 SHA-256
function getFileHash(filePath) {
const content = fs.readFileSync(filePath);
return crypto.createHash('sha256').update(content).digest('hex');
}
const hash1 = getFileHash('data.zip');
// 传输 data.zip...
const hash2 = getFileHash('downloaded-data.zip');
if (hash1 === hash2) {
console.log('✓ 文件完整,未被篡改');
} else {
console.log('✗ 文件已损坏或被篡改');
}
API 签名与验证
确保 API 请求来自授权的客户端:
const crypto = require('crypto');
// 服务器端:生成签名
function generateSignature(data, secret) {
return crypto
.createHmac('sha256', secret)
.update(JSON.stringify(data))
.digest('hex');
}
// 客户端:签署请求
const payload = { userId: 123, action: 'transfer', amount: 100 };
const signature = generateSignature(payload, 'my_secret_key');
// 发送:payload + signature
// 服务器端:验证签名
function verifySignature(data, signature, secret) {
const expectedSignature = generateSignature(data, secret);
return signature === expectedSignature; // 恒定时间比较更安全
}
if (verifySignature(payload, receivedSignature, 'my_secret_key')) {
console.log('✓ 签名有效');
} else {
console.log('✗ 签名无效,可能被篡改');
}
4. 常见安全陷阱
陷阱 1:密码字段设为可逆加密
// ❌ 不安全
const password = encrypt('password123', key); // 可解密!
db.users.insert({ password });
// ✅ 正确
const password = await bcrypt.hash('password123', saltRounds);
db.users.insert({ password });
可逆加密需要保管密钥,一旦泄露所有密码都失守。不可逆哈希即使泄露也无法直接获得密码。
陷阱 2:在日志中打印密码
// ❌ 永远不要这样做
console.log('用户密码:', inputPassword);
logger.info(`用户 ${username} 登录,密码: ${password}`);
// ✅ 只记录必需的信息
logger.info(`用户 ${username} 登录成功`);
日志文件可能被黑客获取,永远不要记录敏感信息。
陷阱 3:忘记密码功能
// ❌ 发送原密码
const user = db.users.findOne({ email });
sendEmail(user.email, `您的密码: ${user.password}`); // ❌ 这是哈希值,无法恢复!
// ✅ 生成一次性重置令牌
const resetToken = crypto.randomBytes(32).toString('hex');
const resetTokenHash = await bcrypt.hash(resetToken, 10);
db.passwordResets.insert({
userId: user.id,
token: resetTokenHash,
expiresAt: Date.now() + 1800000 // 30 分钟有效期
});
const resetLink = `https://yoursite.com/reset-password?token=${resetToken}`;
sendEmail(user.email, `点击链接重置密码: ${resetLink}`);
5. HTTPS 与传输安全
哈希只是纵深防御的一部分,传输层也要保护:
# Nginx 配置强制 HTTPS
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri; # 重定向到 HTTPS
}
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# 确保使用强加密套件
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
}
检查您网站的 SSL/TLS 配置,可以使用我们的 SSL 检查工具。
密码安全清单
在投入生产前,检查以下项:
- 使用 bcrypt 或 Argon2 存储密码,不使用 MD5/SHA-1
- 从不在日志、错误消息或传输中存储明文密码
- 实现"忘记密码"功能,使用临时令牌而非恢复原密码
- 启用 HTTPS,使用最新的 TLS 版本(1.2+)
- 实现 API 速率限制,防止暴力破解
- 定期进行安全审计和渗透测试
- 向用户教育密码安全知识(复杂密码、勿重复使用等)
相关工具推荐: