4、数字签名原理:公钥密码学、哈希函数、签名验证
大家好。今天我们来聊聊数字签名这个核心话题。
你想想看,HSM 为什么会存在?说白了,就是得证明一段数据「是谁签的、签完之后有没有被改过」。所以,数字签名原理这块搞不清楚,后面审计日志签名根本没法落地。
我在项目里见过不少团队,直接把私钥读出来,在应用服务器上做签名。嗯……结果大家都懂,私钥被拿走了,签名就毫无意义了。
下面我把这三个东西拆开来讲:公钥密码学、哈希函数、签名验证到底是怎么配合的。
4.1 公钥密码学:公私钥对才是签名的基础
数字签名不是靠对称密钥,而是靠公钥密码学。为什么?因为你得让所有人能验证签名,但只有你才能生成签名。
这就像我有个印章(私钥),任何人都能用我的公开印鉴(公钥)来核验这个印章是不是真的。
- 私钥(Private Key): 必须存储在 HSM 内部,永不离开;负责对数据生成签名。
- 公钥(Public Key): 公开分发;负责验证签名;HSM 支持导出公钥。
常用的签名算法有 RSA(2048/4096 位)和 ECDSA(P-256/P-384)。我个人习惯:在密钥审计日志场景下,ECC P-384 已经够用;如果合规要求是 FIPS 140-3 Level 3,那就老老实实上 RSA 4096。
来,看一看这两种算法的简单对比:
| 特性 | RSA 签名 | ECDSA 签名 |
|---|---|---|
| 密钥长度 | 2048 / 4096 位 | P-256 / P-384 |
| 签名大小 | 与密钥长度相等(如 256 字节) | 很小(64~96 字节) |
| 计算速度 | 签名较慢,验证较快 | 签名较快,验证稍慢 |
| HSM 典型支持 | 非常成熟 | 现代 HSM 全面支持 |
| 审计日志场景 | 常用,兼容性极好 | 日志空间敏感时首选 |
CKA_SENSITIVE 和 CKA_EXTRACTABLE = FALSE。我曾经遇到过开发团队为了调试方便把私钥设成了可导出,结果上线前审计直接打回来了——私钥一旦出 HSM,HSM 的安全边界就失效了。
4.2 哈希函数:给数据打上「数字指纹」
签名的对象不可能是几十 GB 的原始审计日志。你得先给它算个摘要。
哈希函数的作用是:无论输入多大,输出一个固定长度的摘要。而且这个过程是不可逆的。
常用的哈希算法:
- SHA-256: 输出 32 字节。目前最通用,NIST 推荐。
- SHA-384: 输出 48 字节。用在 ECDSA P-384 上很合适。
- SHA-512: 输出 64 字节。计算量大一点,我一般不用在日志签名上。
核心原则:
先哈希,再签名。HSM 内部一般都支持直接输入 CKM_SHA256_RSA_PKCS 这种机制——把哈希和签名一步完成,减少了数据在传输过程中的暴露。
4.3 签名与验证:完整流程
数字签名的完整流程,我给你拆成两步。
签名生成(Sign):
- 获取审计日志原文(比如
log_entry.txt)。 - 计算哈希:
digest = SHA-256(log_entry)。 - 送入 HSM,用私钥对
digest做加密(或 ECC 点乘),生成签名signature。 - 输出
log_entry+signature一起存储。
签名验证(Verify):
- 拿到
log_entry。 - 重新计算哈希:
digest' = SHA-256(log_entry)。 - 用公钥解密签名,得到
digest''。 - 比较
digest'是否等于digest''。
嗯,这里要注意:上述第三步大多由 HSM 或密码库自动完成,你只需要告诉它:验证签名。
看一下核心的代码片段示意(PKCS#11 接口):
// 生成签名 (HSM 内部计算)
CK_SESSION_HANDLE hSession;
CK_OBJECT_HANDLE hPrivateKey;
CK_BYTE data[] = "audit log line #1001";
CK_BYTE signature[256];
CK_ULONG sigLen = sizeof(signature);
// 设置机制为 SHA256 + RSA PKCS
CK_MECHANISM mech = {CKM_SHA256_RSA_PKCS, NULL_PTR, 0};
C_SignInit(hSession, &mech, hPrivateKey);
C_Sign(hSession, data, sizeof(data)-1, signature, &sigLen);
// 验证签名 (HSM 内部计算)
CK_OBJECT_HANDLE hPublicKey;
C_VerifyInit(hSession, &mech, hPublicKey);
C_Verify(hSession, data, sizeof(data)-1, signature, sigLen); // 返回 CKR_OK 即通过
CKR_OK 才是验证通过。我曾经调试时发现返回的是 CKR_SIGNATURE_INVALID,排查了半天发现是日志读取时多了个换行符。所以我的习惯是:在做验证前,确保原文的字节与签名时完全一致——包括空格、换行、编码。
4.4 为什么审计日志需要哈希再签名?
你可能会问:为什么不直接对整条日志签名?
原因有两点:
- 性能: HSM 的签名运算比较慢,尤其是 RSA 4096。先哈希相当于把大数据压缩成 32 字节,再签。我在参与设计一个要求每秒签 20000 条日志的 HSM 时,如果直接对 Log 原文签名,HSM 根本扛不住;改成先哈希再签名,性能直接翻倍。
- 安全: 哈希函数让原文没法被篡改。哪怕攻击者改了原文的一个比特,重新算出来的摘要也完全不同,签名的验证就会失败。
说白了,哈希函数和签名就像是双重保险:哈希保证数据没被改,签名保证哈希是来自正确的人。
4.5 整章知识体系一览
这些东西其实不复杂,串起来就一行字:
下面这张图把整个流程和组件关系画清楚了,你看一眼就能记住。
一句话总结: 私钥签名、公钥验证、哈希压缩、HSM 保护。这四个合在一起,才叫一个完整、可信的数字签名体系。