两种算法,同一个任务
RSA 签名建立在以大合数为模数的模幂运算之上:私钥由两个保密的素数推导而来,而分解它们的乘积之难,正是私钥得以保密的原因。ECDSA(椭圆曲线数字签名算法,由 FIPS 186-5 标准化)则在椭圆曲线上的点群中运算,其困难问题是椭圆曲线离散对数问题。
困难问题不同,带来的实际后果是:因数分解的攻击难度随规模增长得比椭圆曲线离散对数的慢,因此要保持相同的安全余量,RSA 密钥必须比 EC 密钥增长得快得多。下文中大部分大小和性能上的差异,都源于这一点。
大小:同等强度下的密钥和签名
按照 NIST 对安全强度的比较(见 SP 800-57 第 1 部分),256 位椭圆曲线密钥与 3072 位 RSA 模数同属 128 位安全级别:
| 安全级别 | ECDSA 曲线 | EC 公钥 | EC 签名 | RSA 模数 | RSA 签名 |
|---|---|---|---|---|---|
| 112 位 | P-224 | 57 B / 29 B* | 56 B(原始) | 2048 位 | 256 B |
| 128 位 | P-256 | 65 B / 33 B* | 64 B(原始;DER 约 70~72 B) | 3072 位 | 384 B |
| 192 位 | P-384 | 97 B / 49 B* | 96 B(原始) | 7680 位 | 960 B |
| 256 位 | P-521 | 133 B / 67 B* | 132 B(原始) | 15360 位 | 1920 B |
(*未压缩 / 压缩点编码。)在 128 位级别——这也是如今新系统的最低要求——ECDSA P-256 签名为 64 字节,而 RSA-3072 为 384 字节,公钥也只有后者的十分之一左右。即使是广泛部署(安全余量更低)的 RSA-2048,签名也有 256 字节。在每个字节都很重要的场合——TLS 握手中的证书、HTTP 头部中的令牌、区块链上的交易——椭圆曲线在大小上总是胜出。
注意 ECDSA 签名的两个数字:“原始”格式 64 字节,DER 编码约 70~72 字节。同一个数学意义上的签名有两种常见的传输编码,把二者搞混是“invalid signature”错误最常见的原因之一。这个话题另有专门的文章。
速度:签名与验证的不对称
两种算法的性能特征恰好相反。这种不对称值得牢记,因为它决定着实际的架构:
- RSA 验证非常快。验证只需用公钥指数做一次模幂运算,而公钥指数几乎总是很小的值 65537。这就是 RSA 在证书链中坚守这么久的原因:一张证书只签名一次,却要被验证数十亿次。
- RSA 签名很慢,因为它要在 2048~4096 位的模数上用全长的私钥指数做幂运算。RSA 密钥生成还要更慢——它必须搜索大的随机素数,可能要花上明显可感知的零点几秒,而且耗时的波动无法预测。
- ECDSA 签名很快——只需在 256 位的域上做一次标量乘法,通常比 RSA-2048 签名快几倍到一个数量级,与 RSA-3072 相比差距还要大得多。EC 密钥生成几乎没有成本(一次标量乘法),这对临时密钥和每台设备各自的密钥很重要。
- ECDSA 验证比 RSA 验证慢(大致相当于两次标量乘法),不过在现代硬件上,绝对速度仍然很快。
用几行 Node.js 代码就能直接看到大小上的差异:
import { generateKeyPairSync, sign } from "node:crypto";
const message = Buffer.from("measure me");
const ec = generateKeyPairSync("ec", { namedCurve: "P-256" });
const rsa = generateKeyPairSync("rsa", { modulusLength: 3072 }); // slow: prime search
const ecSig = sign("sha256", message, ec.privateKey); // DER-encoded by default
const rsaSig = sign("sha256", message, rsa.privateKey);
console.log("ECDSA P-256:", ecSig.length, "bytes"); // ~70-72 (64 raw)
console.log("RSA-3072: ", rsaSig.length, "bytes"); // 384
两者如今各用在哪里
- TLS / X.509:两者随处可见。在已部署的证书中 RSA 仍占主导,但 ECDSA 证书(P-256、P-384)已是各大 CA 的标准产品,能明显缩小握手的数据量;许多大型网站提供 ECDSA 证书,并以 RSA 作为后备。TLS 1.3 原生支持这两类签名算法。
- SSH:整个生态已经转向椭圆曲线。新密钥通常默认使用 Ed25519(一种相关的 EC 签名方案,但不是 ECDSA),ECDSA P-256 也很常见。RSA 密钥仍然可用,但旧的
ssh-rsa签名方案(基于 SHA-1)在较新的 OpenSSH 版本中已默认禁用——RSA 密钥必须使用 SHA-2 变体。 - JWT:RS256(RSA)是身份生态中历来的默认选择;ES256(ECDSA P-256)是更紧凑的现代选择,并被多个平台强制要求(Apple 的令牌 API、DPoP、大多数 WebAuthn / 通行密钥(passkey)凭证)。其中的取舍相当具体,因此我们专门写了一篇指南:JWT 选 ES256 还是 RS256。你可以用 JWT 调试器查看真实的令牌。
- 区块链:几乎是 ECDSA 独占的领域。比特币和以太坊都使用 secp256k1 曲线上的 ECDSA 签名(比特币还随 Taproot 引入了 Schnorr 签名)。在交易的每个字节都要付费的地方,RSA 的签名大小让它根本无从谈起。
- 代码签名和固件:历来以 RSA 为主,如今越来越多样;资源受限的设备偏爱 ECDSA,因为它密钥小、签名快且占用内存少。
ECDSA 的隐患:nonce 必须完美无缺
ECDSA 有一项 RSA 根本没有的运维要求:每次签名都要消耗一个全新的保密数值 k(即 nonce),而该方案的安全性取决于这个数对每一个签名都唯一且均匀随机。一旦出错,后果毫无回旋余地:
- 只要同一个密钥对两条不同的消息签名时用过同一个
k,就能用初等代数从这两个签名算出私钥。2010年著名的 PlayStation 3 事件正是如此:一个固定不变的 nonce 暴露了这款游戏机的签名密钥。 - 即使 nonce 只是部分可预测或略有偏差,只要观察到足够多的签名,也可能足以借助格方法重建密钥。因此,签名时系统随机数生成器质量差,构成的是密钥泄露风险,而不只是质量问题。
标准的解决办法是 RFC 6979 规定的确定性 ECDSA:通过 HMAC 从私钥和消息摘要派生出 k,这样相同的输入总会得到相同且分布良好的 nonce,签名时也不再需要随机性。FIPS 186-5 现已允许使用确定性 ECDSA,大多数现代库都实现了 RFC 6979 或某种对冲(hedged)变体(在确定性派生中混入额外的熵)。选择库时,这是最先要检查的几件事之一。相比之下,RSA 使用 PKCS#1 v1.5 时每次签名都不需要随机性,而 RSA-PSS 中的随机盐即使重复,也不会导致密钥泄露。
两者都面临后量子倒计时
无论选哪种,都要为它的退役做好规划。运行 Shor 算法的足够大的量子计算机能同时攻破因数分解和椭圆曲线离散对数——RSA 和 ECDSA 会一起失守。NIST 在 NIST IR 8547(目前仍是初始公开草案)中勾勒了过渡时间表:经典签名算法——ECDSA、EdDSA 和 RSA——在联邦用途中2035年后禁止使用,所有参数集都不例外。更早的一个节点只适用于 112 位安全级别,对签名而言就是 RSA-2048 和 P-224:它们2030年后弃用,即仍可使用,但风险已被正式认定。P-256、P-384 和 RSA-3072 不受2030年这个日期的影响——这一区别值得弄清楚,因为它决定了你的哪些密钥需要优先处理。
指定的继任者是 ML-DSA(基于格,由 FIPS 204 标准化)和 SLH-DSA(基于哈希,FIPS 205),由 Falcon 衍生的 FN-DSA 将随后推出。要做好面对体积冲击的准备:ML-DSA-65 签名约为 3.3 KB——是 P-256 签名的五十倍——这也是混合证书和复合证书的协议工作仍在进行的原因。就当下而言,实际的结论不是“立刻在所有地方采用后量子密码”,而是构建密码敏捷性:清楚系统中每一处签名的位置,让算法选择保持可配置,避免把密钥或签名大小写死在代码里。
那么,现在该用哪一个?
想用真实的字节看看这些差异?在 ECDSA 签名验证工具中生成或粘贴一个签名——它会自动识别曲线和编码,并准确显示它验证了什么。