k
e
y
w
o
r
d
s
:
keywords:
keywords:
coppersmith ,
RSA ,
small_roots()
I
n
t
r
o
d
u
c
t
i
o
n
Introduction
Introduction
一般来说,coppersmith 在RSA 中应用最多的就是,高位攻击一类的已知部分二进制位,通过coppersmith 定理(里面应用了LLL 算法)求得剩余的二进制位
大家有没有想过,究竟在已知多少二进制位数的情况下,coppersmith 定理能够生效?
D
e
d
u
c
t
i
o
n
Deduction
Deduction
(本文提到的coppersmith 定理均只针对单变量)
先来看看coppersmith 定理本身的应用场景是:
现有一个
e
e
e阶的多项式
f
f
f,那么可以:
- 给定
β
\beta
β,快速求出模某个
n
n
n的因数
b
b
b意义下较小的根,其中
b
≥
n
β
b\geq n^{\beta}
b≥nβ,(
0
<
β
≤
1
0< \beta \leq 1
0<β≤1)
- 在模
n
n
n意义下,快速求出
n
β
2
e
n^{\frac{\beta^2}{e}}
neβ2?以内的根
而前面提到的高位攻击显然是应用了第二条性质,此时构造的多项式
f
=
p
h
i
g
h
+
p
u
n
k
n
o
w
n
f=p_{high}+p_{unknown}
f=phigh?+punknown?,阶数
e
=
1
e=1
e=1
应用coppersmith 定理求解
p
u
n
k
n
o
w
n
p_{unknown}
punknown?,前提条件是
p
u
n
k
n
o
w
n
≤
n
β
2
e
p_{unknown}\leq n^{\frac{\beta^2}{e}}
punknown?≤neβ2?
那么
n
β
2
e
n^{\frac{\beta^2}{e}}
neβ2?究竟有多大呢?在高位攻击的情况下
e
=
1
e=1
e=1,所以转换为是求
n
β
2
n^{\beta^2}
nβ2
而
β
\beta
β的定义是
n
n
n的某个因数
b
b
b使得
b
≥
n
β
b\geq n^{\beta}
b≥nβ,(
0
<
β
≤
1
0< \beta \leq 1
0<β≤1)
当然,在RSA 中
n
n
n的因数也就是组成
n
n
n的大素数,一般来说,
n
=
p
?
q
n=p\cdot q
n=p?q,其中
p
,
q
p,q
p,q均为大素数
python 中的实现代码(这里以
1024
b
i
t
s
1024bits
1024bits为例)
from Crypto.Util.number import getPrime
p = getPrime(1024)
q = getPrime(1024)
n = p * q
那么就意味着
n
n
n的大小为
2048
b
i
t
s
2048bits
2048bits或者
2047
b
i
t
s
2047bits
2047bits(大小涉及到乘法对二进制位数的关系,这里就不过多讲述了)
很显然
n
0.5
n^{0.5}
n0.5就是
1024
b
i
t
s
1024bits
1024bits左右的数,与
p
,
q
p,q
p,q很接近,但是仍有可能不满足
p
,
q
≥
n
0.5
p,q\geq n^{0.5}
p,q≥n0.5
检验一下
import gmpy2
assert gmpy2.iroot(n,2)[0] <= p
assert gmpy2.iroot(n,2)[0] <= q
'''
>>> assert gmpy2.iroot(n,2)[0] <= p
>>> assert gmpy2.iroot(n,2)[0] <= q
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
'''
此时不满足条件
回到问题的开头,我们想要知道的是在已知多少
p
p
p或者
q
q
q的二进制位数下,我们能够通过coppersmith 定理求得剩下的二进制位(所需构造的多项式前面已经构造好了)
所以我们可以来看看如果此时满足条件,那么我们至少需要已知多少二进制位数(这里假设求
p
p
p)
计算此时
n
β
2
n^{\beta ^2}
nβ2(
e
=
1
e=1
e=1,所以就忽略不代入运算了)的二进制位数,我们未知的二进制位数需要小于
n
β
2
n^{\beta ^2}
nβ2的二进制位数
gmpy2.iroot(n,2**2)[0].bit_length()
'''
>>> gmpy2.iroot(n,2**2)[0].bit_length()
512
'''
这里我们看到如果
p
,
q
≥
n
β
p,q\geq n^{\beta}
p,q≥nβ,那么我们未知的
p
p
p或者
q
q
q(任意一个均可)的二进制位数必须小于
512
512
512
以上都是假设的,我们来看看真实地满足
p
,
q
≥
n
β
p,q\geq n^{\beta}
p,q≥nβ情况下,我们未知的二进制位数必须小于多少?
既然
n
0.5
n^{0.5}
n0.5(即是
β
=
0.5
\beta=0.5
β=0.5情况下)不满足条件,那么与之最接近的
β
=
0.49
\beta=0.49
β=0.49的情况(也就是
β
<
0.5
\beta < 0.5
β<0.5时)一定能满足条件(这里又涉及到指数运算对二进制位数的影响,也不过多讲述了,可以自己计算试一试)
还是刚才那套检验过程以及计算
n
β
2
n^{\beta ^2}
nβ2的过程
assert gmpy2.iroot(n**49,100)[0] <= p
assert gmpy2.iroot(n**49,100)[0] <= q
gmpy2.iroot(n**(49**2),100**2)[0].bit_length()
gmpy2.iroot(n**(48**2),100**2)[0].bit_length()
gmpy2.iroot(n**(47**2),100**2)[0].bit_length()
'''
>>> assert gmpy2.iroot(n**49,100)[0] <= p
>>> assert gmpy2.iroot(n**49,100)[0] <= q
>>> gmpy2.iroot(n**(49**2),100**2)[0].bit_length()
492
>>> gmpy2.iroot(n**(48**2),100**2)[0].bit_length()
472
>>> gmpy2.iroot(n**(47**2),100**2)[0].bit_length()
453
'''
本来按照定理条件的推导,只要未知二进制位数少于
492
492
492均可应用coppersmith 定理求解
在sagemath 中应用coppersmith 定理的函数有两个:small_roots ,coppersmith_howgrave_univariate
后者的使用方法可以参考 Coppersmith 相关攻击 - CTF Wiki (ctf-wiki.org)
对RSA-Factoring with High Bits Known理解 - 简书 (jianshu.com)
这里求解coppersmith 我们统一使用sagemath 的small_roots() 函数;该函数导入的
β
\beta
β 起作用的只有一位小数(如果是两位小数,其求解范围还是相当于一位小数的求解范围),这就意味着一般形如p = getPrime(bits),q = geyPrime(bits) 的RSA应用coppersmith 求解p 的低位时,
β
\beta
β只能是最接近
0.5
0.5
0.5的
0.4
0.4
0.4
关于small_roots 的使用方法:
small_roots(X = ,beta = ) 有两个参数
X 代表所需要求解根的上限;虽然是根的上限,并不是说上限越高越好,当上限超过某个值的时候就会计算失效,即使已知二进制位数满足条件,也无法用此函数求得结果;所以一般来说X 取在给定情况下的最大求解上限
beta 即是前面提到的
β
\beta
β ,当p,q 二进制位数相同时一般只能取
0.4
0.4
0.4;如果p,q 二进制位数不同,就按照之前的方法具体问题具体分析
那么实际操作下,使用small_roots() 我们至少需要已知多少二进制位数呢?(以下是sagemath环境)
from Crypto.Util.number import getPrime
p = getPrime(int(1024))
q = getPrime(int(1024))
n = p * q
test_bits = 454
p_h = p >> test_bits
R.<x> = PolynomialRing(Zmod(n))
f = (p_h << test_bits) + x
f.small_roots(2**test_bits,0.4)
经过测试得到,当未知量小于等于
454
b
i
t
s
454bits
454bits时(p,q 为
1024
b
i
t
s
1024bits
1024bits),coppersmith 定理可以求解
之后改变了p,q 的大小,经过测试发现,当p,q 同二进制位数时,要使用small_roots() 应用coppersmith 定理求解
n
n
n的某个因数的低位,需要满足未知的二进制位数与因数之间的二进制位数的关系是
u
n
k
n
o
w
n
_
b
i
t
s
÷
p
_
b
i
t
s
≤
0.44
unknown\_bits \div p\_bits \leq 0.44
unknown_bits÷p_bits≤0.44 下面来以例题为标准应用一下small_roots 的灵活应用
P
r
o
b
l
e
m
Problem
Problem
问题来自鹤城杯2021,babyrsa
D
e
s
c
r
i
p
t
i
o
n
Description
Description
from Crypto.Util.number import getPrime, bytes_to_long
from secret import flag
p = getPrime(1024)
q = getPrime(1024)
n = p * q
e = 65537
hint1 = p >> 724
hint2 = q % (2 ** 265)
ct = pow(bytes_to_long(flag), e, n)
print(hint1)
print(hint2)
print(n)
print(ct)
A
n
a
l
y
s
i
s
Analysis
Analysis
显然是应用高位攻击
但是由于之前测试的结果,未知的二进制位数需要小于
454
454
454,而这里p 未知的二进制位数达到了
724
724
724,而q 则更多;
但是这里既然给出了p,q 两者分别的高位,低位;按照高位攻击的思路,需要是某一个因数的多个二进制位数,所以找方法能不呢把q 的低位转换为p 的低位,或者相反
{
q
l
o
w
≡
q
(
m
o
d
2
265
)
q
h
i
g
h
=
k
?
2
265
n
=
p
?
(
q
h
i
g
h
+
q
l
o
w
)
?
n
=
p
?
k
?
2
265
+
p
?
q
(
m
o
d
2
265
)
\begin{cases} q_{low} \equiv q\pmod {2^{265}}\\ q_{high} = k\cdot 2^{265}\\ n = p\cdot (q_{high} + q_{low}) \end{cases} \Rightarrow n =p\cdot k\cdot 2^{265} + p\cdot q\pmod {2^{265}}
??????qlow?≡q(mod2265)qhigh?=k?2265n=p?(qhigh?+qlow?)??n=p?k?2265+p?q(mod2265) 等式两边同时求余
2
265
2^{265}
2265
n
l
o
w
≡
p
?
k
?
2
265
+
p
?
q
l
o
w
(
m
o
d
2
265
)
?
p
l
o
w
≡
n
l
o
w
?
q
l
o
w
?
1
(
m
o
d
2
265
)
n_{low}\equiv p\cdot k\cdot 2^{265} + p\cdot q_{low}\pmod {2^{265}}\\ \Rightarrow p_{low}\equiv n_{low} \cdot q^{-1}_{low} \pmod{2^{265}}
nlow?≡p?k?2265+p?qlow?(mod2265)?plow?≡nlow??qlow?1?(mod2265) 这样一来我们就知道了
p
p
p的低
265
265
265位,此时未知的二进制位数仍然有
459
459
459位,而我们需要的是未知二进制位要在
454
454
454之下
只剩下
5
5
5位了,那么直接就可以爆破剩余的
5
5
5位二进制
S
o
l
v
i
n
g
?
c
o
d
e
Solving~code
Solving?code
from Crypto.Util.number import *
import gmpy2
p_high = 1514296530850131082973956029074258536069144071110652176122006763622293335057110441067910479
q_low = 40812438243894343296354573724131194431453023461572200856406939246297219541329623
n = 21815431662065695412834116602474344081782093119269423403335882867255834302242945742413692949886248581138784199165404321893594820375775454774521554409598568793217997859258282700084148322905405227238617443766062207618899209593375881728671746850745598576485323702483634599597393910908142659231071532803602701147251570567032402848145462183405098097523810358199597631612616833723150146418889589492395974359466777040500971885443881359700735149623177757865032984744576285054725506299888069904106805731600019058631951255795316571242969336763938805465676269140733371287244624066632153110685509892188900004952700111937292221969
enc = 19073695285772829730103928222962723784199491145730661021332365516942301513989932980896145664842527253998170902799883262567366661277268801440634319694884564820420852947935710798269700777126717746701065483129644585829522353341718916661536894041337878440111845645200627940640539279744348235772441988748977191513786620459922039153862250137904894008551515928486867493608757307981955335488977402307933930592035163126858060189156114410872337004784951228340994743202032248681976932591575016798640429231399974090325134545852080425047146251781339862753527319093938929691759486362536986249207187765947926921267520150073408188188
e = 65537
mod = pow(2,265)
p0 = n * inverse_mod(q_low,mod) % mod
PR.<x> = PolynomialRing(Zmod(n))
for i in range(2**5):
f = p_high * (2^724) + p0 + (x * 32 + i) * mod
f = f.monic()
out_p = f.small_roots(2^454,0.4)
if len(out_p) != 0:
print(out_p[0])
break
p = out_p[0] * 32 + i * mod + p_high * (2^724) + p0
p = 133637329398256221348922087205912367118213472434713498908220867690672019569057789598459580146410501473689139466275052698529257254973211963162087316149628000798221014338373126500646873612341158676084318494058522014519669302359038980726479317742766438142835169562422371156257894374341629012755597863752154328407
assert n % p == 0
q = n // p
fai_n = (p-1) * (q-1)
d = inverse_mod(e,fai_n)
m = pow(enc,d,n)
print(bytes.decode(long_to_bytes(m)))
O
t
h
e
r
Other
Other
关于RSA 中应用coppersmith 还有部分私钥泄露攻击等等
这里就不过多阐述了,可以参考 CTF中的RSA基本套路(3) - 食兔人的博客 (ycdxsb.cn)
|