两道题,赛后被问到,顺便水水博客。
ezrsa·
题目:ezrsa.rar
2019 Crypto CTF的Clever Girl 原题,甚至n
、X
和Y
都没改,只是换了个flag。。。
大概意思是,现在有
p p + 1 + q + 1 q = 2 s − X s + Y \frac{p}{p+1} + \frac{q+1}{q} = \frac{2s - X}{s + Y}
p + 1 p + q q + 1 = s + Y 2 s − X
知道X X X 、Y Y Y 和n = p q n = pq n = pq ,分解n n n ,把左边分式合并一下得
2 n + p + q + 1 n + q = 2 s − x s + y \frac{2n + p + q + 1}{n + q} = \frac{2s - x}{s + y}
n + q 2 n + p + q + 1 = s + y 2 s − x
然后分子分母分别相等得
2 n + p + q + 1 = 2 s − x n + q = s + y 2n + p + q + 1 = 2s - x \\
n + q = s + y
2 n + p + q + 1 = 2 s − x n + q = s + y
PS:准确来说这里两边分子分母是存在一个倍数关系,但是因为
GCD ( 2 n + p + q + 1 , n + q ) = GCD ( 2 s − x , s + y ) = 1 \text{GCD}(2n + p + q + 1, n + q) = \text{GCD}(2s - x, s + y) = 1
GCD ( 2 n + p + q + 1 , n + q ) = GCD ( 2 s − x , s + y ) = 1
所以这个倍数为1 1 1 ,才直接相等。
消去s s s 后得到
q = p + 1 + x + 2 y q = p + 1 + x + 2y
q = p + 1 + x + 2 y
两边乘上p p p 再整理一下,得到关于p p p 的方程
p 2 + ( 1 + x + 2 y ) p − n = 0 p^2 + (1 + x + 2y)\ p - n = 0
p 2 + ( 1 + x + 2 y ) p − n = 0
解方程求出p p p ,然后RSA解密即可。
参考代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 x = 153801856029563198525204130558738800846256680799373350925981555360388985602786501362501554433635610131437376183630577217917787342621398264625389914280509 y = 8086061902465799210233863613232941060876437002894022994953293934963170056653232109405937694010696299303888742108631749969054117542816358078039478109426 n = 161010103536746712075112156042553283066813155993777943981946663919051986586388748662616958741697621238654724628406094469789970509959159343108847331259823125490271091357244742345403096394500947202321339572876147277506789731024810289354756781901338337411136794489136638411531539112369520980466458615878975406339 c = 15380535750650959213679345560658190067564859611922563753882617419201718847747207949211621591882732604480600745000879508274349808435529637573773711729853565120321608048340424321537282281161623712479117497156437792084977778826238039385697230676340978078264209760724043776058017336241110097549146883806481148999 R = PolynomialRing(ZZ, 'p' ) p = R.gen() f = R(p^2 + p * (1 + x + 2 *y) - n) p = f.roots()[0 ][0 ] assert n % p == 0 q = n // p import libnumphi = (p-1 ) * (q-1 ) e = 0x10001 d = e.inverse_mod(phi) m = pow (c, d, n) flag = libnum.n2s(int (m)) print (flag)
Dual ECDSA·
题目:Dual ECDSA.zip
听说是零解,后来打听了一下只是上题太晚,而且要爆破(主办方又等着背锅了
这个题有点麻烦,所以还是分一下标题(反正TOC修好了
题目分析·
首先题目有两个曲线,规模分别是256和521,说是NIST的参数所以应该都安全,可以先暂时排除曲线的漏洞。
继续分析,256的曲线用于伪随机数生成(PRNG),521的曲线用于ECDSA签名。
然后直接看embed_secret
,S
是flag的编码,K K K 是PRNG产生的随机数,就直接可以确定需要做PRNG的随机数预测。
翻一下PRNG的调用,PRNG在三个地方被调用:pri_key
的生成,sign
中k_bytes
的生成和embed_secret
。
embed_secret
就不用管了,因为是我要求的东西,k_bytes
由于后面k
做了hashfunc
,所以即使恢复了k
也不能恢复k_bytes
,所以也不用管,所以现在和PRNG有关的只剩私钥pri_key
,问题转化为知道ECDSA
的两个签名恢复私钥。
解ECDSA私钥·
看ECDSA
的sign
,有
s ≡ ( e + p r i _ k e y ⋅ r ) ⋅ k − 1 ( m o d O r d e r ) s \equiv (e + p_{ri\_key} \cdot r) \cdot k^{-1} \pmod {O_{rder}}
s ≡ ( e + p r i _ k ey ⋅ r ) ⋅ k − 1 ( mod O r d er )
未知数的逆元有点难处理,挪一下
s k ≡ e + p r i _ k e y ⋅ r ( m o d O r d e r ) s k \equiv e + p_{ri\_key} \cdot r \pmod {O_{rder}}
s k ≡ e + p r i _ k ey ⋅ r ( mod O r d er )
然后分析一下大小,除了k k k 因为用sha256
生成所以是256 bits,其他都是521 bits,256对于521来说算是“小”的数,所以可以格一格。
只有这样一个关系是不能直接上格的,因为p r i _ k e y p_{ri\_key} p r i _ k ey 是“大”的未知数,但是现在有两个签名,所以可以利用两个签名的关系消去p r i _ k e y p_{ri\_key} p r i _ k ey ,先把p r i _ k e y p_{ri\_key} p r i _ k ey 整理到一边
p r i _ k e y ≡ ( s i k i − e i ) r i − 1 ( m o d O r d e r ) p_{ri\_key} \equiv (s_i k_i - e_i) r_i^{-1} \pmod {O_{rder}}
p r i _ k ey ≡ ( s i k i − e i ) r i − 1 ( mod O r d er )
然后就有
( s 1 k 1 − e 1 ) r 1 − 1 ≡ ( s 2 k 2 − e 2 ) r 2 − 1 ( m o d O r d e r ) r 2 ( s 1 k 1 − e 1 ) ≡ r 1 ( s 2 k 2 − e 2 ) ( m o d O r d e r ) r 1 s 2 k 2 − r q e 2 + r 2 e 1 ≡ r 2 s 1 k 1 ( m o d O r d e r ) ( r 2 s 1 ) − 1 r 1 s 2 k 2 + ( r 2 s 1 ) − 1 ( r 2 e 1 − r q e 2 ) ≡ k 1 ( m o d O r d e r ) (s_1 k_1 - e_1) r_1^{-1} \equiv (s_2 k_2 - e_2) r_2^{-1} \pmod {O_{rder}} \\
r_2 (s_1 k_1 - e_1) \equiv r_1 (s_2 k_2 - e_2) \pmod {O_{rder}} \\
r_1 s_2 k_2 - r_q e_2 + r_2 e_1 \equiv r_2 s_1 k_1 \pmod {O_{rder}} \\
(r_2 s_1)^{-1} r_1 s_2 k_2 + (r_2 s_1)^{-1}(r_2 e_1 - r_q e_2) \equiv k_1 \pmod {O_{rder}}
( s 1 k 1 − e 1 ) r 1 − 1 ≡ ( s 2 k 2 − e 2 ) r 2 − 1 ( mod O r d er ) r 2 ( s 1 k 1 − e 1 ) ≡ r 1 ( s 2 k 2 − e 2 ) ( mod O r d er ) r 1 s 2 k 2 − r q e 2 + r 2 e 1 ≡ r 2 s 1 k 1 ( mod O r d er ) ( r 2 s 1 ) − 1 r 1 s 2 k 2 + ( r 2 s 1 ) − 1 ( r 2 e 1 − r q e 2 ) ≡ k 1 ( mod O r d er )
有点复杂,简化一下,令
A : = ( r 2 s 1 ) − 1 r 1 s 2 B : = ( r 2 s 1 ) − 1 ( r 2 e 1 − r q e 2 ) \begin{aligned}
A &:= (r_2 s_1)^{-1} r_1 s_2 \\
B &:= (r_2 s_1)^{-1}(r_2 e_1 - r_q e_2)
\end{aligned}
A B := ( r 2 s 1 ) − 1 r 1 s 2 := ( r 2 s 1 ) − 1 ( r 2 e 1 − r q e 2 )
就是
A k 2 + B ≡ k 1 ( m o d O r d e r ) A k 2 − k 12 O r d e r + B = k 1 A k_2 + B \equiv k_1 \pmod {O_{rder}} \\
A k_2 - k_{12} O_{rder} + B = k_1
A k 2 + B ≡ k 1 ( mod O r d er ) A k 2 − k 12 O r d er + B = k 1
然后就可以构造格基(D = 2 256 D = 2^{256} D = 2 256 为配平数)
M : = [ A 0 0 − O r d e r 1 0 B 0 D ] M := \begin{bmatrix}
A & 0 & 0 \\
-O_{rder} & 1 & 0 \\
B & 0 & D
\end{bmatrix}
M := ⎣ ⎡ A − O r d er B 0 1 0 0 0 D ⎦ ⎤
就有
( k 2 , k 12 , 1 ) ⋅ M = ( k 1 , k 12 , D ) (k_2, k_{12}, 1) \cdot M = (k_1, k_{12}, D)
( k 2 , k 12 , 1 ) ⋅ M = ( k 1 , k 12 , D )
大概估算一下(需要一点格攻击理论,略)
∥ ( k 1 , k 12 , D ) ∥ ≈ 2 256 < 2 521 + 256 3 ≈ σ ( M ) \| (k_1, k_{12}, D) \| \approx 2^{256} < 2^{\frac{521+256}{3}} \approx \sigma(M)
∥ ( k 1 , k 12 , D ) ∥ ≈ 2 256 < 2 3 521 + 256 ≈ σ ( M )
可以格。
然后规约解出k 1 k_1 k 1 ,代入
p r i _ k e y ≡ ( s i k i − e i ) r i − 1 ( m o d O r d e r ) p_{ri\_key} \equiv (s_i k_i - e_i) r_i^{-1} \pmod {O_{rder}}
p r i _ k ey ≡ ( s i k i − e i ) r i − 1 ( mod O r d er )
求出私钥p r i _ k e y p_{ri\_key} p r i _ k ey
分析PRNG·
有了私钥后再来分析PRNG的运作,首先无论是random_bytes
还是random_bit_integer
都是用round
去运作,所以直接看round
。
round
的输出就是( S t a t e ⋅ Q ) . x (S_{tate} \cdot Q).x ( S t a t e ⋅ Q ) . x ,然后抹去低16 bits,每次round
后都会_update
,对state进行更新
S t a t e , i + 1 = ( S t a t e , i ⋅ P ) . x S_{tate,\ i+1} = (S_{tate,\ i} \cdot P).x
S t a t e , i + 1 = ( S t a t e , i ⋅ P ) . x
然后random_bytes
或random_bit_integer
中把round
完抹去16 bits后剩下的240 bits拼接起来,再取高n n n bits/bytes作为输出。
私钥p r i _ k e y p_{ri\_key} p r i _ k ey 是PRNG生成的第一个伪随机数,p r i _ k e y p_{ri\_key} p r i _ k ey 是521 bits,所以在random_bit_integer
中有rounds=3
,把p r i _ k e y p_{ri\_key} p r i _ k ey 切分成三段:
p r i _ k e y : = ( sk 0 (240 bits) ∣ sk 1 (240 bits) ∣ sk 2 (41 bits) ) p_{ri\_key} := (
\text{sk}_0\text{ (240 bits)}\ |\
\text{sk}_1\text{ (240 bits)}\ |\
\text{sk}_2\text{ (41 bits)})
p r i _ k ey := ( sk 0 (240 bits) ∣ sk 1 (240 bits) ∣ sk 2 (41 bits) )
可得
sk 0 = ( S t a t e , 0 ⋅ Q ) . x (hide low 16 bits) S t a t e , 1 = ( S t a t e , 0 ⋅ P ) . x sk 1 = ( S t a t e , 1 ⋅ Q ) . x (hide low 16 bits) ⋯ ⋯ \begin{aligned}
\text{sk}_0 &= (S_{tate,\ 0} \cdot Q).x\ \text{ (hide low 16 bits)} \\
S_{tate,\ 1} &= (S_{tate,\ 0} \cdot P).x \\
\text{sk}_1 &= (S_{tate,\ 1} \cdot Q).x\ \text{ (hide low 16 bits)} \\
& \cdots\ \cdots
\end{aligned}
sk 0 S t a t e , 1 sk 1 = ( S t a t e , 0 ⋅ Q ) . x (hide low 16 bits) = ( S t a t e , 0 ⋅ P ) . x = ( S t a t e , 1 ⋅ Q ) . x (hide low 16 bits) ⋯ ⋯
Dual_EC_DRBG后门·
根据题目提示可以搜搜DUAL_EC的后门(DUAL_EC BACKDOOR) ,代入这题中,大概意思是,如果知道一个n n n ,使得P = n Q P = n Q P = n Q ,就有
S t a t e , 1 = ( S t a t e , 0 ⋅ P ) . x = ( S t a t e , 0 ⋅ n ⋅ Q ) . x = ( n ⋅ ( S t a t e , 0 ⋅ Q ) ) . x S_{tate,\ 1} = (S_{tate,\ 0} \cdot P).x = (S_{tate,\ 0} \cdot n \cdot Q).x = (n \cdot (S_{tate,\ 0} \cdot Q)).x
S t a t e , 1 = ( S t a t e , 0 ⋅ P ) . x = ( S t a t e , 0 ⋅ n ⋅ Q ) . x = ( n ⋅ ( S t a t e , 0 ⋅ Q )) . x
其中S t a t e , 0 ⋅ Q S_{tate,\ 0} \cdot Q S t a t e , 0 ⋅ Q 就是曲线上以sk 0 \text{sk}_0 sk 0 (补全低16 bits)为x坐标的点,爆破被隐藏的16 bits,即可恢复S t a t e , 1 S_{tate,\ 1} S t a t e , 1 ,然后用S t a t e , 1 S_{tate,\ 1} S t a t e , 1 去做后面的随机数预测。
PS:这里是这道题最恶心的地方,因为椭圆曲线乘法的效率并不高,还好这里sk 0 \text{sk}_0 sk 0 被隐藏的值并不大,测试了一下完整爆破大概要半小时,由于值不大所以爆了大概五分钟。
最后问题就剩下,如何找n n n 。
Meet in Middle爆n·
观察一下P P P 和Q Q Q 的生成,都是在S t a t e , 0 ⋅ G S_{tate,\ 0} \cdot G S t a t e , 0 ⋅ G 上乘了一个规模2 20 2^{20} 2 20 的随机数,姑且可以令
P = p ⋅ S t a t e , 0 ⋅ G Q = q ⋅ S t a t e , 0 ⋅ G \begin{aligned}
P = p \cdot S_{tate,\ 0} \cdot G \\
Q = q \cdot S_{tate,\ 0} \cdot G
\end{aligned}
P = p ⋅ S t a t e , 0 ⋅ G Q = q ⋅ S t a t e , 0 ⋅ G
可以轻易地得到
q P = p Q q P = p Q
qP = pQ
如果想同时爆破p p p 和q q q 的话,复杂度是2 20 + 20 2^{20+20} 2 20 + 20 ,不大可行,但如果用中间相遇的话,复杂度是2 ⋅ 2 20 2 \cdot2^{20} 2 ⋅ 2 20 ,可行。
然后就是Meet in Middle的套路了,首先爆破2 20 2^{20} 2 20 个q i P q_i P q i P ,用set
存起来(因为set
的搜索复杂度是log),然后爆破2 20 2^{20} 2 20 的p j Q p_j Q p j Q ,如果发现有p j Q p_j Q p j Q 落在set
中,即这个p j p_j p j (大概率)就是p p p ,最后再爆一次q i P = ? p Q q_i P \overset{?}{=} p Q q i P = ? pQ 把q q q 也爆出来即可。
然后计算n ≡ q − 1 p ( m o d O r d e r ) n \equiv q^{-1} p \pmod {O_{rder}} n ≡ q − 1 p ( mod O r d er ) 就得到n n n 。
随机数预测·
最后计算一下round
了多少次,把state
更新到对应位置即可。
参考代码:
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 from hashlib import sha256from tqdm import tqdmimport libnumNIST_256 = ( NIST_256_P := 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff , NIST_256_K := GF(NIST_256_P), NIST_256_A := NIST_256_K(0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc ), NIST_256_B := NIST_256_K(0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b ), NIST_256_CURVE := EllipticCurve(NIST_256_K, (NIST_256_A, NIST_256_B)), NIST_256_GEN := NIST_256_CURVE(0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296 , 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5 ), NIST_256_ORDER := 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 * 0x1 ) NIST_256_CURVE.set_order(NIST_256_ORDER) NIST_521 = ( NIST_521_P := 0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff , NIST_521_K := GF(NIST_521_P), NIST_521_A := NIST_521_K(0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc ), NIST_521_B := NIST_521_K(0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00 ), NIST_521_CURVE := EllipticCurve(NIST_521_K, (NIST_521_A, NIST_521_B)), NIST_521_GEN := NIST_521_CURVE(0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66 , 0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650 ), NIST_521_ORDER := 0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409 * 0x1 ) NIST_521_CURVE.set_order(NIST_521_ORDER) pub_key = NIST_521_CURVE(1284057549640164959065310299974715949780883842543987295579845101714329490371866414620403984059688256268916840753322634228189993387677187086805910608913877240 , 2871828286437266324077547225563521746454811466060839307814808187038977339931268294634407454097422974392263009902452840007777204947601588882408605940896033213 ) P = NIST_256_CURVE(44709432769601533647164055172337077577197388971405441067408648552120273899439 , 5146142757398992761089276624845497246953677849813524410717292563307277646248 ) Q = NIST_256_CURVE(79560279623532866071675257110279687192436171302512534366260446974055296569775 , 26859165885861631636190724510665269671005299628857498631278393159264950495322 ) sig1 = (5089140070991418866765317340224488883853896999535207637664248575094162436344850602488004304731596114325990969313189073305315747359692187240695087335320008392 , 2992276069467527712690275343211346442634209004250088074729707123246604474164141954635893526085370984681060737605953014207436859072640690696867665537702633916 ) sig2 = (2872033002190388322518484598506887434919678523735730644910370844671443735688758643453429779989693237887466003372385925789250465970691404896872113423303038969 , 6723990832715616500336095376073676701350917549865164961917998759715703447465536431516566237824694270482046698060410301496792150963281602144685882679390639901 ) emb_flag = NIST_521_CURVE(3956409056364638168400543181298452742239103965766830338476029710731353557119679043260892196298159250455141959488613009091004454622819349680688797299967002549 , 2951776604917486290836695572957027106817487586162444507028471362837490084205243361576174502575216282250806141358680269076670568688622463255476309019517775311 ) r1, s1 = sig1 r2, s2 = sig2 m1 = b"AN INFAMOUS PRNG NAMED DUAL_EC BACKDOORED BY NSA, FINALLY CONFIRMED BY SNOWDEN IN 2013." m2 = b"NO ONE CAN EXTRACT THE BACKDOOR! UNLESS YOU CAN BREAK THE ECDSA SIGNATURE SCHEME / ECDLP!" e1 = Integer(sha256(m1).hexdigest(), 16 ) e2 = Integer(sha256(m2).hexdigest(), 16 ) A = (r2 * s1).inverse_mod(NIST_521_ORDER) * r1 * s2 % NIST_521_ORDER B = (r2 * s1).inverse_mod(NIST_521_ORDER) * (r2 * e1 - r1 * e2) % NIST_521_ORDER D = 2 ^256 M = matrix(ZZ, [ [A, 0 , 0 ], [-NIST_521_ORDER, 1 , 0 ], [B, 0 , D] ]) L = M.LLL() k1 = L[0 ][0 ] pri_key = (s1 * k1 - e1) * r1.inverse_mod(NIST_521_ORDER) % NIST_521_ORDER assert pri_key * NIST_521_GEN == pub_keyprint ('pri_key = %d' % pri_key)qP = set () PP = P for qi in tqdm(range (1 , 2 ^20 )): qP.add(PP) PP += P res_p = [] QQ = Q for pi in tqdm(range (1 , 2 ^20 )): if QQ in qP: res_p += [Integer(pi)] print ('p = %d' % pi) QQ += Q assert len (res_p) == 1 p = res_p[0 ] sG = p * Q PP = P for qi in tqdm(range (1 , 2 ^20 )): if PP == sG: q = Integer(qi) print ('q = %d' % q) break PP += P n = q.inverse_mod(NIST_256_ORDER) * p % NIST_256_ORDER assert pri_key.nbits() == 521 sk0 = pri_key >> (521 - 240 ) sk1 = (pri_key >> (521 - 240 *2 )) & ((1 << 240 ) - 1 ) sk2 = pri_key & ((1 << (521 - 240 *2 )) - 1 ) sk0h = sk0 << 16 for sk0l in tqdm(range (2 ^16 )): try : SK0 = NIST_256_CURVE.lift_x(sk0h + sk0l) except ValueError: continue state1 = Integer((n * SK0).xy()[0 ]) test_sk1 = Integer((state1 * Q).xy()[0 ]) >> 16 if test_sk1 == sk1: print ('state1 = %d' % state1) break def getState (sn ): staten = state1 for i in range (sn - 1 ): staten = Integer((staten * P).xy()[0 ]) return staten sk2 = pri_key & ((1 << (521 - 240 *2 )) - 1 ) state2 = getState(2 ) assert bin (Integer((state2 * Q).xy()[0 ]) >> (256 - sk2.nbits())) == bin (sk2)state = getState(3 *3 ) K = 0 for _ in range (3 ): out = Integer((state * Q).xy()[0 ]) >> 16 K = (K << 240 ) + out state = Integer((state * P).xy()[0 ]) K = K >> (3 *240 - 521 ) print ('K = %d' % K)S = K.inverse_mod(NIST_521_ORDER) * emb_flag flag = libnum.n2s(int (S.xy()[0 ])) print (flag)