加密原理(下):哈希算法、数字签名、消息认证码

上一章我们把对称加密、非对称加密家族认了个遍。但说实话,只靠加密还远远不够。
你想想看——你把文件用AES加密了,结果在传输过程中被人篡改了一个字节,接收方解密后得到的是一堆乱码。你根本不知道它被改过。所以我们需要另外三样东西:哈希算法、数字签名、消息认证码。这三兄弟才是把「防篡改」和「身份确认」补齐的关键。

一、哈希算法:数据的「指纹」

哈希算法也叫散列函数。它能把任意长度的输入,压缩成固定长度的输出。这个输出就是“哈希值”,或者说“摘要”。

我的一个口头禅:哈希是单向的。你没法从摘要反推出原文,就像你没法从一个人的指纹还原出他的脸。我踩过坑吗?有次在项目中用了MD5做备份校验,后来发现MD5已经被找到碰撞方法——两个不同文件能算出同样的MD5。太危险了。从那以后我全线换成了SHA-256。

算法输出长度(位)安全性等级我推荐用吗?
MD5128已破解(碰撞)❌ 别用
SHA-1160理论脆弱❌ 已废弃
SHA-256256安全✅ 首选
SHA-3可变安全✅ 可用
小提示:我们在HTTPS迁移中,签名证书和证书链校验大量使用SHA-256。如果你还在用SHA-1的证书,浏览器会报“不安全”。

二、消息认证码(MAC):有人和你共享密钥

哈希没问题,但如果有人在传输途中同时改了原文和哈希值,接收方拿新哈希对新原文一算——咦,竟然也对得上?

所以我们需要一个钥匙。消息认证码(MAC)就是哈希 + 共享密钥。发方用原文和一个双方事先约定好的密钥,一起丢进MAC算法,产出MAC标签。收方用同样的密钥和原文重新算一遍,结果一样就说明既没被篡改,而且确实是拥有密钥的那位发的。

最常见的是HMAC(Hash-based MAC)。比如HMAC-SHA256。我个人习惯在API签名校验里用HMAC——简单、快、不需要公钥基础设施。

// 伪代码示例:HMAC计算
key = "shared_secret_123"
message = "POST /api/order data=xxx"
mac = HMAC-SHA256(key, message)
// 把mac附在请求头里
// 服务端用同样的key和message算一遍,比对
注意:MAC只能证明消息来自拥有共享密钥的一方,但无法防抵赖——如果密钥泄露,任何人都能伪造。要想抗抵赖,得请出数字签名。

三、数字签名:签名是你的身份证

数字签名用非对称加密来对哈希值做签名。私钥签名,公钥验证。这样一来:

  • 只有持有私钥的人能生成签名——身份唯一
  • 任何篡改原文都会导致哈希值对不上——防篡改
  • 签名者事后无法抵赖——因为私钥只有他自己有

我记得刚接触HTTPS时,一直搞不懂为什么证书需要用CA签名。后来想通:CA用自己的私钥对网站的公钥做数字签名,浏览器拿到证书后用内置的CA公钥验签,确认这个公钥确实属于该网站。整个过程就是数字签名在撑腰。

常用签名算法:RSA-PSS、ECDSA。速度上ECDSA比RSA快,而且密钥长度更短。

特性消息认证码 (MAC)数字签名
密钥类型对称密钥非对称密钥对
抗抵赖性❌ 没有✅ 有
性能快慢(尤其签名)
典型场景API鉴权、短消息校验证书、合同、代码签名

四、三者如何协作?一张图看清

这几个东西到底怎么串起来的?我画了一张日常搬运的逻辑图。你看一遍就懂了。

哈希、MAC、数字签名协作流程 发送方 接收方 原文 哈希算法 哈希值 数字签名 私钥 (签名用) 传输:原文 + 签名 (也可以含MAC标签,但为了简洁只展示签名) 收到原文+签名 分离签名 哈希原文 用公钥解密 签名得到哈希值 比对两个哈希值 ✅ 通过/❌ 拒绝 哈希值一致 + 能成功用公钥解开签名 → 身份确认 & 数据完整
核心逻辑:哈希保证内容没变;数字签名保证内容没变且来源可靠(抗抵赖);MAC保证内容没变但需要共享密钥(不抗抵赖)。你在HTTPS迁移里主要和证书(数字签名)打交道,但了解MAC有助于理解TLS握手过程中临时会话密钥的派生。

五、避坑指南:我曾犯过的错

  • 以为哈希就是加密:我初期把哈希和加密混着叫,跟甲方说“把密码加密存起来”,结果数据库里哈希的密码没法解密。后来老老实实改口说“哈希存储”。千万别搞混,哈希不可逆。
  • 用了太短的哈希:有位同事为了省计算时间,坚持用MD5做文件完整性校验——被我们劝回来了。SHA-256虽然慢一丁点,但安全才是第一位的。
  • 数字签名≠加密:签名是“私钥签,公钥验”,加密是“公钥加密,私钥解密”。我被刚入行者问过“签名不也能加密吗?”其实签名过程里确实涉及加密运算,但目的完全不同——签名的输出是可公开验证的凭证,不是用来隐藏原文的。
生产提醒:在HTTPS证书替换场景中,如果你自己创建自签名证书做测试,一定要确认签名算法用的是SHA-256而不是SHA-1。Chrome等浏览器从2017年起就已经停止信任SHA-1签名的证书。我曾经就因为忘了调参数,被浏览器拦截了整整半天。