Back to Blog
2026-02-23ToolBox Team

密码学与哈希 - 保护用户数据的科学

🔧 返回工具箱 | Back to Tools

浏览所有工具 | View All Tools
securitycryptographyhashingdata-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 速率限制,防止暴力破解
  • 定期进行安全审计和渗透测试
  • 向用户教育密码安全知识(复杂密码、勿重复使用等)

相关工具推荐