3、RSA签名与验签:使用OpenSSL进行RSA签名、验签流程、签名格式(PKCS#1 v1.5 vs PSS)
签名验签这件事,说白了就是给数据盖个「防伪章」。你发出去的东西,别人得能确认是你发的,而且中途没被人动过。RSA签名就是干这个的。
我刚开始接触这块时,总觉得签名和加密是一回事。后来踩了坑才明白——加密是为了保密,签名是为了防篡改和防抵赖。两个概念千万别搞混。
3.1 签名与验签的核心逻辑
先理清流程。RSA签名用的是私钥签名、公钥验签。这和加密正好相反——加密是公钥加密、私钥解密。
关键区别:
- 签名:私钥持有者用私钥对数据摘要签名,任何人用公钥都能验证
- 加密:任何人用公钥加密数据,只有私钥持有者能解密
为什么会这样设计?你想想看,私钥只有你自己有,你用私钥签名的东西,别人用公钥能验证通过,那就证明是你签的。这就是「不可抵赖性」的来源。
我在项目中遇到过有人直接用私钥加密整个文件当签名,结果验签时发现效率极低。嗯,这里要注意——签名不是对原始数据加密,而是对数据的哈希值做签名运算。
3.2 使用OpenSSL进行RSA签名
实际操作时,我习惯用OpenSSL命令行来验证流程。先准备好私钥和公钥。
3.2.1 生成密钥对
# 生成2048位RSA私钥
openssl genrsa -out private.pem 2048
# 从私钥提取公钥
openssl rsa -in private.pem -pubout -out public.pem
我个人建议密钥长度至少2048位。1024位现在已经被认为不安全了,我在审计一个旧系统时就发现还在用1024位的RSA密钥——赶紧让他们换了。
3.2.2 签名操作
签名时,OpenSSL会自动完成哈希计算和签名运算。你只需要指定摘要算法和私钥文件。
# 对文件进行签名(默认使用PKCS#1 v1.5填充)
openssl dgst -sha256 -sign private.pem -out data.sig data.txt
# 查看签名值(二进制)
openssl enc -base64 -in data.sig
这里有个细节——dgst命令默认输出的是二进制签名文件。如果你需要传输或存储,建议用Base64编码一下。我一般会加个-base64参数一步到位。
# 直接输出Base64格式的签名
openssl dgst -sha256 -sign private.pem -out data.sig.b64 -base64 data.txt
3.3 使用OpenSSL进行验签
验签就是签名的逆过程。接收方拿到原始数据和签名值后,用公钥验证。
# 验签(二进制签名文件)
openssl dgst -sha256 -verify public.pem -signature data.sig data.txt
# 验签(Base64格式签名)
openssl dgst -sha256 -verify public.pem -signature data.sig.b64 -base64 data.txt
验签成功时,OpenSSL会输出Verified OK。如果失败,会输出Verification Failure。我曾经排查过一个验签失败的问题,折腾了半天才发现是签名时用的摘要算法和验签时指定的不一致——SHA-256写成SHA-1了。这种低级错误,你一定要注意。
小技巧: 验签时如果报错,先检查三点:
- 公钥是否和签名用的私钥匹配
- 摘要算法是否一致(SHA-256 vs SHA-1)
- 签名文件是否完整传输(二进制模式 vs 文本模式)
3.4 签名格式:PKCS#1 v1.5 vs PSS
RSA签名有两种主流填充方式。我刚开始用OpenSSL时根本没注意这个选项,直到有一次安全审计被问住了。
| 对比项 | PKCS#1 v1.5 | PSS (Probabilistic Signature Scheme) |
|---|---|---|
| 推出时间 | 1993年(老牌标准) | 2001年(PKCS#1 v2.1) |
| 随机性 | 固定填充(无随机盐) | 带随机盐(每次签名结果不同) |
| 安全性 | 有已知攻击向量(需谨慎使用) | 有严格安全证明,推荐使用 |
| 兼容性 | 广泛支持(几乎所有库都支持) | 较新,部分老旧系统不支持 |
| 性能 | 略快 | 略慢(多一步盐值生成) |
3.4.1 PKCS#1 v1.5签名
这是最传统的签名格式。OpenSSL默认使用的就是它。结构比较简单:
签名数据 = 0x00 || 0x01 || 填充字节(0xFF) || 0x00 || DigestInfo
其中DigestInfo包含了摘要算法标识和哈希值。这种格式的问题在于——填充是固定的,没有随机性。同一个数据每次签名结果都一样,这给某些攻击留下了空间。
注意: 虽然PKCS#1 v1.5签名本身目前没有发现致命漏洞,但业界趋势是逐步淘汰它。新系统建议直接上PSS。我在2022年参与的一个金融项目,监管要求明确写了「必须使用PSS签名」。别等到被要求改的时候再动手。
3.4.2 PSS签名
PSS引入了随机盐值,每次签名结果都不同。即使对同一份数据签名两次,得到的签名值也不一样。这大大增加了攻击难度。
PSS的签名结构更复杂:
签名数据 = 掩码后的哈希值 || 盐值 || 其他参数
OpenSSL使用PSS签名的命令:
# PSS签名(需要指定盐值长度)
openssl dgst -sha256 -sigopt rsa_padding_mode:pss \
-sigopt rsa_pss_saltlen:-1 -sign private.pem -out data_pss.sig data.txt
# PSS验签
openssl dgst -sha256 -sigopt rsa_padding_mode:pss \
-sigopt rsa_pss_saltlen:-1 -verify public.pem -signature data_pss.sig data.txt
rsa_pss_saltlen:-1表示盐值长度等于哈希值长度(SHA-256就是32字节)。我个人习惯用这个值,既安全又标准。
3.5 如何选择签名格式
我给你的建议很简单:
- 新系统:直接用PSS。安全、现代、没有历史包袱
- 兼容旧系统:用PKCS#1 v1.5,但要做好升级计划
- 高安全场景:必须PSS,而且盐值长度不要小于哈希值长度
我曾经帮一个客户迁移签名方案,他们原本用PKCS#1 v1.5签了几百万条数据。迁移时既要保证旧数据能验签,又要让新数据用PSS。最后我们做了个双签名过渡方案——新旧格式同时支持,等所有客户端升级后再切掉旧的。这种坑,能避就避。
一句话总结: RSA签名不是加密,是防伪。私钥签名、公钥验签。新项目用PSS,旧项目尽快迁移。签名前一定要哈希,别直接签原始数据。