4. 哈希算法与数字签名:SHA-256/SM3、签名生成与验证、签名与加密的区别
这一章我们来聊聊哈希和数字签名。说实话,这两个概念是 PKI 体系的基石。你想想看,如果没有哈希算法,数字签名就失去了效率;如果没有数字签名,那整个 PKI 就没了灵魂。
我在做第一个企业级 PKI 项目时,就被客户问过:「你们这个签名和加密到底有啥区别?不都是把数据变来变去吗?」嗯,这个问题其实挺有代表性的。今天我们就把它彻底讲清楚。
4.1 哈希算法:SHA-256 与 SM3
哈希算法,说白了就是一个「数据指纹」生成器。不管你的输入是一句话还是一部电影,哈希算法都能给你一个固定长度的摘要。而且这个摘要几乎不可能反向还原出原始数据。
核心特性:
- 单向性:从哈希值无法反推出原文
- 抗碰撞性:很难找到两个不同输入产生相同哈希值
- 雪崩效应:输入改一个比特,输出完全变样
- 固定输出长度:SHA-256 输出 256 位,SM3 也是 256 位
4.1.1 SHA-256
SHA-256 是 SHA-2 家族的一员,输出 256 位(32 字节)的哈希值。它在全球范围内被广泛使用,从 HTTPS 证书到区块链,到处都有它的身影。
我个人习惯在项目里优先推荐 SHA-256,原因很简单:兼容性好,安全性经过长期验证。我记得有一次帮一家金融机构做 PKI 升级,他们原来的系统还在用 SHA-1,我直接建议一步到位换 SHA-256。为什么?因为 SHA-1 已经被证明存在实际碰撞攻击了,继续用就是在给自己埋雷。
4.1.2 SM3 国密算法
SM3 是中国国家密码管理局发布的哈希算法标准,同样输出 256 位。它的设计思路和 SHA-256 类似,但在一些细节上做了优化。
这里有个避坑指南:SM3 和 SHA-256 不兼容。你不能拿 SM3 的哈希值去和 SHA-256 的哈希值做比较,它们完全是两套体系。
| 对比项 | SHA-256 | SM3 |
|---|---|---|
| 输出长度 | 256 位 | 256 位 |
| 分组大小 | 512 位 | 512 位 |
| 轮数 | 64 轮 | 64 轮 |
| 消息扩展 | 线性 | 非线性 |
| 应用场景 | 国际通用 | 国内合规 |
我的建议:如果项目有国密合规要求,直接用 SM3。如果是面向全球的业务,SHA-256 更稳妥。两者在安全性上目前没有本质差距。
4.2 数字签名:生成与验证流程
数字签名解决的是「谁发的、内容有没有被改」这两个问题。它的核心思想是:用私钥签名,用公钥验证。
你想想看,如果我把一份合同签上我的名字,别人拿到后能确认是我签的吗?在纸质世界里,笔迹鉴定可以做到。在数字世界里,数字签名就是那个「笔迹鉴定」工具。
4.2.1 签名生成流程
签名生成分为三步:
- 计算哈希:对原始消息做哈希运算,得到消息摘要
- 私钥加密:用签名者的私钥对摘要进行加密运算
- 附加签名:将加密后的结果作为签名附加到消息上
// 伪代码示例:签名生成
message = "这是一份重要合同"
hash = SHA256(message) // 第一步:计算哈希
signature = RSA_Sign(hash, privateKey) // 第二步:私钥签名
output = message + "||" + signature // 第三步:附加签名
注意:签名不是对整个消息做加密,而是对消息的哈希值做加密。这样做的好处是效率高——不管消息多大,签名运算只处理固定长度的哈希值。
4.2.2 签名验证流程
验证流程是签名生成的逆过程:
- 分离签名:从收到的数据中分离出原始消息和签名
- 计算哈希:对原始消息做同样的哈希运算
- 公钥解密:用签名者的公钥解密签名,得到原始摘要
- 比较摘要:对比两个摘要是否一致
// 伪代码示例:签名验证
received_message = "这是一份重要合同"
received_signature = "0x1234..."
hash = SHA256(received_message) // 第一步:计算哈希
decrypted_hash = RSA_Verify(received_signature, publicKey) // 第二步:公钥解密
if hash == decrypted_hash:
print("签名验证通过")
else:
print("签名验证失败")
我在项目中遇到过好几次签名验证失败的情况,最后发现都是因为哈希算法不一致。比如签名方用了 SHA-256,验证方却用了 SM3,那肯定对不上。所以,签名和验证必须使用相同的哈希算法,这一点一定要在协议设计时就约定好。
4.3 签名与加密的区别
这个问题我经常被问到。很多人觉得签名和加密都是「用密钥处理数据」,容易搞混。其实它们的核心区别非常清晰:
| 对比维度 | 数字签名 | 加密 |
|---|---|---|
| 核心目的 | 身份认证 + 完整性 | 机密性 |
| 使用的密钥 | 私钥签名,公钥验证 | 公钥加密,私钥解密 |
| 处理对象 | 消息的哈希值 | 整个消息 |
| 输出结果 | 签名值(固定长度) | 密文(与消息长度相关) |
| 是否可逆 | 不可逆(哈希) | 可逆(解密) |
说白了,签名是为了证明「你是谁」和「内容没被改」,加密是为了保证「别人看不到」。这两个目的完全不同,使用的密钥方向也正好相反。
一个经典比喻:
- 签名 = 你在合同上盖章,别人可以验证这个章是不是你的
- 加密 = 你把合同放进保险箱,只有有钥匙的人才能打开
我曾经帮一个客户排查问题,他们用 RSA 私钥去「加密」数据,然后用公钥去「解密」。这其实就是在做签名,但他们误以为是加密。虽然数学运算是一样的,但语义完全不同。签名不保证机密性,加密不保证身份认证。这两个概念千万别混用。
4.4 核心知识体系
下面这张图把哈希、签名、加密的关系梳理清楚了:
实战建议:在设计 PKI 方案时,先把哈希算法定下来。SHA-256 和 SM3 选一个,然后整个体系都统一用这个。我见过最头疼的情况就是签名用 SHA-256,证书签名用 SM3,最后两边对不上,排查了整整两天。
好了,这一章的内容就到这里。哈希和签名是 PKI 的根基,理解透了,后面讲证书链、CRL、OCSP 的时候你会觉得轻松很多。记住一句话:哈希做摘要,签名做认证,加密做保密,这三个角色各司其职,谁也替代不了谁。