723 字
4 分钟
CRYPTO--rsa故障攻击
这篇记一类我觉得辨识度特别高的 RSA 题:题面里一旦同时出现“正常签名”和“故障签名”,而且源码又带 CRT 结构,那基本就该往故障攻击上想了。
题目类型
这是典型的 RSA-CRT 故障攻击 例题。
题目常见配置一般是:
- 公钥参数
- 一条消息
- 一份正确签名
- 一份故障签名
- 可能再额外给一段密文
这种题最重要的不是爆破,而是识别模型。
为什么会出问题
CRT-RSA 会把签名拆成两个模数分支来算,然后再合并。
如果其中一边因为故障注入、硬件错误或者人为扰动出了偏差,就会出现一种非常危险的情况:
- 在模一个素数的意义下,签名还是对的
- 在模另一个素数的意义下,签名已经错了
这会导致一个非常经典的结果:
gcd(错误签名^e - m, n)能直接拉出一个素因子。
我会怎么判断这题能不能走这条链
我现在会优先看三件事:
- 是否给了两份签名,而且其中一份明显是 fault
- 是否出现了
dp / dq / p / q这类 CRT 相关变量 - 故障是不是只打在某一个模分支上
如果这三个点都成立,那这题大概率就不是普通 RSA,而是 Bellcore 这一类故障攻击。
一个通用的恢复顺序
这类题我一般按这个顺序来:
先确认故障模型-> 用 gcd 拉一个素因子-> 还原 p 和 q-> 求 phi 和 d-> 再去解密剩余密文顺着这个流程走会很稳。
复现框架
下面放一份通用脚本框架:
from math import gcd
N = 0E = 65537MESSAGE = 0FAULT_SIGNATURE = 0CIPHER = 0
def recover_prime(n: int, e: int, message: int, fault_signature: int) -> int: return gcd(pow(fault_signature, e, n) - message, n)
def recover_plaintext(n: int, e: int, p: int, cipher: int) -> int: q = n // p phi = (p - 1) * (q - 1) d = pow(e, -1, phi) return pow(cipher, d, n)
def int_to_bytes(value: int) -> bytes: hex_text = hex(value)[2:] if len(hex_text) % 2: hex_text = "0" + hex_text return bytes.fromhex(hex_text)
def main() -> None: p = recover_prime(N, E, MESSAGE, FAULT_SIGNATURE) plaintext_int = recover_plaintext(N, E, p, CIPHER) plaintext = int_to_bytes(plaintext_int) print(plaintext) print(plaintext.decode())
if __name__ == "__main__": main()这类题最容易踩的地方
- 没认出“故障签名”才是真正入口
- 先去折腾正常签名,结果绕远路
- 分解出模数后忘了题目还额外给了密文
我现在更习惯先问自己一句:
题目既然把 fault 明着给出来,那是不是就在暗示我直接从 gcd 下手?
很多时候这句一问,方向就清了。
总结
这类题的精髓不是复杂计算,而是模型识别:
- 看到 CRT
- 看到 fault
- 就想到 gcd 分解模数
只要第一反应对了,后面通常都不长。
喜欢这篇文章吗?
点击右侧按钮为文章点赞,让更多人看到!
CRYPTO--rsa故障攻击
https://sliver-yu.cc/posts/学习/crypto--rsa故障攻击/
文章最后更新于 2026-04-25
在下余林阳