哈希值
通过哈希函数将任意长度的数据转换为固定长度的值。用于数据唯一性验证和篡改检测。
哈希值是通过哈希函数对任意长度的输入数据生成的固定长度输出。相同的输入始终产生相同的哈希值,而从哈希值反推原始数据在计算上极其困难。这种单向性是哈希函数最核心的特征,也是安全和数据验证技术的基础。
常见的哈希算法包括 MD5 (128 位输出)、SHA-1 (160 位)、SHA-256 (256 位) 和 SHA-3。MD5 于 1991 年设计,长年被使用,但自 2004 年碰撞攻击被实证以来,在安全用途上已成为不推荐的选择。SHA-1 也在 2017 年由 Google 与 CWI 阿姆斯特丹的联合研究给出了实际的碰撞 (两份不同的 PDF 得到同一个哈希值的例子),目前推荐使用 SHA-256 以上的算法。不过被判为不推荐的,说到底只是「防止恶意篡改」这一类用途;在传输途中的损坏检测、缓存键的生成这些不设想攻击者的场合,计算速度快的 MD5 至今仍被实用地使用着。按用途是否属于安全来选择,这就是实务上的判断基准。
哈希值的应用场景非常广泛。在密码存储方面,存储哈希值而非明文,可以降低数据库泄露时原始密码被直接暴露的风险。实务中,添加盐值 (随机字符串) 后再进行哈希是标准做法,bcrypt 和 Argon2 这类专用算法受到推荐。这里单独使用 SHA-256 之所以不合适,原因恰恰在于 SHA-256「快」。穷举攻击一方也能以同样的速度去试,所以需要选择带有刻意放慢计算的机制 (bcrypt 的成本系数、Argon2 的内存用量) 的算法。在文件完整性验证方面,把下载文件的哈希值与官方网站公布的值进行比对,就可以检测出篡改或损坏。
在区块链技术中,哈希值发挥着核心作用。每个区块包含前一个区块的哈希值,从而形成链式结构。篡改过去的数据会导致后续所有哈希值发生变化,于是欺诈行为可被检测出来。Git 的版本控制也使用 SHA-1 哈希来标识提交和文件。
关于哈希值的一个常见误解是「哈希化等同于加密」。加密是使用密钥可以恢复原始数据的可逆过程,而哈希化是不可逆的。另一个误解是「哈希值相同则原始数据一定相同」。不同输入产生相同哈希值的现象称为碰撞 (collision),碰撞抗性是评估哈希算法安全性的关键指标。
从字符计数的角度看,哈希值始终以固定长度的 16 进制字符串表示。MD5 是 32 个字符,SHA-256 是 64 个字符,输出的字符数由算法唯一地确定。输入数据无论是 1 个字符还是 1 GB 的文件,输出的字符数都不会改变。这种固定长度的性质,在数据库的列设计和日志格式的设计上带来了可以事先确定存储区域这一实务上的好处。具体来说,SHA-256 若以 16 进制字符串保存就是固定 64 个字符,用 CHAR(64) 即可;若直接以二进制保存则只需 32 个字节,存储量减半。并没有必要用可变长的 VARCHAR 来预留。
实务中也有陷阱。16 进制的写法有大写与小写 2 种,同一个哈希值作为字符串却会变成不一致,因此比较之前若不放入统一到其中一种的处理,就会酿成「值本身是对的,验证却过不去」的事故。另外正如 Git 会把提交 ID 缩短到 7 个字符左右来显示,哈希值也有只把开头的几个字符当作标识符来用的情形,但缩短之后自然就更容易碰撞。仓库长大以后 7 个字符就不够用了,Git 自身也会按需要增加显示的位数。缩短的哈希值用于显示,核对时用全部位数,这样区分开来使用才安全。