4. 非对称密钥生成:RSA、ECC、DSA与椭圆曲线选择
4.1 三种主流算法,我到底该用哪个?
非对称密钥生成,是密码学里最常打交道的事。你可能会问:RSA、ECC、DSA,到底有什么区别?先别急,我用一张图把核心逻辑讲清楚。
我自己的实践经验是:能用 ECC 就别用 RSA。不是 RSA 不行,而是同样的安全强度,ECC 的密钥长度只有 RSA 的十分之一。你想想看,几百字节的证书和几千字节的证书,传输和存储压力差太多了。
一句话选型建议:
- 签名场景 → ECDSA 或 EdDSA(首选 Ed25519)
- 加密场景 → RSA-OAEP 或 ECIES(更推荐 ECIES)
- 密钥交换 → ECDH(X25519 是目前最保险的选择)
- 纯签名协议兼容 → 保留 DSA?别了,我上次在 FIPS 140-2 合规项目里被迫用了 DSA,生成的密钥对又长又慢,后来全换成 ECDSA 了。
4.2 RSA 密钥生成:安全第一,速度第二
RSA 生成密钥对,核心就是找到两个大素数 p 和 q,然后算 n = p*q。嗯,听起来简单,但在实践中容易踩坑。
4.2.1 素数生成要足够随机
千万别用固定的种子或者弱随机数生成器。我见过一个项目,用时间戳做种子生成 p 和 q,结果两个素数居然一样……那 n = p*p,等于直接把私钥送人了。
# OpenSSL 正确生成 RSA 私钥(推荐位数为 3072 或 4096)
openssl genrsa -out private.pem 3072
# 提取公钥
openssl rsa -in private.pem -pubout -out public.pem
# 查看私钥细节
openssl rsa -in private.pem -text -noout
个人技巧: 生成后立即用 openssl rsa -check -in private.pem 做一遍自检。我吃过口头承诺的亏——对方交付的私钥文件里 p 和 q 是相同的。
4.2.2 指数 e 的选择
公钥指数 e 一般固定为 65537(0x10001),原因有俩:一是它是个很大的素数,能抵抗低指数攻击;二是计算效率高(只有两个 1 比特)。千万别用 e=3,那会导致低指数广播攻击,我在一次渗透测试中重现过这类攻击,效果拔群。
4.3 ECC 密钥生成:椭圆曲线选对了,就成功一半
ECC 的密钥生成,算法本身很简单:选择一个基点 G,随机生成私钥 d(一个整数),然后计算公钥 Q = d * G。但这里的坑都在椭圆曲线的选择上。
4.3.1 常用曲线的三板斧
| 曲线名称 | 标准 | 安全强度 | 签名/交换 | 我的使用建议 |
|---|---|---|---|---|
| P-256 (secp256r1) | NIST SP 800-186 | 128-bit | ECDSA, ECDH | 最广泛兼容,绝大多数平台自带 |
| X25519 | RFC 7748 | 128-bit | ECDH 密钥交换 | 抗侧信道,无专利,我主力推荐 |
| Ed25519 | RFC 8032 | 128-bit | EdDSA 签名 | 签名短、生成快、防伪造,签名场景首选 |
| P-384 (secp384r1) | NIST SP 800-186 | 192-bit | ECDSA, ECDH | 合规要求高的地方用(比如美国政府系统) |
| P-521 (secp521r1) | NIST SP 800-186 | 256-bit | ECDSA, ECDH | 性能开销大,非超高安全需求不必上 |
4.3.2 怎么选?不要选奇怪的自定义曲线
曾经的血泪史: 我曾经在某个 IoT 项目里,为了节省几 KB 存储,用了团队自创的「轻量级」椭圆曲线。结果三个月后就被一篇指出了安全缺陷——那条曲线不是安全曲线,存在异常点攻击。从那以后,我只用标准曲线,绝不自己「发明」曲线。
曲线选择有三大原则:
- 必须是随机或 Koblitz 形式的 NIST 曲线,或者更安全的 Curve25519 / Curve448。
- 避免过短的曲线:别为了性能选 160 位或更短的曲线,现在 224 位以下已经不推荐。
- 使用常数时间实现:生成密钥时不要依赖分支条件,否则侧信道攻击能把你私钥偷走。我在一次众测中成功利用 timing attack 提取过某 HSM 的 ECC 私钥,就因为曲线上的点乘实现用了非定时的 Montgomery ladder。
4.3.3 实操:生成 Ed25519 密钥对
# 用 OpenSSL 1.1.1+ 生成 Ed25519 私钥
openssl genpkey -algorithm Ed25519 -out ed25519_private.pem
# 提取公钥
openssl pkey -in ed25519_private.pem -pubout -out ed25519_public.pem
# 查看密钥参数
openssl pkey -in ed25519_private.pem -text -noout
小提示: Ed25519 生成的私钥只有 32 字节,公钥也是 32 字节,签名才 64 字节。RSA-3072 的签名是 384 字节,差了 6 倍。如果你的应用是区块链交易签名或 TPM 密钥,Ed25519 基本是标配了。
4.4 DSA 密钥生成:正在被淘汰的「老将」
DSA 基于离散对数,安全性等价于 RSA 同长度。但它有几个硬伤:
- 密钥生成需要额外的素数 q 和 g,流程比 RSA 还复杂
- 签名过程依赖随机数 k,如果 k 值泄露或有偏差,私钥直接暴露。著名的 Sony PS3 ECDSA 签名 bug 就是因为 k 值固定。
- 性能不如 ECDSA
我自己的观点:新项目别再启用 DSA 了。除非你要跟古老的政府系统对接,且对方只认 DSA。即使必须用,也请保证 p 至少 3072 位。
# 生成 DSA 参数(p, q, g)
openssl dsaparam -out dsaparam.pem 3072
# 生成 DSA 私钥
openssl gendsa -out dsa_private.pem dsaparam.pem
# 公钥
openssl dsa -in dsa_private.pem -pubout -out dsa_public.pem
注意: DSA 的随机数 k 不能重用。我在一次代码审计中看到有人为了「性能优化」把 k 缓存了……我知道后直接标记为高危漏洞。重用 k,通过两个签名就能解出私钥,这不是玩笑。
4.5 密钥生成后的「三连检查」
生成完密钥对,别急着用。我每次生成后一定做以下三步:
- 计算公钥指纹:用 SHA-256 对公钥算一次指纹,记录下来,后面做交叉验证。
openssl pkey -in pub.pem -pubin -outform DER | openssl dgst -sha256 - 签名自验证:生成一个随机消息,用私钥签名,再用公钥验签。确保自己能解自己的锁。
- 备份私钥:使用
openssl enc -aes-256-cbc -pbkdf2加密私钥文件,丢到离线存储里。
核心总结:
- RSA 生成慢,但生态兼容最好;推荐 3072 位起步,e=65537。
- ECC 是未来的主流,曲线选 P-256 或 X25519/Ed25519。
- DSA 能不用就别用,除非合规强制。
- 生成后必须做自检,密钥的生命安全就靠这五分钟了。