4. 非对称密钥生成:RSA、ECC、DSA与椭圆曲线选择

4.1 三种主流算法,我到底该用哪个?

非对称密钥生成,是密码学里最常打交道的事。你可能会问:RSA、ECC、DSA,到底有什么区别?先别急,我用一张图把核心逻辑讲清楚。

非对称密钥生成三种算法对比 非对称密钥生成:RSA / ECC / DSA 核心逻辑 RSA 大整数因子分解 公私钥对:无附加结构 安全性依赖:大数分解 推荐位数:2048~4096 ECC 椭圆曲线离散对数 密钥更短,性能更好 安全性依赖:ECDLP 推荐曲线:P-256 / X25519 DSA 离散对数(模素数群) 仅用于签名,性能一般 安全性依赖:DLP 推荐位数:2048+ ⚠️ 椭圆曲线选择才是 ECC 的命门:用错曲线 = 白干活,甚至比 RSA 还容易翻车。 我个人推荐:新项目全用 ECC(X25519/Ed25519),遗留系统兼容 RSA-3072+,DSA 基本可以告别了。 密钥生成速度对比(相对值) | RSA: 慢 | ECC: 快 | DSA: 中等 安全性等级:ECC 256-bit ≈ RSA 3072-bit ≈ DSA 3072-bit

我自己的实践经验是:能用 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 存储,用了团队自创的「轻量级」椭圆曲线。结果三个月后就被一篇指出了安全缺陷——那条曲线不是安全曲线,存在异常点攻击。从那以后,我只用标准曲线,绝不自己「发明」曲线。

曲线选择有三大原则:

  1. 必须是随机或 Koblitz 形式的 NIST 曲线,或者更安全的 Curve25519 / Curve448。
  2. 避免过短的曲线:别为了性能选 160 位或更短的曲线,现在 224 位以下已经不推荐。
  3. 使用常数时间实现:生成密钥时不要依赖分支条件,否则侧信道攻击能把你私钥偷走。我在一次众测中成功利用 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 密钥生成后的「三连检查」

生成完密钥对,别急着用。我每次生成后一定做以下三步:

  1. 计算公钥指纹:用 SHA-256 对公钥算一次指纹,记录下来,后面做交叉验证。 openssl pkey -in pub.pem -pubin -outform DER | openssl dgst -sha256
  2. 签名自验证:生成一个随机消息,用私钥签名,再用公钥验签。确保自己能解自己的锁。
  3. 备份私钥:使用 openssl enc -aes-256-cbc -pbkdf2 加密私钥文件,丢到离线存储里。

核心总结:

  • RSA 生成慢,但生态兼容最好;推荐 3072 位起步,e=65537。
  • ECC 是未来的主流,曲线选 P-256 或 X25519/Ed25519。
  • DSA 能不用就别用,除非合规强制。
  • 生成后必须做自检,密钥的生命安全就靠这五分钟了。