2022 D3CTF 的d3factor,顺便用来学习一元(univariate)的Coppersmith方法,然后尽量讲一下二元(bivariate)的方法;

二元的方法大部分跟一元类似的,只是有些细节还没搞懂。。。(菜

然后二元的方法还有个模的版本,拿了Boneh-Durfee的攻击来做栗子。

全文有点长,不过还是建议从前往后阅读,有些前面详细讲过的东西后面都是直接略了的(逃

d3factor·

题目链接:https://paste.ubuntu.com/p/VkNv57QGkh/

先讲一下原理,当时队友已经找到这篇论文([NR15])了(不过当时不会Coppersmith没搞出来,),原理就在论文的第4章。

首先代码的14行以上是没漏洞的(应该),题目的意思大概是要从16-21行里解出ppqq,然后14行前的RSA解mm。从代码上看,有NN下的两组密钥:

e1d11(modφ(N))e2d21(modφ(N))e_1d_1 \equiv 1 \pmod {\varphi(N)} \\ e_2d_2 \equiv 1 \pmod {\varphi(N)}

然后(d2d1)(d_2-d_1)是一个小的数(1000bits,相对于NN是小的。。。),直觉上说是要把这个数搞出来的(而且确定了要用格做的话是肯定要提取一些小的未知数的),所以,把第一条式子乘一个e2e2,第二条式子乘一个e1e_1然后相减(有点像扩展Wiener里面Guo 's Approach的操作):

e1e2d1e2(modφ(N))e1e2d2e1(modφ(N))e1e2(d2d1)e1e2(modφ(N))e_1e_2d_1 \equiv e_2 \pmod {\varphi(N)} \\ e_1e_2d_2 \equiv e_1 \pmod {\varphi(N)} \\ e_1e_2(d_2-d_1) \equiv e_1-e_2 \pmod {\varphi(N)}

PS:构造到这一步,其实有点像之前说用格解方程的格式,但后来算了一下发现不在可解范围里,所以就需要更强大的Coppersmith方法(虽然论文里也有说- )

接着,设x=d2d1x=d_2-d_1a0=e2e1a_0=e_2-e_1a1=e1e2a_1=e_1e_2,然后把上面最后一条式子整理一下,就是:

a0+a1x0(modφ(N))a_0+a_1x \equiv 0 \pmod {\varphi(N)}

然后,现在这个式子是模φ(N)\varphi(N)00的,而φ(N)=p6(p1)(q1)\varphi(N)=p^6(p-1)(q-1),所以也是模p6p^600(展开一下就可以得到),之所以要转成模p6p^6的,是因为φ(N)\varphi(N)是未知的,NN是已知的,p6p^6的一个NN的一个因子,一会用Coppersmith时某些p6p^6的操作可以用已知的NN代替(至于具体是什么,一会再说-)。然后上式就可以化为:

a0+a1x0(modp6)a_0+a_1x \equiv 0 \pmod {p^6}

符合一元Coppersmith的格式,虽然,一元Coppersmith里是要求模数已知的,但是调函数时把模数设成NN还是可以阴差阳错地解出来- -

一元的Coppersmith在sage里面已经可以直接调了(即官方wp的small_roots,虽然我也现在才知道- ),某个在github上找的代码也可以直接用,这里以后者怼个例子:

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
# sage
load('coppersmith.sage')

c = 2420624631315473673388732074340410215657378096737020976722603529598864338532404224879219059105950005655100728361198499550862405660043591919681568611707967
N = 1476751427633071977599571983301151063258376731102955975364111147037204614220376883752032253407881568290520059515340434632858734689439268479399482315506043425541162646523388437842149125178447800616137044219916586942207838674001004007237861470176454543718752182312318068466051713087927370670177514666860822341380494154077020472814706123209865769048722380888175401791873273850281384147394075054950169002165357490796510950852631287689747360436384163758289159710264469722036320819123313773301072777844457895388797742631541101152819089150281489897683508400098693808473542212963868834485233858128220055727804326451310080791
e1 = 425735006018518321920113858371691046233291394270779139216531379266829453665704656868245884309574741300746121946724344532456337490492263690989727904837374279175606623404025598533405400677329916633307585813849635071097268989906426771864410852556381279117588496262787146588414873723983855041415476840445850171457530977221981125006107741100779529209163446405585696682186452013669643507275620439492021019544922913941472624874102604249376990616323884331293660116156782891935217575308895791623826306100692059131945495084654854521834016181452508329430102813663713333608459898915361745215871305547069325129687311358338082029
e2 = 1004512650658647383814190582513307789549094672255033373245432814519573537648997991452158231923692387604945039180687417026069655569594454408690445879849410118502279459189421806132654131287284719070037134752526923855821229397612868419416851456578505341237256609343187666849045678291935806441844686439591365338539029504178066823886051731466788474438373839803448380498800384597878814991008672054436093542513518012957106825842251155935855375353004898840663429274565622024673235081082222394015174831078190299524112112571718817712276118850981261489528540025810396786605197437842655180663611669918785635193552649262904644919
a = (e2-e1)*(e1*e2).inverse_mod(N) % N
X = 2^1000

R = Zmod(N)
P.<x> = PolynomialRing(R, 1);
f = x-a
bounds = [X]
xs = small_roots(f, bounds, m=8)

assert len(xs) > 0
x0 = xs[0][0]
p6 = gcd(f(x=x0), N)
p = iroot(int(p6), 6)
assert p[1] == True
p = p[0]
q = N // p^7

print('p = %d' % p)
print('q = %d' % q)

Univariate Coppersmith·

一元的Coppersmith是[Cop96a]里提出的,但是现在常用的是[HG97]里简化的版本,下面说的也是[HG97]的版本:

简单例子·

考虑下面方程:

x2+4x+30(mod37)x^2+4x+3 \equiv 0 \pmod {37}

如果有条件x4|x| \le 4的话,简单计算一下,42+44+3=35<374^2+4*4+3=35<37,即模数3737在上面其实是不起作用的,所以可以把(mod37)\pmod {37}删掉然后写成:

x2+4x+3=0x^2+4x+3 = 0

然后就变成了整数域上的方程就有很多方法可以解了。

简单原理·

形式化一点就是,设f(x)=i=0df(i)xif(x)=\sum^{d}_{i=0} f^{(i)} x^if(x)0(modN)f(x) \equiv 0 \pmod N,另f(x)=i=0df(i)xi|f|(x)=\sum^{d}_{i=0} |f^{(i)}| x^if(x)f(x)的1范数(1-norm),若存在正整数界限XX使得f(X)<N|f|(X)<N的话,则对于所有符合xX|x|\le Xxx,都有f(x)<N|f(x)| < N,若刚好xxf(x)f(x)在模NN下的根的话,xx也是f(x)f(x)在整数域下的根。(有点像NTRU的某一步,逃

通常做题时都不会给出系数符合f(x)<N|f(x)| < N的(未知数可能会给出xX|x|\le X,或者需要构造一下),不然就可以直接解了,所以就需要有一个可以把系数减小的方法,而说到减小,自然(吗)就会想到格的SVP最短向量问题了。

格规约的可行性·

先考虑一下把上面方程写成大一学的线性方程形式(不需要模数,因为只是为了降低系数):

Ax0(modN)Ax \equiv 0 \pmod N

L=UAL=UA的话,又可以写成:

LxUAxU00(modN)Lx \equiv UAx \equiv U*0 \equiv 0 \pmod N

这里的L=UAL=UA可以看成是一个换基的行操作,或者直接一点,UU就是LLL的操作,如果把AA看作是某个系数矩阵的话,LL就是格规约(reduce)后的系数矩阵(有更小的系数),而从上面的式子看来,规约操作后xx并没改变,即是LL系数下的(模NN的)根时,也是AA系数下的(模NN的)根

格的构造(例子)·

到这还有个问题,f(x)f(x)只是一条方程,并不能把系数写成上面说的矩阵AA的形式(顶多写成一个行向量)。要构造出一个矩阵(/格)的话,则还需要构造出一堆gi(x)g_i(x),符合:

gi(x)0(modN)g_i(x) \equiv 0 \pmod N

PS:每个gi(x)g_i(x)的系数可以“转化”出一个行向量,所以构造的gi(x)g_i(x)个数至少是这堆多项式的项数减1,即,gi(x)g_i(x)加上f(x)f(x)对应这个矩阵AA的行,这些多项式里的全部项(monomials)对应矩阵AA的列,AA要是一个基的话至少是一个方阵。

先给个直观点的例子描述一下这个矩阵是长啥样的,比如现在我有3个方程:

f(x)=1+2x+3x20(modN)g1(x)=4x+5x20(modN)g2(x)=6x20(modN)f(x) = 1 + 2x + 3x^2 \equiv 0 \pmod N \\ g_1(x) = 4x + 5x^2 \equiv 0 \pmod N \\ g_2(x) = 6x^2 \equiv 0 \pmod N

那么这个矩阵(/格)就是(PS:注意这里XX代进去了,原因是现在要求的是f(X)<N|f|(X)<N时的系数,也可参考下一节的推导):

[12X3X204X5X2006X2]\begin{bmatrix} 1 & 2X & 3X^2 \\ 0 & 4X & 5X^2 \\ 0 & 0 & 6X^2 \\ \end{bmatrix}

从上到下三行分别对应三个多项式f(xX)f(xX)、g1(xX)g_1(xX)g2(xX)g_2(xX)的系数,从左到右三列分别对应三个项11xxx2x^2。多项式和项的顺序其实是不重要的(不过记得格规约后的顺序跟规约前的保持一样就好),但是构造时为了美观/效率/容易算行列式通常会选择弄成三角矩阵(特别是论文里的,分析的时候大多是需要算行列式的- -)

使用条件的推导·

怎么构造这些gi(x)g_i(x)的话,其实有很多方法(而且不同的论文好像都有不同的方法- ),所以讲怎么构造之前,先来讲一下这个格(/矩阵)需要符合什么条件。

上面说了,对于f(x)=i=0df(i)xif(x)=\sum^{d}_{i=0} f^{(i)} x^if(x)0(modN),f(x) \equiv 0 \pmod N,要把模数NN去掉的话,需要符合f(X)<N|f|(X)<N,再形式化一点,令向量:

vf=(f(0),f(1)X,f(2)X2,...,f(d)Xd)v_f = (f^{(0)}, f^{(1)}X, f^{(2)}X^2, ... , f^{(d)}X^d)

则需要符合vf<N|v_f|<N,注意这里的|·|是指1范数(绝对值范数),因为格规约时用的是2范数(欧几里德范数,这里用vf\lVert v_f \rVert表示),所以需要用以下关系转换以下(维基)上有讲,证明我就略了(逃:

把我们的vfv_f代进去一下就是:

vfd+1vf|v_f| \le \sqrt{d+1} \lVert v_f \rVert

为了保证f(X)<N|f|(X)<N一定会成立,联立一下vf<N|v_f|<N(注意这里我是在反推,即以下每多一个不等号界可能会更紧,然后最终推出最坏情况下的界)就是(PS:vfv_f里元素个数是(d+1)(d+1)):

vfd+1vf<N|v_f| \le \sqrt{d+1} \lVert v_f \rVert < N

根据LLL的性质(取δ=3/4\delta=3/4时),

假设vf\lVert v_f \rVert是我们用LLL解出来的LL中的最短向量,则会满足vf2d/4 det(L)1/(d+1)\lVert v_f \rVert \le 2^{d/4}\ det(L)^{1/(d+1)},同样为了保证d+1vf<N\sqrt{d+1} \lVert v_f \rVert < N一定会成立,再联立一下,就是:

vfd+1vfd+12d/4det(L)1/(d+1)<N|v_f| \le \sqrt{d+1} \lVert v_f \rVert \le \sqrt{d+1} · 2^{d/4} · det(L)^{1/(d+1) } < N

最后的"<<"就是推出的条件,通常在使用时,度dd是小的(可以看成常数),NN是大的,即夸张点当NN \to \infty时,d+12d/4\sqrt{d+1} · 2^{d/4}部分可以忽略,最终就是需要(dNd \ll N):

det(L)<Nd+1det(L) < N^{d+1}

PS:注意换基不会改变格的行列式(的绝对值),所以也可以看成det(A)<Nd+1det(A) < N^{d+1}AALL是上面例子的AALL)。

从上面结论来看,我们会想要构造出来的格行列式det(L)det(L)越小越好,而且从应用来看,会希望XX越大越好,但从上节的例子来看,det(L)det(L)XX是相关的,即小的det(L)det(L)和大的XX是矛盾的,所以,要使得构造出来的格在XX尽量大的时候符合det(L)<Nd+1det(L) < N^{d+1}(应该)是一个挺难的问题。

具体步骤(例子)·

这里先不考虑最优的构造,举个简单的例子([Gal12]19.1.1节),顺便说说Coppersmith/HG97的方法是怎么操作的,而且怎么计算在这一个构造下XX的界。

按照上面的说法,我们需要构造一堆gi(x)0(modN)g_i(x) \equiv 0 \pmod N,有模NN余0的话比较简单且自然(吗)地会想到,只要gi(x)g_i(x)里面地每一项都含NN就好了。这里还有一处细节,因为gi(x)g_i(x)(加上f(x)f(x))是用来构造格基地,所以需要保证每个行向量vgiv_{g_i}加上vfv_f是线性无关的。

gi(x)=Nxi,0i<dg_i(x) = Nx^i, 0 \le i < d

然后构造出来的矩阵就是(上面说了,要代XX,即矩阵里放的是f(xX)f(xX)gi(xX)g_i(xX)的系数,还有一处细节是,f(x)f(x)的最高项系数为1,即其实是可以对f(x)f(x)的所有项成上一个(fd)1(modN)(f^{d})^{-1} \pmod N然后把f(x)f(x)转化成monic的,这里这个过程略了):

M=(N0000NX0000NXd10f(0)f(1)Xf(d1)Xd1Xd)M = \begin{pmatrix} N & 0 & \cdots & 0 & 0 \\ 0 & NX & \cdots & 0 & 0 \\ \vdots & \vdots & \ddots & \vdots & \vdots \\ 0 & 0 & \cdots & NX^{d-1} & 0 \\ f^{(0)} & f^{(1)}X & \cdots & f^{(d-1)}X^{d-1} & X^d \\ \end{pmatrix} \\

栗子·

以[Gal12]的Example 19.1.6作个栗子来讲一下操作步骤(不过变量名被我换了)。模数N=10001N=10001f(x)=x3+10x2+5000x222f(x)=x^3+10x^2+5000x-222,可以看出d=3d=3f(x)f(x)的最高次数),取X=10X=10。首先构造gi(x)=Nxi,0i<dg_i(x) = Nx^i, 0 \le i < d

g0(x)=Nx0=10001g1(x)=Nx1=10001xg2(x)=Nx2=10001x2g_0(x) = Nx^0 = 10001 \\ g_1(x) = Nx^1 = 10001x \\ g_2(x) = Nx^2 = 10001x^2

这里的gi(x)g_i(x)并没增加更多的项,所以可以简单看出这里的所有项是(1,x,x2,x3)(1, x, x^2, x^3),按照行是(g0(xX),g1(xX),g2(xX),f(xX))(g_0(xX), g_1(xX), g_2(xX), f(xX))的系数(注意顺序),列是上面对应的项,可以排出矩阵:

M=(N0000NX0000NX202225000X10X2X3)M = \begin{pmatrix} N & 0 & 0 & 0 \\ 0 & NX & 0 & 0 \\ 0 & 0 & NX^{2} & 0 \\ -222 & 5000X & 10X^{2} & X^3 \\ \end{pmatrix} \\

这里的NNXX是已知的,把具体值代进去就是:

M=(1000100001000100000100010002225000010001000)M = \begin{pmatrix} 10001 & 0 & 0 & 0 \\ 0 & 100010 & 0 & 0 \\ 0 & 0 & 1000100 & 0 \\ -222 & 50000 & 1000 & 1000 \\ \end{pmatrix} \\

MM做LLL(比如取参数δ=3/4\delta=3/4),得到LL

L=(44410200020009557102000200022250000100010009892500500100500000)L= \begin{pmatrix} 444 & 10 & -2000 & -2000 \\ 9557 & -10 & 2000 & 2000 \\ -222 & 50000 & 1000 & 1000 \\ 989 & 2500 & 500100 & -500000 \\ \end{pmatrix} \\

根据LLL的性质,LL的第一行(应该?)就是格LL的最短向量,可以使用这个(行)向量恢复我们想要的小系数,即把这一行中每个元素和(1,X,X2,X3)(1, X, X^2, X^3)中的元素分别相除,恢复出系数:

(444100,10101,2000102,2000103)=(444,1,20,2)(\frac{444}{10^0}, \frac{10}{10^1}, \frac{-2000}{10^2}, \frac{-2000}{10^3}) = (444, 1, -20, -2)

可以简单验证一下这里的除法都是整除的:先把MM中每一列的(1,X,X2,X3)(1, X, X^2, X^3)分离出来,即拆成M=DMM=DM',其中DD(1,X,X2,X3)(1, X, X^2, X^3)的对角矩阵:

M=DM=M=(10000X0000X20000X3)(N0000N0000N02225000101)M = DM' = M = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & X & 0 & 0 \\ 0 & 0 & X^2 & 0 \\ 0 & 0 & 0 & X^3 \\ \end{pmatrix} · \begin{pmatrix} N & 0 & 0 & 0 \\ 0 & N & 0 & 0 \\ 0 & 0 & N & 0 \\ -222 & 5000 & 10 & 1 \\ \end{pmatrix} \\

假设UU是LLL的操作(上面有说过),则L=UM=UDML=UM=UDM',由于DD是对角矩阵,所以DD在矩阵乘法中是可交换的,可得L=UDM=D(UM)L=UDM'=D(UM'),即(1,X,X2,X3)(1, X, X^2, X^3)可分别整除LL中的对应列。

然后把系数(444,1,20,2)(444, 1, -20, -2)(1,x,x2,x3)(1, x, x^2, x^3)分别对应回去,即得:

h(x)=2x320x2+x+444h(x) = -2x^3 - 20x^2 + x + 444

可以简单验证一下h(X)<N|h|(X)<N,把XX带进去一下就得到(注意取绝对值)h(X)=4454<10001=N|h|(X)=4454 < 10001 = N。按照最开头说的,这时候的modN\mod N可以去掉,解h(x)0(modN)h(x) \equiv 0 \pmod N即解h(x)=0  on  Zh(x)=0\ \ on\ \ \mathbb{Z}。通过解2x320x2+x+444-2x^3 - 20x^2 + x + 444可以解出x0=4x_0=4

根据上面讲“可行性“时说的,h(x)h(x)的根与f(x)f(x)在模NN下是一样的,即x0=4x_0=4也是f(x)0(modN)f(x) \equiv 0 \pmod N的根(可以简单代进去验算一下)。

计算条件·

在”条件推导“里说道,若dNd \ll N,则这个方法可以成功使用时,需要det(L)<Nd+1det(L) < N^{d+1},但没有说XX需要符合的条件,因为使用不同构造的格算出来的XX条件是不一样的。

拿这节构造的格LL作栗子,首先根据”条件推导“里的结论,我们只需要算出det(L)det(L)就好了。然后,这节构造出来的格基MM是个三角矩阵,det(L)det(L)即其对角线上的乘积,即:

det(L)=XdΠi=0d1(NXi)=NdΠi=0dXi=NdXd(d+1)2det(L) = X^d * \Pi_{i=0}^{d-1} (NX^i) = N^{d} \Pi_{i=0}^{d} X^i = N^dX^{\frac{d(d+1)}{2}}

联立,即:

det(L)=NdXd(d+1)2<Nd+1det(L) = N^dX^{\frac{d(d+1)}{2}} < N^{d+1} \\

解一下,最终得到XX需要符合的条件是dNd \ll N时):

X<N2d(d+1)X < N^{\frac{2}{d(d+1)}}

PS:感觉这个只是x0x_0的范围,因为把上面栗子的XX代进去是不符合的,但x0x_0符合。

更优的构造·

上一节讲的栗子只是个直观的栗子,肯定不是最优的,怎么可以继续优化呢。看回det(L)<Nd+1det(L) < N^{d+1},前面也讲到,det(L)det(L)中是含XX的,想要同时得到小的det(L)det(L)和大的XX(似乎)是难的,但是如果可以增大"<<"右边呢。有两种方法:增大格的维度(项数)和增大模数:

  • 如果有f(x)0(modN)f(x) \equiv 0 \pmod N的话,那么也会有xif(x)0(modN)x^if(x) \equiv 0 \pmod N,通过构造一堆xif(x)x^if(x)就可以增大多项式的数量和项的数量,从而增大ω\omega(虽然det(L)det(L)也会增大就是了),这种方法又叫“x-shift”;
  • 如果有f(x)0(modN)f(x) \equiv 0 \pmod N的话,那么也会有ft(x)0(modNt)f^t(x) \equiv 0 \pmod {N^t},这样就可把原来的模数NN增大到NtN^t(同时项数也会增多,在项数增多后为了弄出个方阵还要增加多项式的数量,所以通常会和上一个方法混着用),其实更广义一点是:Ntifi(x)0(modNt)N^{t-i}f^i(x) \equiv 0 \pmod {N^t}
  • 最后把两种方法结合的话,就有Ntifi(x)xj0(modNt)N^{t-i} f^i(x) x^j \equiv 0 \pmod {N^t},可以简单验证一下,因为f(x)0(modN)f(x) \equiv 0 \pmod N,所以存在kk使得f(x)=kNf(x) = kN,于是

    Ntifi(x)xj=NtikiNixj=kixjNt0(modNt)N^{t-i} f^i(x) x^j = N^{t-i} k^i N^i x^j = k^i x^j N^t \equiv 0 \pmod {N^t}

    即得证。

到这里,之前推导的关系就要重写一下了:

det(L)<Nωtdet(L) < N^{\omega t}

其中ω\omega是我们构造的格的维度(原本的d+1d+1),tt就是原本的NN扩到NtN^t

[Cop96]/[HG97]的构造就是混合了上面两种方法,构造了(当然变量名应该和我不一样):

gi,j(x)=Ntifi(x)xj,  0it, 0j<dg_{i, j}(x) = N^{t-i}f^{i}(x)·x^j,\ \ 0 \le i \le t,\ 0 \le j < d

然后(根据上面两个方法说的)就会有gi,j0(modNt)g_{i, j} \equiv 0 \pmod {N^t};多项式数是d(t+1)d·(t+1),项数是dt+t=d(t+1)d·t+t=d·(t+1),即构造出来的格基维度是ω=d(t+1)\omega=d·(t+1)。稍微算一下行列式:

det(L)=i=0tj=0d1(NtiXdiXj)=Ndt(t+1)/2Xd(t+1)(dt+d1)/2det(L) = \prod_{i=0}^{t} \prod_{j=0}^{d-1} (N^{t-i}·X^{di}·X^j) = N^{dt(t+1)/2} X^{d(t+1)(dt+d-1)/2}

(PS:不会有人手算吧-)

代进det(L)<Nωtdet(L) < N^{\omega t},就有(dNd \ll N时):

NtX(dt+d1)<N2d(t+1)td(t+1)=N2tX<Ntdt+d1N^{t} X^{(dt+d-1)} < N^{\frac{2d·(t+1)·t}{d(t+1)}} =N^{2t} \\ X < N^{\frac{t}{dt+d-1}}

即这个构造的成功条件是x0<Ntdt+d1|x_0| < N^{\frac{t}{dt+d-1}}

PS:显而易见的是,参数tt(注意tt是我们选的参数)越大,x0|x_0|就可以越大:tdt+d1=1d+d1t\frac{t}{dt+d-1} = \frac{1}{d+\frac{d-1}{t}}tt越大时整个NN的指数部分也越大,当tt \to \infin时,"<<"右边趋近N1/dN^{1/d},即:

x0<N1d, t|x_0| < N^\frac{1}{d},\ t \to \infin

当然,tt越大构造出来的格也越大。

最后拿[Gal12]的Example 19.1.11做个栗子:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# sage
# stolen from 2022 TQLCTF wp
def matrix_overview(BB):
for ii in range(BB.dimensions()[0]):
a = ('%02d ' % ii)
for jj in range(BB.dimensions()[1]):
if BB[ii, jj] == 0:
a += ' '
else:
a += 'X'
if BB.dimensions()[0] < 60:
a += ' '
print(a)

# input
p = 2^30 + 3
q = 2^32 + 15
N = p*q
a0, a1, a2, a3 = (1942528644709637042, 1234567890123456789, 987654321987654321, 1)
P.<x> = PolynomialRing(ZZ)
f = a0 + a1*x + a2*x^2 + a3*x^3
X = 2^14

d = f.degree()
# choose yourself, bigger better and slower
t = 3

# coustruct gi
g_ij = []
for i in range(t+1):
for j in range(d):
g_ij.append( N^(t-i) * f^i * x^j )
g_ij = sorted(g_ij)
#print(g_ij)

monomials = []
for g in g_ij:
monomials += g.monomials()
monomials = sorted(set(monomials))
print('\nmonomials:')
print(monomials)

# construct matrix
w = d * (t+1)
assert w == len(monomials)
M = matrix(ZZ, w, w)
for i in range(w):
for j in range(w):
if monomials[j] in g_ij[i].monomials():
M[i, j] = g_ij[i](x=x*X).monomial_coefficient(monomials[j])
print('\nConstructed matrix:')
matrix_overview(M)

# reduce coeffcients
L = M.LLL()
h = sum([(L[0][i]/monomials[i](x=X)) * monomials[i] for i in range(w)])
print('\nReduced poly:')
print(h)
print('\nRoots of h:')
print(h.roots())

# simple check
res = []
for r in h.roots():
xx = r[0]
if h(x=xx) < N^t:
res.append(xx)

print('\nGot x0:')
print(res)

·

这章的内容主要参考了[Gal12]的19章和[Jou09]的13章,还有一些格的基础内容参考[HPS14]的第7章,还有一些论文(略)。

d3factor again·

讲了这么多再回来看一下d3factor,如果上一章有看完的话会发现一个问题,那个多项式方程的模式是要已知的,而d3factor的模数是p6p^6p6p^6肯定是未知的(不然就可以直接解了),然后官方wp用small_roots的时候模数的参数传的是NN,还居然可以直接解出来- -

首先要提到的是,[NR15]的其实是用了[LZP15]里3.1的方法,而[LZP15]里构造的gkg_k是长这样的:

最后还提到,gk(y)0(modpvt)g_k(y) \equiv 0 \pmod {p^{vt}},即模数是pvtp^{vt}(给没看过这篇论文的观众标注一下,这里gv=gcd(N,φ(N))g^v=gcd(N, \varphi(N))),仍然是未知的,但是因为gvg^vNN的一个因子,所以仍然可以用NN来扩大模数(参考“更优构造”的第二个方法),但是要注意NN中含的pvp^v的“份量”要大于tkt-k,即要确保(modpvt)\pmod {p^{vt}}下是余00。所以就有了上图中的max{}max\{·\}.

而直接用NN作模数传进small_roots也可以解出来的原因是,按照上一章的构造,pvp^v的“份量”只会比[LZP15]的更多,所以还是能保证gk(y)0(modpvt)g_k(y) \equiv 0 \pmod {p^{vt}},但是显然(吗?)算出来的x0|x_0|范围会更小,如果题目的输入卡一下的话估计small_roots会算不出来。

直观上来看,对于det(L)<Nωtdet(L) < N^{\omega t},两种方法都不会影响"<<“右边的NωtN^{\omega t},而对于”<<"左边的det(L)det(L),如果使用small_roots的话,NN的“含量”会增加(没看过源码,但估计是上一章的算法),则XX的“含量”会相应减小,则x0|x_0|的取值会更小。(具体小多少就懒得算了,逃下面已补充)

最后附一个[LZP15]解法的代码(和上面的大同小异):

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# sage
# from 2022 tql wp
def matrix_overview(BB):
for ii in range(BB.dimensions()[0]):
a = ('%02d ' % ii)
for jj in range(BB.dimensions()[1]):
if BB[ii, jj] == 0:
a += ' '
else:
a += 'X'
if BB.dimensions()[0] < 60:
a += ' '
print(a)

# https://eprint.iacr.org/2014/343.pdf (3.1)
def YaoLu(f, N, m, t, X):
P = f.parent()
dim = m+1

# as g_k(x) in YaoLu's paper
gks = []
for k in range(dim):
gks.append( N^max(ceil(v*(t-k)/u), 0) * f^k )
gks = sorted(gks)

# order is not important
# but monomials would be convenient (:
monomials = []
for gk in gks:
monomials += gk.monomials()
monomials = sorted(set(monomials))
print(monomials)

assert dim == len(monomials)
M = matrix(QQ, dim, dim)
for i in range(dim):
for j in range(dim):
if monomials[j] in gks[i].monomials():
M[i, j] = gks[i](x=x*X).monomial_coefficient(monomials[j])
# just debug
matrix_overview(M)

# LLL reduce coefficients
L = M.LLL()
h = sum([(L[0][i]/monomials[i](x=X)) * monomials[i] for i in range(dim)])

# simple check
res = []
for r in h.roots():
xx = r[0]
if h(x=xx) < N^t: # p^{vt} < N^t, loose
res.append(xx)
return res

if __name__ == '__main__':
from gmpy2 import iroot
import libnum

# known
c = 2420624631315473673388732074340410215657378096737020976722603529598864338532404224879219059105950005655100728361198499550862405660043591919681568611707967
N = 1476751427633071977599571983301151063258376731102955975364111147037204614220376883752032253407881568290520059515340434632858734689439268479399482315506043425541162646523388437842149125178447800616137044219916586942207838674001004007237861470176454543718752182312318068466051713087927370670177514666860822341380494154077020472814706123209865769048722380888175401791873273850281384147394075054950169002165357490796510950852631287689747360436384163758289159710264469722036320819123313773301072777844457895388797742631541101152819089150281489897683508400098693808473542212963868834485233858128220055727804326451310080791
e1 = 425735006018518321920113858371691046233291394270779139216531379266829453665704656868245884309574741300746121946724344532456337490492263690989727904837374279175606623404025598533405400677329916633307585813849635071097268989906426771864410852556381279117588496262787146588414873723983855041415476840445850171457530977221981125006107741100779529209163446405585696682186452013669643507275620439492021019544922913941472624874102604249376990616323884331293660116156782891935217575308895791623826306100692059131945495084654854521834016181452508329430102813663713333608459898915361745215871305547069325129687311358338082029
e2 = 1004512650658647383814190582513307789549094672255033373245432814519573537648997991452158231923692387604945039180687417026069655569594454408690445879849410118502279459189421806132654131287284719070037134752526923855821229397612868419416851456578505341237256609343187666849045678291935806441844686439591365338539029504178066823886051731466788474438373839803448380498800384597878814991008672054436093542513518012957106825842251155935855375353004898840663429274565622024673235081082222394015174831078190299524112112571718817712276118850981261489528540025810396786605197437842655180663611669918785635193552649262904644919
a = (e1-e2)*(e1*e2).inverse_mod(N) % N

P.<x> = PolynomialRing(ZZ)
f = x-a
X = 2^1000
u = 7
v = u-1

# YaoLu's params
m = 8
t = 6

# main
xs = YaoLu(f, N, m, t, X)
assert len(xs) > 0
x0 = xs[0]

# factor p, q
pv = gcd(f(x=x0), N) # gcd(k*p^vt, p^u*q) == p^v
p = iroot(int(pv), v)
assert p[1] == True
p = p[0]
q = N // p^7
print('p = %d' % p)
print('q = %d' % q)

# hack
n = p*q
phi = (p-1)*(q-1)
e = 65537
d = e.inverse_mod(phi)
m = pow(c, d, n)
flag = libnum.n2s(int(m))
print(flag)

2022.3.24补充·

大概算了一下怎么卡参数(x),补充下过程。YaoLu的方法在论文里已经有给出x0|x_0|的范围了,就直接引结论了(变量名被我换了,且把 uuvv代进了具体的rr ,这里的 β\beta大概是pp的规模,在d3factor中即2562048=18\frac{256}{2048}=\frac{1}{8}ϵ\epsilon是误差):

x0Nr(r1)β2ϵ|x_0| \le N^{r(r-1)\beta^2 - \epsilon}

所以这里主要关注HG97的方法的范围。方法还是关注det(L)<Nωtdet(L) < N^{\omega t}(类似但不完全相同),首先算det(L)det(L),方法类似“更优构造”里讲的,或者直接把d=1d=1代进去就好了,算出:

det(L)=i=0tNtiXi=Nt(t+1)2Xt(t+1)2det(L) = \prod_{i=0}^t N^{t-i}X^i = N^{\frac{t(t+1)}{2}} X^{\frac{t(t+1)}{2}}

ω\omega就是维度了即ω=t+1\omega=t+1。然后重点关注NωtN^{\omega t}里的NN,前面说了这个NN本应是使得gi,j0(modNt)g_{i, j} \equiv 0 \pmod {N^t}的那个模数里的NN,现在换到YaoLu的方法里的话是gk(y)0(modpvt)g_k(y) \equiv 0 \pmod {p^{vt}}即原本的NN现在应该是pv=pr1p^v=p^{r-1}。所以最终须符合的关系是:

det(L)<pωt(r1)det(L) < p^{\omega t (r-1)}

带进去具体值:

(prq)t(t+1)2Xt(t+1)2<pt(t+1)(r1)(p^rq)^{\frac{t(t+1)}{2}} X^{\frac{t(t+1)}{2}} < p^{t(t+1)(r-1)}

化简(假设ppqq同规模):

X<pr2qpr3X < \frac{p^{r-2}}{q} \approx p^{r-3}

放到d3factor里,如果用YaoLu的方法算出来的上界是1344ϵ1344-\epsilon bits(ϵ\epsilon是某个误差),用HG97算出来的大概是10241024 bits。

实际测试的话YaoLu的大概是12251225 bits,HG97的大概是题目的10001000 bits(small_roots的跟HG97相近)

PS:原来small_roots用的不是HG97那篇论文。。。

最后附测试代码:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# sage
#
# https://eprint.iacr.org/2014/343.pdf (3.1)
def YaoLu(f, N, m, t, X):
P = f.parent()
dim = m+1

# as g_k(x) in YaoLu's paper
gks = []
for k in range(dim):
gks.append( N^max(ceil(v*(t-k)/u), 0) * f^k )
gks = sorted(gks)

# order is not important
# but monomials would be convenient (:
monomials = []
for gk in gks:
monomials += gk.monomials()
monomials = sorted(set(monomials))

assert dim == len(monomials)
M = matrix(QQ, dim, dim)
for i in range(dim):
for j in range(dim):
if monomials[j] in gks[i].monomials():
M[i, j] = gks[i](x=x*X).monomial_coefficient(monomials[j])
# just debug
#matrix_overview(M)

# LLL reduce coefficients
L = M.LLL()
h = sum([(L[0][i]/monomials[i](x=X)) * monomials[i] for i in range(dim)])

# simple check
res = []
for r in h.roots():
xx = r[0]
if h(x=xx) < N^t: # p^{vt} < N^t, loose
res.append(xx)
return res

# gen
size = 1225
from Crypto.Util.number import getPrime
from sympy import nextprime
from gmpy2 import invert, iroot
p = getPrime(256)
q = getPrime(256)
N = pow(p, 7) * q
phi = pow(p, 6) * (p - 1) * (q - 1)
d1 = getPrime(2000)
d2 = nextprime(d1 + getPrime(size))
e1 = invert(d1, phi)
e2 = invert(d2, phi)
print('Gen:')
print('p = %d' % p)
print('q = %d' % q)

N = Integer(N)
e1 = Integer(e1)
e2 = Integer(e2)
print()

# from Nu1L wp
print('small_roots:')
try:
P.<x> = PolynomialRing(Zmod(N))
f = e1*e2*x - (e1-e2)
f = f.monic()
res = int(f.small_roots(X=2**size, beta=0.75)[0])
#p = int(gcd(int(f(res)), N)) # ????????
p = iroot(int(gcd(int(f(res)), N)), 6)
assert p[1] == True
p = p[0]
q = N // p**7
print('p = %d' % p)
print('q = %d' % q)
except:
print('Fail')
print()


print('YaoLU:')
try:
P.<x> = PolynomialRing(ZZ)
a = (e1-e2)*(e1*e2).inverse_mod(N) % N
f = x-a
X = 2^size
u = 7
v = u-1

# YaoLu's params
m = 16
t = 12

# main
xs = YaoLu(f, N, m, t, X)
assert len(xs) > 0
x0 = xs[0]

# factor p, q
pv = gcd(f(x=x0), N) # gcd(k*p^vt, p^u*q) == p^v
p = iroot(int(pv), v)
assert p[1] == True
p = p[0]
q = N // p^7
print('p = %d' % p)
print('q = %d' % q)

except:
print('Fail')
print()


# HG97
print('HG97:')
try:
P.<x> = PolynomialRing(ZZ)
a = (e1-e2)*(e1*e2).inverse_mod(N) % N
f = x-a
X = 2^size

d = f.degree()
# choose yourself, bigger better and slower
t = 21

# coustruct gi
g_ij = []
for i in range(t+1):
for j in range(d):
g_ij.append( N^(t-i) * f^i * x^j )
g_ij = sorted(g_ij)
#print(g_ij)

monomials = []
for g in g_ij:
monomials += g.monomials()
monomials = sorted(set(monomials))

# construct matrix
w = d * (t+1)
assert w == len(monomials)
M = matrix(ZZ, w, w)
for i in range(w):
for j in range(w):
if monomials[j] in g_ij[i].monomials():
M[i, j] = g_ij[i](x=x*X).monomial_coefficient(monomials[j])

# reduce coeffcients
L = M.LLL()
h = sum([(L[0][i]/monomials[i](x=X)) * monomials[i] for i in range(w)])
r
# simple check
res = []
for r in h.roots():
xx = r[0]
if h(x=xx) < N^t:
res.append(xx)

assert len(res) > 0
x0 = res[0]

# factor p, q
pv = gcd(f(x=x0), N) # gcd(k*p^vt, p^u*q) == p^v
p = iroot(int(pv), v)
assert p[1] == True
p = p[0]
q = N // p^7
print('p = %d' % p)
print('q = %d' % q)

except:
print('Fail')

Bivariate Coppersmith·

二元的Coppersmith在[Cop96b]里提出,然后Coron在[Cor04]和[Cor07]里提出了更简化的版本。

上图截自[Cop96b],注意这里的多项式pp是在Z\mathbb{Z}上的,而不是(modN)\pmod N,然后大概就是构造一个q(x,y)0(modN)q(x, y) \equiv 0 \pmod N,(下图截自[Cop96b]的完整版本-)

通过类似一元Coppersmith的方法规约出h(x,y)0(modN)h(x, y) \equiv 0 \pmod N,如果h(x,y)h(x, y)的系数足够小的话,模数NN可以去掉,就是h(x,y)=0h(x, y) = 0。最后如果p(x,y)p(x, y)h(x,y)h(x, y)不相关的话(上面写定理时假设了p(x,y)p(x, y)是不可约的,所以也不会相关),就是两条方程两个未知数,联立解出xxyy

PS:具体的做法其实是下面这样的,先构造Resultant解出x0x_0,然后x0x_0代进其中一条方程解出y0y_0

模N下的二元Coppersmith·

在实际应用(或做题)中,通常需要解的是p(x,y)0(modN)p(x, y) \equiv 0 \pmod N,比如著名的Boneh-Durfee攻击([BD00])。

这种二元模NN下的Coppersmith解法其实更像三元的Coppersmith(想一想把二元的同余方程展开不就是整数上的三元方程了- ),而且还少了构造q(x,y)0(modN)q(x, y) \equiv 0 \pmod N这一步:

上面大概意思是说,我们对p(x,y)0(modN)p(x, y) \equiv 0 \pmod N使用类似一元Coppersmith的方法进行LLL规约后,如果只拿格基中的第一行构造h1(x,y)h_1(x, y)是不够的(因为一个方程两个未知数- ),所以还可以拿格基的第二行(或者其他的行)来构造h2(x,y)h_2(x, y),这样才有两个方程。当XXYY足够小的时候,可以同时保证整数上有h1(x,y)h_1(x, y)h2(x,y)h_2(x, y),联立这两条方程即可解x0x_0y0y_0

下面大概说说[BD00]的构造(详细的话看[BD00]的Ⅳ章就好了,略- ):

[BD00]造格时用了两种“偏移”:x-shift的gi,k(x,y)g_{i, k}(x, y)和y-shift的hj,k(x,y)h_{j, k}(x, y),其中k=0,...,mk=0, ..., mi=0,...,mki=0, ..., m-kj=0,...,tj=0, ..., t,这里的mmtt都是参数。首先注意[BD00]里的模数是ee(即RSA的解密指数),k+(mk)=mk+(m-k)=m,即gi,k(x,y)g_{i, k}(x, y)hj,k(x,y)h_{j, k}(x, y)都是模eme^m00的。然后iijj的选择估计是为了把格基弄成方阵(表示不知道怎么想出来的,大概可能的想法可以参考下面维度的计算)。

接下来算一下这个格的维度,首先是多项式的数量(行数),即gik(x,y)g_{ik}(x, y)的个数加上hjk(x,y)h_{jk}(x, y)的个数,减去gik(x,y)g_{ik}(x, y)hjk(x,y)h_{jk}(x, y)重叠的个数(在i=0i=0j=0j=0时,g0k(x,y)=h0k(x,y)=fk(x,y)emkg_{0k}(x, y)=h_{0k}(x, y)=f^k(x, y)e^{m-k}):

k=0m(i=0mk+j=0t1)=(m+1)(2t+m+2)2\sum_{k=0}^{m}(\sum_{i=0}^{m-k} + \sum_{j=0}^{t} -1) = \frac{ (m+1) (2t+m+2) }{2}

然后是项的数量,直接算项数好像有点困难,但可以取巧一下,比如证明除了在i=0i=0j=0j=0时,gik(x,y)g_{ik}(x, y)hjk(x,y)h_{jk}(x, y)的最高项都是不同的。设dxd_xdyd_y分别是f(x,y)f(x, y)xxyy的度,则gik(x,y)g_{ik}(x, y)hjk(x,y)h_{jk}(x, y)的最高项可以表示如下:

gik: xkdx+iykdyhjk: xkdxykdy+jg_{ik}:\ x^{kd_x+i}y^{kd_y} \\ h_{jk}:\ x^{kd_x}y^{kd_y+j}

以上最高项中,gikg_{ik}xx指数部分总会比hjkh_{jk}的多了iihjkh_{jk}yy指数部分总会比gikg_{ik}的多了jj,所以可以知道只有在i=0i=0j=0j=0时上面才会有相同的项出现。i=0i=0j=0j=0刚好就是上面算多项式个数时重叠的情况,所以除去这些重叠的部分后即最高项的个数(然后应该还要证一下这些最高项包含所有项的,不会,逃)。随后算出的维度就是:

然后算行列式的话,把gik(x,y)g_{ik}(x, y)hjk(x,y)h_{jk}(x, y)排列一下的话,格基可以排成上面列出来的“最高项”为对角线的三角矩阵,所以行列式就是上面的“最高项”(删去重叠部分)的乘积了,和[BD00]一样,我把行列式分成det=detxdetydet=det_x·det_y来计算。首先是detxdet_x,即gikg_{ik}的最高项乘积(不会有人手算吧*2):

然后是detydet_y,即hjkh_{jk}的最高项乘积,注意这里我删去了和gikg_{ik}重叠的部分:

最后两个乘起来就是总的行列式:

最最后把行列式代进det<eωmdet<e^{\omega m}就是XXYY需要符合的条件(复鬼杂咯- -):

附一个从2022 TQLCTF里偷来的代码,应该可以直接用(清华改的代码就是不一样),另,这里最后对H的两个循环其实就是在试规约后的格的全部行中哪两行是不相关的,其他的应该上面都有说了(逃:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# sage
def matrix_overview(BB):
for ii in range(BB.dimensions()[0]):
a = ('%02d ' % ii)
for jj in range(BB.dimensions()[1]):
if BB[ii, jj] == 0:
a += ' '
else:
a += 'X'
if BB.dimensions()[0] < 60:
a += ' '
print(a)

def lattice_attack(PR, pol, e, mm, tt, X, Y):
x, y = PR.gens()
polys = []

for ii in range(mm + 1):
for jj in range(0, mm - ii + 1):
poly = e ^ (mm - ii) * x ^ jj * pol ^ ii
polys.append(poly)

for ii in range(mm + 1):
for jj in range(1, tt + 1):
poly = e ^ (mm - ii) * y ^ jj * pol ^ ii
polys.append(poly)

polys = sorted(polys)
monomials = []
for poly in polys:
monomials += poly.monomials()
monomials = sorted(set(monomials))
dims1 = len(polys)
dims2 = len(monomials)
M = matrix(QQ, dims1, dims2)

for ii in range(dims1):
M[ii, 0] = polys[ii](0, 0)
for jj in range(dims2):
if monomials[jj] in polys[ii].monomials():
M[ii, jj] = polys[ii](x * X, y * Y).monomial_coefficient(monomials[jj])

matrix_overview(M)
print('=' * 128)
print(len(M.rows()), len(M.columns()))
#M = M.hermite_form()
B = M.LLL()
print('LLL done')

det = B.det()
print(f"monomials: {monomials}")
nn = len(monomials)
matrix_overview(B)
H = [(i, 0) for i in range(dims1)]
H = dict(H)
for j in range(dims2):
for i in range(dims1):
H[i] += (monomials[j] * B[i, j]) / monomials[j](X, Y)

PQ.<q> = PolynomialRing(ZZ)
H = list(H.values())
solutions = []
print(len(H))
for i in range(len(H)):
for j in range(i + 1, len(H)):
pol1 = PR(H[i])
pol2 = PR(H[j])
rr = pol1.resultant(pol2, y)
if rr.is_zero() or rr.monomials() == [1]:
continue
sols = rr(q, q).roots()
for sol in sols:
solx = sol[0]
if solx == -1:
continue
try:
soly = pol1(solx, q).roots()[0][0]
solutions.append((solx, soly))
print('=' * 128)
except:
pass
if len(solutions) > 0:
break
if len(solutions) > 0:
break
if len(solutions) > 0:
break
return solutions

另外,[BD00]的Ⅴ章里有继续优化的方案,即构造出来的格基据说可以删掉某些行和列的(具体略);另另外[Cor07]的方法(好像)在构造时不用考虑构造成方阵的,留个坑有空撸个代码(菜,

https://github.com/pwang00/Cryptographic-Attacks/blob/master/Public%20Key/RSA/coron.sage

总结·

菜(:

参考·

  1. [BD00] Boneh D, Durfee G. Cryptanalysis of RSA with private key d less than N/sup 0.292[J]. IEEE transactions on Information Theory, 2000, 46(4): 1339-1349.
  2. [Cop96a] D. Coppersmith, Finding a Small Root of a Univariate Modular Equation, proceedings of Eurocrypt ’96, vol. 1070, Lecture Notes in Computer Science.
  3. [Cop96b] D. Coppersmith, Finding a Small Root of a Bivariate Integer Equation; Factoring with High Bits Known, proceedings of Eurocrypt’ 96, vol. 1070, Lecture Notes in Computer Science.
  4. [Cor04] Coron J S. Finding small roots of bivariate integer polynomial equations revisited[C]//International Conference on the Theory and Applications of Cryptographic Techniques. Springer, Berlin, Heidelberg, 2004: 492-505.
  5. [Cor07] Coron J S. Finding small roots of bivariate integer polynomial equations: A direct approach[C]//Annual International Cryptology Conference. Springer, Berlin, Heidelberg, 2007: 379-394.
  6. [Gal12] Galbraith, Steven D. Mathematics of public key cryptography. Cambridge University Press, 2012.
  7. [HG97] N. A. Howgrave-Graham, Finding small roots of univariate modular equations revisited. In Cryptography and Coding, volume 1355 of LNCS, pp. 131-142. Springer Verlag, 1997.
  8. [HPS14] Hoffstein, Jeffrey, et al. An introduction to mathematical cryptography. Vol. 1. New York: springer, 2008.
  9. [Jou09] Joux, Antoine. Algorithmic cryptanalysis. Chapman and Hall/CRC, 2009.
  10. [LZP15] Lu Y, Zhang R, Peng L, et al. Solving linear equations modulo unknown divisors: revisited[C]//International Conference on the Theory and Application of Cryptology and Information Security. Springer, Berlin, Heidelberg, 2015: 189-213.
  11. [NR15] Nitaj A, Rachidi T. New Attacks on RSA with Moduli N= p r q[C]//International Conference on Codes, Cryptology, and Information Security. Springer, Cham, 2015: 352-360.
  12. https://github.com/defund/coppersmith
  13. https://github.com/pwang00/Cryptographic-Attacks/blob/master/Public%20Key/RSA/coron.sage