866 字
4 分钟
CRYPTO--ecdsa随机数重用
这类题我一直觉得特别适合拿来练签名直觉,因为表面上看是“验签题”,实际上根因几乎总在签名阶段。
题型判断
如果一道题同时给出:
- 同一条曲线上的公钥
- 两条不同消息
- 两条不同签名
- 但两个签名的
r完全一样
那第一反应基本就应该是:
ECDSA 重复 nonce因为在 ECDSA 里,r 本质上是由临时随机数 k 推出来的。
同一个私钥下,如果两次签名用了同一个 k,最明显的特征就是 r 一样。
为什么重复 nonce 会直接出事
ECDSA 的核心式子是:
s = k^{-1}(z + r*d) mod n这里:
d是私钥k是临时随机数z是消息哈希n是曲线阶
如果两条不同消息共用了同一个 k,那就会出现:
s1 = k^{-1}(z1 + r*d) mod ns2 = k^{-1}(z2 + r*d) mod n两式相减之后,d 会被消掉,直接变成一个关于 k 的一次式。
最核心的恢复公式
先恢复随机数:
k = (z1 - z2) * (s1 - s2)^{-1} mod n再恢复私钥:
d = (s1*k - z1) * r^{-1} mod n这就是这类题最常见的两步走。
我做这类题的顺序
先看 r 是否重复-> 如果重复,直接怀疑 nonce 复用-> 用两组 (z, s) 算出 k-> 再把私钥 d 代出来-> 最后给目标消息重新签名这种题一般不需要去碰离散对数,也不需要碰更复杂的椭圆曲线攻击,关键只是签名者把临时随机数用坏了。
目标消息为什么也能签
一旦私钥恢复出来,后面的事情就和“正常签名”没有区别了。
对目标消息:
- 先做同样的哈希
- 得到
z_target - 再用恢复出来的
d和k计算新的s_target
最后提交:
(r, s_target)就能得到目标消息对应的合法签名。
常见坑点
1. 模逆的模数别用错
这里做逆元运算的时候,用的是曲线阶 n,不是椭圆曲线素域里的那个 p。
这点在手算或脚本里都很容易写错。
2. 不要重复 hash 已经给出的 z
很多题会直接把 z1、z2 给出来。
这时它们已经是消息摘要映射后的结果了,不能再对 z 本身做一次哈希。
3. 真正需要自己 hash 的通常只有 target
前两条签名往往题目已经给完参数,真正需要自己补的是目标消息的 z_target。
复现骨架
下面这类骨架就足够把题做出来:
from hashlib import sha256
n = <curve_order>
def inv(x): return pow(x, -1, n)
k = ((z1 - z2) * inv(s1 - s2)) % nd = ((s1 * k - z1) * inv(r)) % n
z_target = int.from_bytes(sha256(target_message).digest(), "big")s_target = (inv(k) * (z_target + r * d)) % n如果是交互题,最后把原来的 r 和新算出来的 s_target 交上去就行。
总结
这类题的重点根本不在“怎么伪造管理员身份”,而在于签名者自己把 nonce 管理坏了。
一句话总结就是:
同一个 k -> 同一个 r -> k 可恢复 -> 私钥可恢复 -> 任意消息可伪造签名所以以后看到 ECDSA 题里 r 重复,真的要立刻警觉起来,这往往已经不是“有点危险”,而是“基本可以直接拿下”了。
喜欢这篇文章吗?
点击右侧按钮为文章点赞,让更多人看到!
CRYPTO--ecdsa随机数重用
https://sliver-yu.cc/posts/学习/crypto--ecdsa随机数重用/
文章最后更新于 2026-04-25
在下余林阳