4、ECDSA 签名算法:椭圆曲线密码学基础、ECDSA 签名与验签、曲线选择(P-256 / secp256k1)

ECDSA,全称是椭圆曲线数字签名算法。说白了,它就是 ECC(椭圆曲线密码学)和 DSA(数字签名算法)的结合体。为什么我们要专门花一章来讲它?因为现在绝大多数的组件签名、区块链钱包、TLS 证书,底层用的都是它。

我记得刚接触这个算法时,第一反应是「这数学也太复杂了吧」。但后来我发现,作为工程师,你不需要把每条公式都推导一遍。你只需要理解它的核心逻辑、知道怎么选曲线、能看懂签名和验签的过程,就足够了。

椭圆曲线密码学基础

先聊聊椭圆曲线。它长什么样?数学上,它满足这样一个方程:

y² = x³ + ax + b

嗯,就这么简单。画出来是一条关于 x 轴对称的曲线。但真正用在密码学里的,不是实数域上的连续曲线,而是有限域上的离散点集。

我个人习惯把椭圆曲线密码学理解为「在一个封闭的沙盒里做加法」。你可以在曲线上取两个点,做点加法,得到第三个点。反复做,就能得到一个很大的循环群。这个群里的元素数量,就是曲线的阶。

为什么它能做密码?因为「已知起点 G 和终点 Q,求做了多少次点加」——这个问题,目前没有多项式时间的解法。这就是椭圆曲线离散对数问题(ECDLP)。

核心概念速览:

  • 基点 G:公开的曲线上的一个点,所有运算的起点。
  • 私钥 d:一个随机的大整数,必须保密。
  • 公钥 Q:Q = d * G,即私钥乘以基点。公开。
  • 阶 n:使得 n * G = 无穷远点的最小正整数。决定了密钥空间的大小。

我在项目中遇到过一个问题:有人把私钥直接存成字符串,结果被反编译拿到了。你想想看,私钥一旦泄露,整个签名体系就形同虚设。所以,私钥的存储一定要用硬件安全模块(HSM)或安全飞地(Secure Enclave)。

ECDSA 签名与验签

ECDSA 的签名过程,其实比 RSA 要复杂一点。它引入了随机数 k,每次签名都不一样。这意味着,同样的消息,每次签名出来的值都不同。这是好事,能防止重放攻击。

签名过程大致分四步:

  1. 计算消息哈希:对消息 m 做哈希,得到 e = hash(m)。
  2. 生成随机数 k:在 [1, n-1] 范围内选一个随机数 k。注意,这个 k 必须每次都不一样,且绝对不能泄露。
  3. 计算点 R = k * G:取 R 的 x 坐标作为 r。如果 r 为 0,重新选 k。
  4. 计算 s = k⁻¹ * (e + r * d) mod n:如果 s 为 0,重新选 k。

最终签名就是 (r, s) 对。长度取决于曲线参数,P-256 下是 64 字节。

⚠️ 我曾经踩过一个坑:随机数 k 的生成必须使用密码学安全的随机数生成器(CSPRNG)。如果 k 有规律,或者两次签名用了同一个 k,私钥就能被直接算出来。索尼 PS3 的签名密钥泄露,就是这个原因。别用 rand(),别用时间戳,别用 UUID。

验签过程相对简单,不需要私钥:

  1. 检查 r 和 s 是否在有效范围内。
  2. 计算 e = hash(m)。
  3. 计算 w = s⁻¹ mod n。
  4. 计算 u1 = e * w mod n,u2 = r * w mod n。
  5. 计算点 P = u1 * G + u2 * Q。
  6. 如果 P 的 x 坐标等于 r,验签通过。

这里有个细节:验签时用的是公钥 Q,不是私钥。所以任何人都可以验证签名的有效性。这也是数字签名的核心价值——公开可验证。

💡 我的建议:在实际开发中,不要自己实现 ECDSA。用现成的库,比如 OpenSSL、libsecp256k1、Bouncy Castle。这些库经过了大量安全审计,比你自己写的要靠谱得多。

曲线选择:P-256 vs secp256k1

曲线选择是个大问题。目前最主流的两条曲线是 P-256(也叫 prime256v1)和 secp256k1。它们都是 256 位曲线,安全强度相当,但设计哲学完全不同。

特性 P-256 (NIST P-256) secp256k1
参数来源 NIST 标准,参数由随机种子生成 Koblitz 曲线,参数最小化
性能 中等,有优化空间 较快,尤其适合硬件实现
安全性 广泛认可,经过多年分析 同样安全,但部分人担心 NIST 曲线有后门
主要应用 TLS 证书、政府标准、iOS/macOS 签名 比特币、以太坊、区块链生态
曲线方程参数 a -3 0
曲线方程参数 b 0x5ac635d8...(随机生成) 7

我个人习惯这样选:

  • 如果你的应用需要和浏览器、操作系统、TLS 协议交互,选 P-256。它兼容性最好,几乎所有平台都原生支持。
  • 如果你的应用在区块链或加密货币领域,选 secp256k1。它是比特币和以太坊的标配,生态工具链最成熟。
  • 如果你在做组件签名,比如给移动 App 或桌面软件签名,我建议优先考虑 P-256。因为 Apple 和 Microsoft 的签名工具链对 P-256 支持得最好。

避坑指南:不要为了「看起来更安全」而选择 384 位或 521 位曲线。256 位曲线的安全强度已经足够(相当于 3072 位 RSA)。更长的曲线只会带来性能开销,而且兼容性会变差。我在项目中见过有人用 P-521,结果在老旧设备上验签耗时超过 1 秒——完全没必要。

ECDSA 核心流程

下面这张图,把 ECDSA 的签名和验签流程串起来了。你可以看到,私钥只出现在签名端,公钥只出现在验签端。中间的消息哈希和曲线运算是桥梁。

ECDSA 签名与验签流程 签名方(持有私钥 d) 消息 m ↓ 哈希运算 e = hash(m) ↓ 随机数 k + 私钥 d 签名 (r, s) 验签方(持有公钥 Q) 消息 m + 签名 (r, s) ↓ 哈希运算 e = hash(m) ↓ 公钥 Q + 曲线运算 验签通过 / 失败 传输 曲线参数(公开) 基点 G | 阶 n | 曲线方程 y² = x³ + ax + b P-256: a = -3, b = 随机生成 secp256k1: a = 0, b = 7

代码示例:使用 OpenSSL 进行 ECDSA 签名

光说不练假把式。下面我用 OpenSSL 的命令行工具,演示一下 P-256 曲线的签名和验签。你可以在自己的机器上跑一遍。

# 1. 生成 P-256 私钥
openssl ecparam -genkey -name prime256v1 -out private.pem

# 2. 从私钥导出公钥
openssl ec -in private.pem -pubout -out public.pem

# 3. 对消息文件签名
echo "hello, ecdsa" > message.txt
openssl dgst -sha256 -sign private.pem -out signature.bin message.txt

# 4. 验签
openssl dgst -sha256 -verify public.pem -signature signature.bin message.txt
# 输出: Verified OK

如果你用的是 secp256k1,只需要把 prime256v1 换成 secp256k1 即可。OpenSSL 1.1.1 以上版本原生支持 secp256k1。

💡 小技巧:签名文件 signature.bin 是 DER 格式的。你可以用 openssl asn1parse -inform DER -in signature.bin 查看它的内部结构,能看到 r 和 s 两个大整数。

总结

ECDSA 是目前最主流的签名算法之一。它的核心优势是:密钥短、性能好、安全性高。256 位的 ECDSA 密钥,安全强度相当于 3072 位的 RSA 密钥。

曲线选择上,P-256 适合通用场景和合规场景,secp256k1 适合区块链场景。我个人更倾向于 P-256,因为它的生态支持更广泛,而且 NIST 标准在政府和企业级应用中几乎是必选项。

最后再强调一次:随机数 k 的安全性是 ECDSA 的生命线。别在这上面偷懒。用 CSPRNG,用硬件随机数,别自己造轮子。