4、常用签名算法:RSA与ECDSA
签名算法是整个日志防篡改链条的“锁”。锁不行,链再长也白搭。 目前工业界用得最多的就是 RSA 和 ECDSA。 我个人习惯先看业务场景再选算法,毕竟这两东西差别挺大。
4.1 RSA:老牌可靠,但有点“胖”
RSA 诞生于 1977 年,数学基础是“大整数分解困难”。 签名时用私钥加密摘要,验签时用公钥解密比对。 我最早做 PKI 项目时用的就是 RSA 2048,稳定,但心里总想着它那又长又慢的签名生成。
核心参数:密钥长度通常选 2048 或 4096 位。安全强度对标对称加密 112 位和 128 位。
RSA 的签名过程其实很直接:
- 对消息做哈希(比如 SHA-256)得到摘要
- 用私钥对摘要做 RSA 加密(实际是模幂运算)得到签名
- 验签时用公钥解密,比对哈希值
避坑:我曾经遇到一个项目,日志签名用 RSA 1024,结果 3 年后被证明不安全,全部证书得换。现在起码要 2048 起步,别省那点计算资源。
4.2 ECDSA:短小精悍,但曲线选择要当心
ECDSA 基于椭圆曲线离散对数难题。同样的安全强度,密钥长度只有 RSA 的十分之一。 你想想看,2048 位 RSA 和 256 位 ECDSA 安全等级差不多,但 ECDSA 的签名长度才 64 字节,RSA 要 256 字节。 在日志链式存储里,每个区块都要嵌入签名,ECDSA 能让链长得更长、更省空间。
ECDSA 签名过程比 RSA 复杂一点,涉及随机数 k:
- 计算消息的哈希 z
- 选取随机数 k,计算椭圆曲线点 R = k * G,取 x 坐标 r
- 计算 s = k⁻¹ (z + r * d) mod n,d 是私钥
- 签名对为 (r, s)
提示:随机数 k 不能用两次,也不能用固定值。否则私钥当场泄露。我见过一位同事把 k 写死在代码里,测试网爆了后直接被提取私钥…
4.3 核心对比:选谁更合适?
| 对比维度 | RSA | ECDSA |
|---|---|---|
| 密钥长度(安全128位) | 3072 位 | 256 位 |
| 签名长度 | ≈ 256 字节 | ≈ 64 字节 |
| 签名速度 | 慢(模幂运算) | 快(标量乘法) |
| 验签速度 | 较慢 | 稍快 |
| 抗量子攻击 | 弱(Shor算法) | 弱(Shor算法) |
| 标准支持 | PKCS#1 v1.5 / PSS | FIPS 186-4,SECG |
简单结论:日志链式存储这种签名密集的场景,我倾向用 ECDSA。 体积小、速度快,256 位已经够用。除非你对接的老系统只认 RSA,那就用 RSA-PSS(更安全,避免 PKCS#1 v1.5 的漏洞)。
4.4 代码示例:Go 语言实现签名与验签
我平时用 Go 写日志链组件,下面给两个核心函数,你感受下差异:
// RSA 签名(使用 PSS)
func RSASign(msg []byte, priv *rsa.PrivateKey) ([]byte, error) {
hashed := sha256.Sum256(msg)
return rsa.SignPSS(rand.Reader, priv, crypto.SHA256, hashed[:], nil)
}
// ECDSA 签名(P256 曲线)
func ECDSASign(msg []byte, priv *ecdsa.PrivateKey) ([]byte, error) {
hashed := sha256.Sum256(msg)
r, s, _ := ecdsa.Sign(rand.Reader, priv, hashed[:])
// 将 (r,s) 序列化为 ASN.1 或固定 64 字节
signature := append(r.Bytes(), s.Bytes()...)
return signature, nil
}
注意 ECDSA 验签时要检查 (r,s) 是否在合法范围内,并且 公钥曲线必须和签名曲线一致。 我遇到过有人脱机导入公钥时误用了 secp256k1 的曲线,结果验签全失败。
4.5 知识图谱:RSA vs ECDSA 签名流程
4.6 总结:实际选型的几个铁律
- 性能优先:日志链每秒可能有成千上万次签名 → 选 ECDSA(P-256 或 P-384)
- 兼容优先:要和各种 CA、HSM 互通 → 选 RSA 2048(PSS 填充)
- 安全底线:两个算法都不要用 1024 位或 secp160k1 等弱曲线
- 防重放:签名消息里务必带上时间戳或区块计数器
我自己的经验:做日志链存储,一开始就定好曲线和填充方式,后期改算法代价极大。不如先花半天测试两种算法在目标 CPU 上的签名吞吐,再决定。