加密与安全通信

哈希函数 (Hash Function)

约 5 分钟阅读

什么是哈希函数

哈希函数(Hash Function)是一种接受任意长度输入数据并输出固定长度值(哈希值,也称摘要)的函数。相同的输入始终产生相同的哈希值,但从哈希值恢复原始数据在计算上是不可行的,这一特性称为「单向性」。

例如,用 SHA-256 对 hello 进行哈希运算,会得到 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 这个 64 字符的十六进制字符串。即使输入只改变一个字符,哈希值也会完全不同(雪崩效应),无法从中推测原始输入。

哈希函数与加密不同。加密可以用密钥还原原始数据,而哈希是不可逆的。这种区别正是密码存储和数据篡改检测选择哈希函数的原因。

主要算法比较

MD5(128 位)
1991 年设计。速度快但碰撞抗性已被攻破,禁止用于安全用途。仅可用于文件校验和(损坏检测)。
SHA-1(160 位)
2005 年出现理论碰撞攻击,2017 年 Google 实证了实际碰撞(SHAttered)。部分遗留系统仍在使用,但新项目不推荐采用。
SHA-256(256 位)
SHA-2 家族的代表。目前没有已知的碰撞攻击,广泛用于 TLS 证书、区块链和文件验证。通用哈希的标准选择。
bcrypt
专为密码存储设计。具有可配置的成本因子,故意降低计算速度以抵抗 GPU 加速的暴力破解。内置自动生成盐值的机制。

通用数据验证使用 SHA-256,密码存储使用 bcrypt(或 Argon2)是当前的最佳实践。请勿将 MD5 和 SHA-1 用于安全目的。

密码存储中的作用

Web 服务存储用户密码时,绝不能将密码明文保存在数据库中。一旦数据库泄露,所有用户的密码都会暴露。正确的做法是将密码哈希后存储,登录时对输入的密码进行相同的哈希运算并与存储的哈希值比较。

但仅仅哈希是不够的。相同的密码会产生相同的哈希值,攻击者可以使用预先计算的「密码→哈希值」对照表(彩虹表)来反查原始密码。

防御措施是「盐值」(Salt)。盐值是为每个密码附加的随机字符串,在哈希之前与密码拼接。即使密码相同,不同的盐值也会产生不同的哈希值,使彩虹表失效。bcrypt 和 Argon2 会自动生成和管理盐值。配合密码管理器生成长随机密码,可以将彩虹表和暴力破解的风险降到最低。

区块链中的应用

区块链巧妙地利用了哈希函数的特性。每个区块都包含前一个区块的哈希值,形成链式结构。篡改任何一个区块的数据都会改变其哈希值,导致与下一个区块中记录的「前一区块哈希值」不匹配。要篡改一个区块,就必须重新计算之后所有区块,除非掌握超过全网的算力,否则实际上不可能实现。

比特币挖矿(工作量证明)是一场寻找特定 SHA-256 哈希值(前导零达到一定数量)的竞赛。由于哈希函数的单向性,只能通过穷举尝试来找到满足条件的输入,这种计算成本支撑着区块链的安全性。

彩虹表攻击与盐值

彩虹表攻击是预先大量计算常用密码的哈希值,然后与泄露的哈希值进行比对以还原原始密码的手法。

  • 盐值(Salt):为每个密码附加随机值后再哈希。不同的盐值使相同密码产生不同的哈希值,使预计算表失效。
  • 胡椒值(Pepper):在盐值之外,为密码附加一个应用全局的秘密值。即使数据库泄露,只要胡椒值存储在别处,哈希值的破解就更加困难。
  • 拉伸(Stretching):将哈希计算重复数千到数万次,故意增加单次哈希的计算时间。bcrypt 的成本因子和 PBKDF2 的迭代次数就是这种方法的实现。

常见误解

哈希和加密是一回事
加密可以用密钥还原原始数据,是可逆的;哈希是不可逆的单向操作。密码存储应使用哈希,通信保护应使用加密,需要根据用途正确选择。
用 SHA-256 哈希密码就安全了
SHA-256 作为通用哈希很优秀,但对密码存储来说太快了。GPU 每秒可以计算数十亿次 SHA-256 哈希。密码存储应使用 bcrypt 或 Argon2 等故意降速的算法。
分享

相关术语