RSA 与模运算知识点扩展¶
本文档是 Dino Vault Writeup 的补充知识点,重点解释 RSA 中常见的模运算规则、模逆元、同余方程求解,以及为什么
d = e^{-1} mod φ(n)不是普通倒数。
1. 模运算与同余的基本概念¶
1.1 什么是 a mod m¶
a mod m 表示 a 除以 m 后的余数。通常取:
0 <= a mod m < m
例如:
17 mod 5 = 2
23 mod 7 = 2
-1 mod 7 = 6
在 Python / Go 的大整数场景里,最终通常希望把余数规范到 [0, m-1]。
1.2 什么是同余¶
如果 a 和 b 除以 m 的余数相同,就说:
等价定义是:
也就是 a-b 能被 m 整除。
例子:
17 ≡ 2 (mod 5)
23 ≡ 2 (mod 7)
100 ≡ 28 (mod 12)
因为:
17 - 2 = 15 = 5 * 3
23 - 2 = 21 = 7 * 3
100 - 28 = 72 = 12 * 6
2. 模运算的常用运算法则¶
如果:
那么下面这些运算都成立。
2.1 加法¶
等价地:
例子:
17 ≡ 2 (mod 5)
13 ≡ 3 (mod 5)
17 + 13 = 30 ≡ 0 (mod 5)
2 + 3 = 5 ≡ 0 (mod 5)
2.2 减法¶
例子:
17 ≡ 2 (mod 5)
13 ≡ 3 (mod 5)
17 - 13 = 4 ≡ 4 (mod 5)
2 - 3 = -1 ≡ 4 (mod 5)
2.3 乘法¶
例子:
17 ≡ 2 (mod 5)
13 ≡ 3 (mod 5)
17 * 13 = 221 ≡ 1 (mod 5)
2 * 3 = 6 ≡ 1 (mod 5)
2.4 幂运算¶
如果:
那么:
这就是 RSA 里可以用快速幂:
pow(m, e, n)
来计算:
的原因。
2.5 负数取模¶
负数也可以取模:
更准确地说,如果 a mod m = r,那么:
(-a) mod m = (m-r) mod m
例子:
-3 mod 7 = 4
因为:
-3 + 7 = 4
2.6 除法不能直接做¶
在普通实数里:
表示乘以 1/b。但在模运算里,不能随便“除以 b”。
模运算中的“除以 b”必须改写为:
其中 b^{-1} 是 b 在模 m 意义下的乘法逆元,满足:
而这个逆元只有在:
时才一定存在。
例子:
3^{-1} mod 10 = 7
因为:
3 * 7 = 21 ≡ 1 (mod 10)
但:
2^{-1} mod 10
不存在,因为 gcd(2,10)=2,没有任何整数 x 能让:
2x ≡ 1 (mod 10)
3. 模逆元怎么求¶
3.1 模逆元定义¶
a 关于模 m 的逆元记为:
它是满足下面式子的整数:
也就是:
这里的 x 就是 a 的模逆元。
3.2 存在条件¶
模逆元存在当且仅当:
这也是 RSA 里要求:
的原因。只有这样才能求:
3.3 扩展欧几里得算法¶
扩展欧几里得算法可以求:
当 gcd(a,m)=1 时:
对两边取模 m:
所以:
3.4 小例子:求 3^{-1} mod 10¶
扩展欧几里得:
10 = 3 * 3 + 1
1 = 10 - 3 * 3
所以:
1 = (-3) * 3 + 1 * 10
也就是说:
3 * (-3) ≡ 1 (mod 10)
把 -3 规范成正余数:
-3 mod 10 = 7
所以:
3^{-1} mod 10 = 7
4. 同余方程怎么解¶
同余方程最基础、最常见的是一次同余方程:
这类方程在 RSA、CRT、线性递推、伪随机数恢复题里非常常见。
4.1 一次同余方程的解法¶
求解:
步骤:
- 计算:
$$ g = \gcd(a,m) $$
-
判断
g是否整除b -
如果:
$$ g \nmid b $$
则无解
-
如果:
$$ g \mid b $$
则有
g个模m意义下的解 -
两边同时除以
g:
$$ a' = \frac{a}{g},\quad b'=\frac{b}{g},\quad m'=\frac{m}{g} $$
原方程化成:
$$ a'x \equiv b' \pmod {m'} $$
- 此时:
$$ \gcd(a',m')=1 $$
可以求逆元:
$$ (a')^{-1} \pmod {m'} $$
- 得到一个基础解:
$$ x_0 \equiv b'\cdot (a')^{-1} \pmod {m'} $$
- 原模数
m下的全部解为:
$$ x \equiv x_0 + k\cdot m' \pmod m,\quad k=0,1,\dots,g-1 $$
4.2 例子:解 14x ≡ 30 (mod 100)¶
第一步:
g = gcd(14, 100) = 2
因为 2 | 30,所以有解,而且有 2 个模 100 意义下的解。
两边除以 2:
7x ≡ 15 (mod 50)
求 7^{-1} mod 50:
7 * 43 = 301 ≡ 1 (mod 50)
所以:
x ≡ 15 * 43 ≡ 645 ≡ 45 (mod 50)
因此模 100 下全部解为:
x ≡ 45 (mod 100)
x ≡ 45 + 50 = 95 (mod 100)
验证:
14 * 45 = 630 ≡ 30 (mod 100)
14 * 95 = 1330 ≡ 30 (mod 100)
4.3 特殊情况:ax ≡ 1 (mod m)¶
这就是求逆元:
只有当:
时有解。
RSA 中:
就是这个特殊情况。
5. 多个同余方程:CRT 中国剩余定理¶
如果题目给出多个余数条件:
这就是同余方程组。
5.1 模数两两互素时¶
如果 m_i 两两互素,设:
对每个方程定义:
再求:
那么解为:
5.2 例子¶
求:
总模数:
M = 3 * 5 * 7 = 105
分别计算:
M1 = 105 / 3 = 35, 35^{-1} mod 3 = 2
M2 = 105 / 5 = 21, 21^{-1} mod 5 = 1
M3 = 105 / 7 = 15, 15^{-1} mod 7 = 1
所以:
x ≡ 2*35*2 + 3*21*1 + 2*15*1
≡ 140 + 63 + 30
≡ 233
≡ 23 (mod 105)
验证:
23 mod 3 = 2
23 mod 5 = 3
23 mod 7 = 2
5.3 模数不互素时¶
两个方程:
有解的条件是:
如果不满足,则无解。
例子:
x ≡ 1 (mod 4)
x ≡ 3 (mod 6)
因为:
gcd(4, 6) = 2
1 ≡ 3 (mod 2)
所以有解。
但:
x ≡ 1 (mod 4)
x ≡ 2 (mod 6)
因为:
1 ≠ 2 (mod 2)
所以无解。
6. RSA 为什么要用模逆元¶
6.1 RSA 参数¶
RSA 的核心参数是:
p, q: 两个大素数
n = p * q
φ(n) = (p-1)(q-1)
e: 公钥指数,常用 65537
d: 私钥指数
d 的定义是:
也就是:
等价写法:
其中 k 是某个整数。
6.2 加密与解密¶
加密:
解密:
代入:
因为:
所以:
当 gcd(m,n)=1 时,根据欧拉定理:
所以:
这就是 RSA 能正确解密的原因。
如果 m 和 n 不互素怎么办?
标准 RSA 的正确性也可以通过分别在模 p、模 q 下讨论,再用 CRT 合并来证明。CTF 入门分析时,先理解 gcd(m,n)=1 的欧拉定理版本通常已经足够。
6.3 为什么 1/65537 没有意义¶
在 RSA 里:
不是实数倒数:
而是要找一个整数 d:
也就是:
由于 φ(n) 通常非常大,这个 d 往往也是一个很大的整数。
7. Dino Vault 中的 RSA 结构¶
题目代码:
transmission_key = getPrime(primesize)
dinosaur_modulation_index = transmission_key * self.vault_key
evergreen_number = 2**16 + 1
resampled_dna = pow(bytes_to_long(self.dna.encode()), evergreen_number, dinosaur_modulation_index)
对应到 RSA:
| 代码变量 | RSA 含义 |
|---|---|
transmission_key |
素数 q |
self.vault_key |
素数 p |
dinosaur_modulation_index |
模数 n = p*q |
evergreen_number |
公钥指数 e = 65537 |
bytes_to_long(self.dna.encode()) |
明文整数 m |
resampled_dna |
密文整数 c = m^e mod n |
漏洞来自同一只恐龙复用 self.vault_key:
因此:
拿到 p 后就能分解 n_1,再求:
最终解密:
8. 常见误区总结¶
误区 1:e^{-1} 等于 1/e¶
错误。RSA 里的 e^{-1} 是模逆元,不是小数倒数。
正确理解:
d 是整数,并且 e*d ≡ 1 (mod φ(n))
误区 2:模运算里可以随便除法¶
错误。模运算中除法必须转成乘以逆元,而且逆元不一定存在。
a / b mod m 应理解为 a * b^{-1} mod m
前提是:
gcd(b, m) = 1
误区 3:看到 pow(x, y, z) 就一定是 RSA¶
不一定。pow(x, y, z) 只是模幂运算。判断 RSA 还要看:
- 模数是否是两个大素数乘积
- 指数是否是 RSA 公钥指数,如 65537
- 明文是否被转成整数再模幂
Dino Vault 之所以能判断为 RSA,是因为这些条件同时出现。
误区 4:只要 e=65537 就安全¶
错误。e=65537 是常见安全参数之一,但安全性还依赖:
- 模数不能共享素因子
- 素数必须随机且独立
- 不能使用裸 RSA 加密结构化明文
- 实际协议应使用 OAEP 等安全填充
9. 快速代码片段¶
Python:求模逆元¶
d = pow(e, -1, phi)
或者:
from Crypto.Util.number import inverse
d = inverse(e, phi)
Go:求模逆元¶
d := new(big.Int).ModInverse(e, phi)
if d == nil {
panic("inverse does not exist")
}
Python:解一次同余方程¶
from math import gcd
def solve_linear_congruence(a, b, m):
g = gcd(a, m)
if b % g != 0:
return []
a1, b1, m1 = a // g, b // g, m // g
inv = pow(a1, -1, m1)
x0 = (b1 * inv) % m1
return [(x0 + k * m1) % m for k in range(g)]
print(solve_linear_congruence(14, 30, 100))
# [45, 95]