RSA construct 函数漏洞

本文最后更新于:2023年11月4日 下午

本文整理了国赛Ciscn两道利用RSA.construct函数漏洞的题目

Ciscn-badkey1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
signal.alarm(10)
print("Give me a bad RSA keypair.")

try:
p = int(input('p = '))
q = int(input('q = '))
assert p > 0
assert q > 0
assert p != q
assert p.bit_length() == 512
assert q.bit_length() == 512
assert isPrime(p)
assert isPrime(q)
n = p * q
e = 65537
assert p % e != 1
assert q % e != 1
d = inverse(e, (p-1)*(q-1))
except:
print("Invalid params")
exit(2)

try:
key = RSA.construct([n,e,d,p,q])
print("This is not a bad RSA keypair.")
exit(3)
except KeyboardInterrupt:
print("Hacker detected.")
exit(4)
except ValueError:
print("How could this happen?")
from secret import flag
print(flag)

RSA.construct 中检测了ddnn是否互素,而服务器的代码里缺少这一步,因此解题思路就是找到一对满足ddnn有公因数的p,qp,q

不妨假设p=gcd(d,n)p = gcd(d,n),那么d=kpd=kp。再根据ed1(mod  (p1)(q1))ed\equiv 1(mod\;(p-1)(q-1)),可列出:

ekp1=k(p1)(q1)e\cdot kp-1=k^′(p-1)(q-1)

改写一下就是 ekpk(p1)(q1)=1e\cdot kp-k^′(p-1)(q-1)=1,注意到kk^′的大小和ee接近,因此可以先爆破kk^′,把kk^′当作已知量,对给定一个pp,利用拓展欧几里得算法求出k,(q1)k,(q-1)和公因数gg。如果公因数g=1g=1而且qq是质数,那就找到了一组解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from Crypto.Util.number import *

while True:
p = getPrime(512)
e = 0x10001
for k in range(2, e):
g, x, y = xgcd(e*p, -k*(p-1))
if g == 1:
q = y + 1
if len(bin(q)) == 514 and isPrime(q):
print('p =', p)
print('q =', q)


Ciscn-badkey2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
e = 65537
p = getPrime(512)
while p % e == 1:
p = getPrime(512)

signal.alarm(10)
print("Give me 10 bad RSA keypairs that have the same prime factor:", p)

used_n = []
for i in range(10):
try:
n = int(input('n = '))
assert n % p == 0
assert n not in used_n
used_n.append(n)
q = n // p
assert q > 0
assert q != p
assert q.bit_length() == 512
assert isPrime(q)
assert q % e != 1
d = inverse(e, (p-1)*(q-1))
except:
print("Invalid n")
exit(2)

try:
key = RSA.construct([n,e,d])
print("This is not a bad RSA keypair,")
exit(3)
except KeyboardInterrupt:
print("Hacker detected.")
exit(4)
except ValueError:
print("Good job!")

print("How could this happen?")
from secret import flag
print(flag)

这题里面的RSA.construct函数只传了n,e,dn,e,d三个参数,RSA.construct函数需要从这三个参数里恢复出p,qp,q,如果不能恢复就会报错ValueError。

下面是construct函数内部代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
ktot = d * e - 1
# The quantity d*e-1 is a multiple of phi(n), even,
# and can be represented as t*2^s.
t = ktot
while t % 2 == 0:
t //= 2

# Cycle through all multiplicative inverses in Zn.
# The algorithm is non-deterministic, but there is a 50% chance
# any candidate a leads to successful factoring.
# See "Digitalized Signatures and Public Key Functions as Intractable
# as Factorization", M. Rabin, 1979
spotted = False
a = Integer(2)
while not spotted and a < 100:
k = Integer(t)
# Cycle through all values a^{t*2^i}=a^k
while k < ktot:
cand = pow(a, k, n)
# Check if a^k is a non-trivial root of unity (mod n)
if cand != 1 and cand != (n - 1) and pow(cand, 2, n) == 1:
# We have found a number such that (cand-1)(cand+1)=0 (mod n).
# Either of the terms divides n.
p = Integer(n).gcd(cand + 1)
spotted = True
break
k *= 2
# This value was not any good... let's try another!
a += 2
if not spotted:
raise ValueError("Unable to compute factors p and q from exponent d.")

其实是传统的已知私钥分解模数nn​的方法,任意取一个底数aa​,如果存在

t=ed12k,t,kNt=\frac{ed-1}{2^k},\quad t,k\in \N

使得

at≢1  or1(mod  n)a2t1(mod  n)a^t\not\equiv 1\;or -1(mod\; n)且a^{2t}\equiv 1(mod\; n)

那么gcd(at1,n)gcd(a^t-1,n)gcd(at+1,n)gcd(a^t+1,n)就为nn​的因数。

但是问题在于,construct函数里用来检验的数aa只是2-100之间的偶数。因此可以构造一对p,qp,q使得函数无法对nn进行分解。

为了将问题进行简化,先假设pq3(mod  4)p\equiv q\equiv 3(mod\;4)​,容易得到:(p1)//2(p-1)//2​和(q1)//2(q-1)//2​都为奇数。那么求解思路就是找到的p,qp,q​使得

a(p1)(q1)41(mod  n)a^{\frac{(p-1)(q-1)}4}\equiv1(mod\;n)

也等价于:

ordn(a)(p1)(q1)4ord_n(a)\leq\frac{(p-1)(q-1)}4

根据中国剩余定理,如果ordp(a)=p12,ordq(a)=q12ord_p(a)=\frac{p-1}2,ord_q(a)=\frac{q-1}2,就有:

ordn(a)=(p1)(q1)4ord_n(a)=\frac{(p-1)(q-1)}4

主要利用中国剩余定理是一个环同构:

φ:Zm1mnZm1Zmnrˉ(rˉ,,rˉ)\begin{aligned}\varphi:\quad \mathbb{Z}_{m_1\cdots m_n}&\to\mathbb{Z}_{m_1}\oplus\cdots\oplus\mathbb{Z}_{m_n}\\ \bar{r}&\mapsto(\bar{r},\cdots,\bar{r})\end{aligned}

并且 φ(1ˉ)=(1ˉ,1ˉ)\varphi(\bar 1)=(\bar1, \bar1)

所以如果对于所有检验的aa,如果ap12(mod  p)a^{\frac{p-1}2}(mod\;p)aq12(mod  q)a^{\frac{q-1}2}(mod\;q)同为1,就有:

a(p1)(q1)41(mod  n)a^{\frac{(p-1)(q-1)}4}\equiv1(mod\;n)

而只利用这一个限制条件基本上是不能完成的,因为对任意的 pp,在2-100之间几乎一定会有pp的原根。但如果要求 aappqq 的原根,即:

ap121(mod  p),aq121(mod  q)a^{\frac{p-1}2}\equiv-1(mod\;p),a^{\frac{q-1}2}\equiv-1(mod\;q)

利用 φ(1)=(1,1)\varphi(\overline {-1})=(\overline {-1}, \overline {-1})从而:

a(p1)(q1)41(mod  n)a^{\frac{(p-1)(q-1)}4}\equiv-1(mod\;n)

这种情况下,a(p1)(q1)21(mod  n)a^{\frac{(p-1)(q-1)}2}\equiv1(mod\;n),依然无法对nn进行分解。

根据欧拉判别条件,如果ap121(mod  p)a^{\frac{p-1}2}\equiv1(mod\;p)aa就是模pp的二次剩余;否则ap121(mod  p)a^{\frac{p-1}2}\equiv-1(mod\;p)aa就是模pp的非二次剩余,两种情况下的勒让德符号(ap)\left(\frac ap\right)分别等于1、-1.

综合上面的结果,只要让(ap)=(aq)\left(\frac ap\right)=\left(\frac aq\right)​,因此考虑pq(mod  a)p\equiv q(mod\;a)​,又因为pq3(mod  4)p\equiv q\equiv 3(mod\;4)​,根据二次互反律可以得到:

(ap)=(aq),a>2\left(\frac ap\right)=\left(\frac aq\right),a>2

a=2a=2时,需要pq(mod  8)p\equiv q(mod\;8)才能满足(ap)=(aq)\left(\frac ap\right)=\left(\frac aq\right).

二次互反律如下:

Quadratic Reciprocity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from Crypto.Util.number import *
from Crypto.PublicKey import RSA
from Crypto.Math.Primality import *
from gmpy2 import next_prime
from math import prod
from random import randint
while 1:
p = getPrime(512)
if p % 4 == 3:
break

t = []
for i in range(2, 50):
if isPrime(i):
t.append(i)

pmod = p % 614889782588491410

while 1:
while 1:
q = pmod + randint((2 ** 511) // 614889782588491410, (2 ** 512) // 614889782588491410) * 614889782588491410
# q = getPrime(512)
if isPrime(q) and q % 4 == 3:
break
n = p * q
e = 0x10001
d = inverse(e, (p - 1) * (q - 1))

try:
key = RSA.construct([n, e, d])

continue
except ValueError:
print(p)
print(q)
break



RSA construct 函数漏洞
https://algellar.github.io/2023/05/30/RSA/RSA construct 函数漏洞/
作者
algellar
发布于
2023年5月30日
许可协议