RSA 基础
欧拉函数 φ(n)
φ(n) 表示 “小于等于 n 且与 n 互质的正整数的个数”
如果 n 是质数 p,那么 φ(p) = p - 1(因为 1,2,…,p-1 都和 p 互质)
如果 n = p * q,且 p、q 都是质数,那么
φ(n) = (p - 1) * (q - 1)例如 p = 5, q = 11,则 n = 55,φ(55) = 4 * 10 = 40
密钥生成
选两个大质数 p 和 q
实际中 p、q 有 1024 位或 2048 位,这里我们用小数字演示
1 | p = 61 |
计算 n 和 φ(n)
1 | n = p * q = 61 * 53 = 3233 |
选公钥指数 e
要求:
1 < e < φ(n)
e 与 φ(n) 互质(即最大公约数为 1)
小例子中我们选 e = 17
计算私钥指数 d
d 是 e 在模 φ(n) 下的乘法逆元,即满足:
1 | (e * d) mod φ(n) = 1 |
换句话说,存在整数 k 使得:
1 | e * d = k * φ(n) + 1 |
计算 d 可以用扩展欧几里得算法。小例子中,可以验证 d = 2753
加密过程
加密公式:
1 | c = (m ^ e) mod n |
例子:加密明文 m = 65
1 | c = 65 ^ 17 mod 3233 |
直接算 65^17 巨大,但模运算可以逐步取余。结果:
1 | c = 2790 |
解密过程
1 | m = (c ^ d) mod n |
例子:c = 2790, d = 2753, n = 3233
1 | m = 2790 ^ 2753 mod 3233 = 65 |
推导过程
即要证明:
1 | c^d ≡ m (mod n) |
将 c = m^e mod n 代入 c^d:
1 | c^d ≡ (m^e)^d = m^(e*d) (mod n) |
利用 ed = kφ(n) + 1:
1 | m^(e*d) = m^(k*φ(n) + 1) = [m^(φ(n))]^k * m |
所以:
1 | c^d ≡ [m^(φ(n))]^k * m (mod n) |
欧拉定理说:如果 m 和 n 互质(即 gcd(m, n) = 1),那么
1 | m^(φ(n)) ≡ 1 (mod n) |
因此
1 | [m^(φ(n))]^k ≡ 1^k = 1 (mod n) |
于是
1 | c^d ≡ 1 * m = m (mod n) |
实战案例
equationSet(提公因数 p)

解题技巧先放前面:
- 符号化:所有给出的数值用数学符号表示出来,并建立它们之间的关系
- 寻找公因数:如果给出的两个大数 A 和 B 都是由相同的素数因子构成的,那么
GCD(A, B)往往是解密的关键 - 确定解题路径:一旦
p被分解出来,接下来就要想方设法得到q和r的信息
题目给出代码
1 | from Crypto.Util.number import * |
转换成数学公式
1 | n = p * q * r |
我们可以清楚地看到,p 是 n 和 t 的公共因子
那 GCD(n, t) 会正好是 p 吗?会不会包含其他因子?
由于 q 和 r 都是生成的 512 位随机大素数,它们互质,且它们的和 q+r 极大概率与它们的积 qr 互质
因此 GCD(qr, q+r) = 1
结论:直接计算 GCD(n, t) 就能提取出素数 p
观察公因数 是 Crypto 选手的直觉。看到 n 和 t 有明显的公共结构(都含有 p),第一时间想到的就是 GCD
1 | from Crypto.Util.number import * |
easyRSA(线性组合泄露 d 与 φ(n))

1 | from Crypto.Util.number import * |
RSA 里,我们有:
p,q两个大素数n = p * qφ(n) = (p-1)*(q-1)公钥指数
e = 65537(题目固定)私钥指数
d满足e * d ≡ 1 (mod φ(n))
这等价于存在一个整数 k,使得:
1 | e * d = k * φ(n) + 1 |
即
1 | d = (k * φ(n) + 1) / e |
题目除了给 n 和密文 c,还给了
1 | x = 11 * d + 7 * φ(n) |
也就是说,x 是 d 和 φ(n) 的一个线性组合,系数分别是 11 和 7
将上面 d 的表达式代入 x
1 | x = 11 * [ (kφ + 1)/e ] + 7φ |
两边同时乘以 e,去掉分母
1 | e * x = 11 * (kφ + 1) + 7eφ |
展开
1 | e * x = 11kφ + 11 + 7eφ |
把常数项移到左边,含 φ 的项合并
1 | e * x - 11 = (11k + 7e) φ |
记 T = ex - 11,则
1 | T = (11k + 7e) * φ |
这是一个关键等式,T 是 φ 的整数倍,倍数 A = 11k + 7e 依赖于未知的 k
从 e * d = k φ + 1 得
1 | k = (e * d - 1) / φ |
因为 d < φ(实际上 d 是模 φ 下的逆元,通常比 φ 略小),所以
1 | e * d - 1 < e * φ - 1 → k < e |
所以思路:
计算
T = e * x - 11(已知x和e)对每个可能的
k从 0 到e-1,计算A = 11k + 7e如果
A能整除T,那么φ = T // A就是一个候选的欧拉函数值用这个
φ去分解n,因为p + q = n - φ + 1,p * q = n,所以p和q是二次方程X^2 - (p + q)X + n = 0的两个根判断
p和q是否为整数且满足p * q == n,若是,则分解成功
已知
1 | s = p + q |
根据韦达定理,以 p 和 q 构造一个二次方程
1 | (t-q)(t-p) = 0 |
把已知条件带入得到
1 | t² - s * t + n = 0 |
对于任意二次方程,判别式公式为
1 | at² + bt + c = 0 |
代入得到
1 | a = 1,b = -s,c = n |
如果判别式是完全平方数,方程就有整数解
1 | p = (s + √Δ)/2 |
1 | from Crypto.Util.number import * |
little RSA(分解 n)
题目给了
1 | c=32949 |
分解 n 得到 p,q

1 | from Crypto.Util.number import long_to_bytes |
解出为 18429 解压密码
Rivest Shamir Adleman(分解 n)
和上题一样的方法

1 | from math import isqrt |
shenyue2(RSA 利用 ed 同余关系分解 n)

源码里先把 FLAG 转成整数,然后设置 RSA 参数:e=65537,随机生成两个 2048 bit 素数 p,q,并给了一个固定的 100 bit 左右的 r
关键泄露点在这里:
1 | phi = (p-1)*(q-1) |
标准 RSA 中:
1 | n = p * q |
所以一定存在一个整数 t,使得
1 | e * d = 1 + t * phi |
所以
1 | d = (1 + t * phi) / e |
因为 0 < d < phi,所以
1 | 1 <= t < e |
这就给了我们爆破空间,把泄露的 k 代入
1 | k = (p - r) * d |
又因为 e * d = 1 + t * phi,所以
1 | e * k = (p - r) * (1 + t * phi) |
在 RSA 中
1 | phi = (p - 1)(q - 1) |
代入得到
1 | e * k = (p - r) * (1 + t * (n - p - q + 1)) |
这个式子里,n,e,k,r 已知,t,p,q 未知
对于真实的 t
1 | phi = n - p - q + 1 |
而 p + q 相比 n 非常小,所以
1 | phi = n - (p + q - 1) |
于是
1 | e * k = (p - r) * (1 + t * phi) |
所以
1 | p ≈ e * k / (t * n) + r |
于是解题策略变成:
枚举
t = 1 ~ 65536计算
p0 = floor(e*k / (t*n)) + r在
p0附近小范围搜索如果某个候选
p_candidate可以整除n,说明找到了真正的p
1 | import re |
运行结果
1 | [+] found t = 38634 |
RSA(RSA PKCS#1 padding)

核心代码如下,如果 RSA 加密失败则重新生成 p,q,e
1 | flag = open('flag', 'r').read().strip() * 23 |
首先查看公钥
1 | openssl rsa -pubin -text -modulus -in pubkey.pem |
得到
1 | n = |
分解 n
1 | p = |
但是代码最后 flag * 23 长度非常大
RSA PKCS#1 v1.5 要求 明文长度 < n 长度 - padding,也就是说第一次必然失败
所以我们只需把题目中的 encrypt() 逻辑原样反过来写成 decrypt(),让 RSA 解密过程自己触发异常,然后继续更新 p、q、e,直到成功
1 | import gmpy2 |

RSA(RSA 利用 p 和 q 的近似比例关系分解 n)

题目大概是
1 | A = (((y % x) ** 5) % (x % y)) ** 2019 + y ** 316 + (y + 1) / x |
已知 A, n, c,从 A 推出 x 和 y
已知 A < 2 ^ 2019(题目给出的 A 具体数值可测出)
所以 (((y % x) ** 5) % (x % y)) ** 2019,如果底数 ≥ 2,就会爆到至少 2 ^ 2019,超过 A。因此这个底数只能是 0 或 1
于是 A 主要由 y ** 316 贡献
所以直接取 316 次整数根:y = iroot(A, 316)[0]
得到 y = 83,再代回原式爆小范围的 x,得到
1 | x = 2 |
1 | p = next_prime(z * x * y) |
然后在 sqrt(n // 166) 附近爆破,找到能整除 n 的 q
1 | from math import isqrt |
simple_math(RSA Wilson 定理 + 利用 ed 同余关系分解 n)

题目核心代码如下
1 | def gen_p(): |
最后加密
1 | _E = 65537 |
所以真正用来加密 flag 的 RSA 模数是 N = _P * _Q,我们要分别恢复 _P 和 _Q
题目给了 A 和 C,并且
1 | B = (A - 1) // 2 |
其中 C = A - randint(1e3, 1e5),所以 C 和 A 非常接近
令 k = A - C,附件里实际算出来 k = 67381,也就是说 C = A - 67381
如果直接算 C! mod A 肯定不行,因为 C 是一个 513 bit 级别的大数
Wilson 定理一句话:如果 A 是素数,那么 (A - 1)! ≡ -1 (mod A)
阶乘定义是 n! = 1 * 2 * 3 * …… * n,也就是说 1 * 2 * 3 * …… * (A - 1) ≡ A - 1 (mod A)
因为 -1 + A = A - 1,所以 A - 1 ≡ -1 (mod A)
由同余的传递性,得到 (A - 1)! ≡ -1 ≡ A - 1 (mod A)
题目里 C = A - K,因为 K 是正整数,且 K < A,所以 1 < C < A
也就是说 C 是 1 到 A - 1 之间的一个整数
因此 1 * 2 * 3 * …… * (A - 1) 可以拆成两段:第一段是从 1 到 C,第二段是从 C + 1 到 A - 1
1 | (A - 1)! = (1 * 2 * 3 * …… * C) * ((C + 1) * (C + 2) * …… * (A - 1)) |
而 1 * 2 * 3 * …… * C = C!,所以
1 | (A - 1)! = (1 * 2 * 3 * …… * C) * ((C + 1) * (C + 2) * …… * (A - 1)) |
又因为 C = A - K,所以 K = A - C,于是
1 | C + 1 = A - K + 1 = A - (K - 1) |
一直到最后 A - 1 = A - 1,所以第二段变成 (A - (K - 1) * A - (K - 2) * …… * (A - 1))
在模 A 下
1 | A - (K - 1) ≡ -(K - 1) |
所以第二段模 A 等于 (-(K - 1) * (-(K - 2)) * …… * (-1))
一共有 K - 1 个负号,所以 (-1) ^ (K - 1) * (K - 1) * (K - 2) * …… * 1
也就是 (-1) ^ (K - 1) * (K - 1)!,再代回去
1 | (A - 1)! = C! * ((C + 1) * (C + 2) * …… * (A - 1)) |
由于 A 是素数,Wilson 定理得到 (A - 1)! ≡ -1 (mod A)
所以 -1 ≡ C! * (-1) ^ (K - 1) * (K - 1)! (mod A)
两边同时乘以 (-1) ^ (K - 1)
1 | -1 * (-1) ^ (K - 1) = (-1) ^ k |
所以 (-1) ^ k ≡ C! * (K - 1)! (mod A),两边同时乘以 (K - 1)! 的逆元
因为 A 是素数,且 K - 1 < A,所以 1 * 2 * 3 * …… * (K - 1) = (K - 1)! 与 A 互素,即
1 | gcd((K - 1)!, A) = 1 |
所以 (K - 1)! 在模 A 下存在逆元,设这个逆元为 inv = ((K - 1)!) ^ (-1) (mod A)
1 | (-1) ^ k * inv ≡ C! * (K - 1)! * inv (mod A) |
因为 (K - 1)! * inv = 1 (mod A)
1 | (-1) ^ k * inv ≡ C! * ((K - 1)! * inv) (mod A) |
因此 N2 = C! (mod A) = (-1) ^ k * ((K - 1)!) ^ (-1) (mod A)
接下来继续恢复 N1 = ((A - 1) // 2)! mod A,令 m = (A - 1) / 2
根据 Wilson 定理:(A - 1)! ≡ -1 mod A,把 (A - 1)! 从 m 处切开
1 | (A - 1)! = (1 * 2 * 3 * …… * m) * ((m + 1) * (m + 2) * …… * (A - 1)) |
前半段为 1 * 2 * 3 * …… * m = m!,后半段在模 A 下
1 | m + 1 = -m (mod A) |
所以
1 | (m + 1) * (m + 2) * …… * (A - 1) ≡ (-m) * (-(m - 1)) * …… * (-1) (mod A) |
这里一共有 m 个负号,所以
1 | (-m) * (-(m - 1)) * …… * (-1) = (-1) ^ m * m * (m - 1) * …… * 1 |
而 m * (m - 1) * …… * 1 = m!,所以
1 | (m + 1) * (m + 2) * …… * (A - 1) ≡ (-m) * (-(m - 1)) * …… * (-1) (mod A) |
由 Wilson 定理:(A - 1)! ≡ -1 (mod A),所以
1 | (A - 1)! ≡ (-1) ^ m * (m!) ^ 2 (mod A) |
因为 A ≡ 3 (mod 4),设 A = 4t + 3,则
1 | m = (A - 1) / 2 |
因为 m 是奇数,因此
1 | (-1) ^ m = (-1) ^ (2t + 1) |
代入 -1 ≡ (-1) ^ m * (m!) ^ 2 (mod A) 中,得到
1 | -1 ≡ (-1) ^ m * (m!) ^ 2 (mod A) |
两边同时乘以 −1
1 | 1 ≡ (m!) ^ 2 (mod A) |
而 N1 ≡ m! (mod A),所以 N1 ^ 2 ≡ 1 (mod A)
1 | N1 ^ 2 ≡ 1 (mod A) |
因为 A 是素数,所以 A | (N1 - 1) 或 A | (N1 + 1),于是
1 | N1 ≡ 1 (mod A) 或 N1 ≡ -1 (mod A) |
即 N1 = 1 或 N1 = A - 1
题目里
1 | seed1 = 2019 * N1 + 2020 * N2 |
所以对 N1 的两个取值,分别计算一次 seed1,得到两个候选 _p
当 N1 = 1 时,得到 _P = 29980405391237365494597168525833309900267720449837722663797882464292688736399471276643180209621186905684266642360886383187160507504219009609357296973525651439
题目给了
1 | n = p * q |
其中 e * d ≡ 1 mod phi(n),所以 e * d - 1 = k * phi(n)
也就是说,题目泄露了 ed,相当于泄露了一个 phi(n) 的倍数
对于任意与 n 互素的随机数 a,都有 a ^ (ed - 1) ≡ 1 (mod n)
把 ed - 1 拆成 ed - 1 = 2 ^ s * r,其中 r 是奇数,然后取随机底数 a,计算
1 | x = pow(a, r, n) |
不断平方 x = x ^ 2 mod n,如果某一步出现 x ^ 2 ≡ 1 mod n,但是
1 | x != 1 |
那么 x 是模 n 下的非平凡平方根,可以用 gcd(x - 1, n) 分解出 n 的一个因子
1 | import re |