作者:enze
编辑:Liz
引言
近期,JavaScript 生态中广泛使用的 elliptic 加密库被发现存在严重安全漏洞(GHSA-vjh7-7g9h-fjfh)。攻击者可以通过构造特定输入,在仅签名一次的情况下提取私钥,从而完全掌控受害者的数字资产或身份凭证。
该漏洞的根本原因在于 elliptic 库对非标准输入的处理缺陷,导致 ECDSA 签名中的随机数 k 可能重复。由于 ECDSA 算法的安全性极度依赖 k 的唯一性,一旦 k 重复,私钥便可被直接推导出来,造成不可逆的安全风险。
本文将分析漏洞原理、成因及影响,并提供修复建议,帮助开发者降低安全风险。
漏洞背景
elliptic 库简介
elliptic 是 JavaScript 生态中广泛使用的椭圆曲线密码学库,支持多种曲线(如 secp256k1、ed25519)。该库被大量加密货币钱包、身份认证系统、Web3 应用等使用。
影响范围
受影响版本:elliptic <= 6.6.0
受影响曲线:secp256k1、ed25519 等
影响场景:所有接受外部输入进行 ECDSA 签名的应用(特别是接受外部输入进行签名的系统)
如果应用依赖 elliptic 进行 ECDSA 签名,并允许用户提供未经过滤的消息作为签名内容,则极有可能受到该漏洞影响。
漏洞原理
ECDSA 签名中的随机数 k
想象你有一枚独一无二的印章(私钥),每次签署文件时,都需要蘸取一种独特的印泥(随机数 k)。盖出的印迹会生成两个数字 r 和 s,这就是签名结果。
这个过程有几个关键点:
印章(私钥)始终不变 —— 代表你的身份。
每次签名都必须用全新的印泥(随机数 k 绝不能重复)。
即使签署相同的文件,签名结果(r, s)也必须不同。
任何人都可以验证印迹是否由你的印章盖出(公钥验证签名)。
但没人能从印迹反推出你的印章(私钥),只要 k 是安全的。
为了保证 k 的安全性,ECDSA 采用 RFC 6979 规范,使用确定性随机数生成器(HMAC_DRBG 或 HKDF)来计算 k。生成 k 时,使用私钥 d 和消息 m 作为种子,确保 k 在相同输入下固定。计算流程如下:

关键点:
1. k 必须是随机且唯一的,否则会导致私钥泄露。
2. 如果 k 在两次签名时相同,攻击者可以简单地通过两个签名 (r, s1) 和 (r, s2) 直接恢复私钥 d。
数学推导:
假设用户对两条不同的消息 m1 和 m2 进行了签名,但随机数 k 意外相同,那么签名结果如下:

攻击者可以计算 s1 和 s2 的差值:

由于 m1、m2、s1 和 s2 都是已知的,攻击者可以解出 k:

然后,将 k 代入原始签名方程,解出私钥 d:

漏洞分析
elliptic 库本应使用 HMAC_DRBG(确定性随机数生成器)确保每次签名的 k 唯一。然而,由于输入处理的错误,某些情况下不同的消息可能生成相同的 k,导致私钥泄露。
k 生成代码
在 elliptic 库的 sign() 方法中,k 由 HMAC_DRBG 伪随机数生成器生成,相关代码如下:
elliptic/lib/elliptic/ec/index.js

HMAC_DRBG 的输入参数主要包括:
entropy(熵) → 这里使用的是私钥 bkey,确保 HMAC_DRBG 具有足够的随机性。
nonce(随机数种子) → 由 msg 计算而来,影响 k 的最终生成值。
由于 HMAC_DRBG 是确定性的,相同的 entropy 和 nonce 会生成相同的 k。如果 nonce 在不同的签名过程中相同,则 k 也会相同,这将导致私钥泄露。
漏洞成因:BN 转换导致 nonce 复用
在 elliptic 的实现中,msg 被转换为 BN (Big Number),然后 nonce 由 msg.toArray() 计算:

问题点:
msg 是 BN 实例,但 nonce 是数组。
不同的 BN 实例可能在转换后生成相同的数组。
这意味着可以为不同的消息生成相同的 nonce,最终导致 k 复用。
如果 k 在两个不同的消息 msg1 和 msg2 上复用,则可以通过上文提到的数学公式恢复私钥 d。
攻击者可以构造特殊的 msg,使其 nonce 相同,从而导致 k 复用。只需要获取一对 (r, s1) 和 (r, s2),即可通过数学计算恢复私钥 d。更严重的是,攻击者可以针对任何已知的消息/签名对构造这样的恶意 msg,这意味着只要诱导受害者签署一次恶意消息,攻击者就能完全恢复私钥,并伪造任意签名。
漏洞利用(PoC)
elliptic 库允许使用十六进制字符串作为输入类型之一,在签名时会将 msg 转换为 BN 实例,再转换为数组类型。如果两个不同的消息在转换后生成相同的数组,nonce 也会相同,最终导致 k 复用。
代码示例:

运行输出:

msg1 = "message" 和 msg2 = "-message" 经过 BN 转换后,生成了相同的 nonce(elliptic 的设计接受十六进制字符串作为可能的输入类型之一)。由于 nonce 相同,导致 HMAC_DRBG 生成的 k 也相同。从签名结果中可以看到,r 值完全相同,这正是 k 复用的直接表现。
完整攻击流程:
1. 模拟受害者签名一条正常的消息,获取消息 msg0 和签名 sig0。
2. 攻击者构造恶意消息 msg1,并诱导受害者进行签名,获得 sig1。
3. 利用 k 复用漏洞,从 (r, s1) 和 (r, s2) 计算 k。
4. 利用 k 进一步计算出私钥 d,攻击成功。
代码示例:

运行输出:

修复建议
1. 升级 elliptic 至 6.6.1+,官方已修复该问题。
2. 避免直接签名未经验证的消息,确保 msg 经过标准化处理。
3. 更换可能受影响的私钥。
总结
elliptic 的这个漏洞暴露了 ECDSA 签名对随机数 k 的极端敏感性。一旦 k 复用,私钥便可被直接恢复,攻击者可完全控制用户资产和身份。开发者应及时修复漏洞,并严格规范输入处理,以确保 ECDSA 签名的安全性。最后,感谢 Rabby 钱包提供的漏洞情报。
参考资料
[1] https://github.com/indutny/elliptic/security/advisories/GHSA-vjh7-7g9h-fjfh
[2] https://paulmillr.com/posts/deterministic-signatures
