哈希函数:签名系统中那个被低估的“中间人”
说到数字签名,很多人第一反应就是非对称算法——RSA、ECDSA、SM2这些。嗯,这没错。但真正扛起签名性能和安全大旗的,其实是哈希函数。我入行那会儿,有个老工程师跟我说过一句话:“没有哈希,签名就是个笑话。”当时不太理解,干久了才明白,这话真不夸张。
为什么签名前必须先做哈希?
你想想看,非对称签名算法处理长消息会怎样?
- 性能灾难:RSA-2048 一次签名大概能处理 256 字节数据。如果你要签一个 1MB 的文件,得拆成 4096 块,每块都做一次公钥运算。这速度,用户肯定骂娘。
- 安全漏洞:不哈希直接签名,容易遭受选择明文攻击。攻击者可以构造特殊的消息块,利用数学特性搞破坏。
- 标准矛盾:几乎所有签名标准(PKCS#1、P1363)都要求先对摘要签名,而不是对原文。
所以,哈希函数在签名系统里的角色,说白了就是个“减速换挡器”。把任意长度的输入,压缩成固定长度的摘要(比如 256 位),然后再交给私钥去签。这样一来,性能上去了,安全性也没降。
# 签名前的标准流程(伪代码)
message = "这是一个待签名的文档"
digest = sha256(message) # 原文 -> 固定长度摘要
signature = rsa_sign(digest, private_key) # 摘要 -> 签名值
我见过的哈希埋坑案例
我之前在项目中遇到过一次很奇怪的问题。HSM 返回的签名值,在 PC 端验签总是不通过。折腾了两天,最后发现是哈希用的编码不一样——HSM 那边用的是 SHA-256,但上层调用库默认走的是 SHA-256 双次哈希(某种特殊协议)。两边的摘要压根儿就不一样,签名怎么可能对得上?
- 算法名称是否一致(SHA-256 vs SHA-2-256,写法不同,结果一样)
- 输入数据类型:是原始字节,还是十六进制字符串
- 大端小端问题:HSM 和主机之间用网络序还是主机序
- 是否做了额外的填充或编码(比如 Base64)
三大主流哈希算法的对比
目前签名字段里,哈希算法三足鼎立。我个人习惯这样区分它们:SHA-256 是稳妥的老将,SHA-3 是未来的新贵,SM3 是中国的国标。下面这张表你可以存着,选型时翻一翻。
| 指标 | SHA-256 | SHA-3 | SM3 |
|---|---|---|---|
| 输出长度 | 256 位 (32 字节) | 可配置 (通常 256 位) | 256 位 (32 字节) |
| 核心结构 | Merkle–Damgård | 海绵构造 (Sponge) | Merkle–Damgård |
| 安全强度 | 高,未发现有效碰撞 | 极高,抗量子攻击潜力 | 高,接近 SHA-256 |
| 国内合规 | 通用场景,非国密标准 | 国际标准,国内非强制 | 国密标准,金融/政务必选 |
| 性能(软件实现) | 优,硬件加速广泛 | 中等,无专用加速较慢 | 良,国产芯片支持好 |
| HSM 支持度 | 几乎所有 HSM 都支持 | 部分高端型号支持 | 国密 HSM 标配,国际 HSM 需额外买授权 |
我为什么很少用 SHA-1 了?
这不是我个人喜好的问题。SHA-1 已经被理论攻破了——Google 和荷兰研究机构在 2017 年搞出了实际的碰撞攻击。现在如果还有人在签名里用 SHA-1,那基本等于裸奔。我在做内部审计时,就查出过某老系统还在用 SHA-1 签固件,立马让客户升级了。
哈希在签名流程中的真正位置
为了让你更直观地理解,我画了一张图,放在下面。这张图展示了整个签名验证的生命周期,哈希函数其实是连接“原始数据”和“密码学运算”之间的那座桥。
SM3 与 SHA-256 的细微差别
这两个算法输出都是 256 位,结构也类似,但 SM3 在消息扩展和压缩函数上做了改动。SM3 的迭代轮数从 64 轮加到了 68 轮,而且使用了不同的非线性置换。
在 HSM 里切换这两种算法时,你只需要注意一点:SM3 的 OID 是 1.2.156.10197.1.401,而 SHA-256 的 OID 是 2.16.840.1.101.3.4.2.1。接口调用时,指定错 OID,HSM 会直接报错,不会含糊。
# Python 示例:使用 pyshsm 库计算两种哈希
from hsm_lib import HSMClient
client = HSMClient(slot=0)
# 计算 SHA-256 摘要
digest_sha256 = client.hash(
data=b"Hello, world!",
algorithm="SHA256" # OID 自动匹配 2.16.840.1.101.3.4.2.1
)
# 计算 SM3 摘要
digest_sm3 = client.hash(
data=b"Hello, world!",
algorithm="SM3", # OID 自动匹配 1.2.156.10197.1.401
)
print(f"SHA-256: {digest_sha256.hex()}")
print(f"SM3: {digest_sm3.hex()}")
# 注意:实际输出不同,因为算法内核不同
哈希算法的未来方向
SHA-3 目前还没大规模铺开,但我建议你了解一下它的海绵结构。SHA-3 不像 SHA-256 那样有长度扩展攻击的隐患,这一点在做 HMAC 或者基于哈希的签名时尤其重要。SM3 在国密体系里已经是必选项了,如果你在做金融终端、政务系统,跑不了 SM3 的验证。
实话实说:不管是 SHA-256、SHA-3 还是 SM3,对于签名应用来说,安全性目前都是够用的。你不是在选哪个更安全,而是在选哪个 合规、生态好、HSM 支持度最高。我承接的项目里,八成的客户其实用 SHA-256 就够了,剩下的两成跑 SM3 是为了过密评。