NepCTF2022 招新赛 crypto篇

动态更新至全部复现完成


还是太菜了,看了wp依然是学到了很多,发现nep的密码师傅们经验也太丰富了!

p or s

from secret import keys
from Crypto.Util.number import *
assert(len(keys)==6)
Pbox=[
[0, 3, 6, 9, 10, 11, 13, 16, 18, 19, 20, 24, 25, 27, 28, 29, 30, 31],
[0, 1, 3, 8, 9, 11, 12, 14, 16, 18, 19, 23, 24, 25, 26, 28, 29],
[0, 1, 2, 3, 9, 10, 11, 13, 19, 20, 22, 25, 27, 28, 29, 31],
[0, 2, 3, 5, 6, 7, 8, 13, 16, 19, 21, 25, 26, 27, 28],
[2, 4, 6, 7, 9, 11, 12, 13, 16, 17, 20, 21, 22, 23, 24, 25, 27, 31],
[2, 10, 13, 15, 16, 17, 21, 22, 23, 24, 29, 31],
[1, 2, 8, 11, 12, 13, 16, 17, 19, 21, 22, 24, 25, 26, 27, 28, 30, 31],
[0, 3, 6, 13, 14, 17, 19, 21, 22, 23, 26, 27, 28],
[1, 5, 7, 8, 11, 12, 14, 15, 19, 23, 25, 27, 31],
[0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 16, 18, 19, 22, 23, 24, 25, 26, 27, 28],
[0, 1, 6, 7, 10, 15, 16, 21, 24, 25, 29, 30],
[1, 4, 5, 6, 7, 12, 13, 15, 18, 19, 20, 22, 26, 27, 29, 31],
[0, 3, 5, 8, 9, 17, 21, 22, 24, 25, 26, 27, 30],
[0, 2, 3, 4, 5, 6, 7, 8, 11, 17, 19, 20, 24, 25, 26, 27, 30],
[2, 6, 7, 8, 11, 12, 14, 16, 20, 21, 22, 24, 29, 30, 31],
[0, 2, 5, 6, 7, 8, 9, 10, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 29, 31],
[0, 1, 2, 3, 4, 5, 8, 10, 11, 12, 13, 16, 17, 18, 20, 21, 22, 23, 25, 26, 28, 29, 30],
[3, 5, 6, 8, 10, 13, 14, 17, 19, 20, 21, 22, 24, 26, 27, 29, 30],
[1, 3, 6, 12, 14, 15, 16, 17, 18, 21, 24, 25, 26, 27, 28],
[0, 1, 2, 3, 5, 6, 7, 8, 9, 12, 13, 19, 20, 23, 26, 29, 30],
[3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 20, 21, 22, 25, 26, 27, 28, 29, 30],
[0, 1, 2, 4, 6, 7, 9, 10, 11, 13, 15, 16, 18, 19, 20, 21, 25, 31],
[0, 2, 7, 10, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 29, 31],
[1, 2, 3, 5, 7, 8, 18, 19, 21, 22, 23, 25, 31],
[3, 4, 7, 8, 10, 11, 13, 14, 17, 18, 19, 21, 22, 23, 24, 28, 29],
[0, 2, 6, 7, 8, 10, 11, 12, 13, 16, 18, 19, 21, 23, 31],
[0, 1, 3, 4, 8, 13, 14, 16, 18, 19, 21, 26, 27, 30, 31],
[5, 6, 7, 9, 13, 14, 15, 18, 19, 20, 21, 24, 25, 28],
[1, 3, 4, 5, 6, 7, 11, 14, 16, 17, 19, 20, 21, 22, 23, 25, 30, 31],
[2, 3, 4, 6, 7, 11, 13, 17, 18, 19, 20, 23, 24, 25, 26, 28, 29, 30, 31],
[0, 1, 2, 3, 4, 7, 9, 10, 13, 15, 16, 19, 22, 23, 24, 25, 27],
[0, 1, 3, 4, 12, 16, 18, 19, 26, 30]]

def enc(flag):
    t=flag
    for i in keys:
       q=[]
       for j in Pbox:
           q.append(sum([t[k] for k in j])%2)
       t=[int(q[j])^int(i[j]) for j in range(32)]
    return t
 
assert(len(flag)==32)
fb=bin(bytes_to_long(flag))[2:].zfill(32*8)
ciphertext=""
for i in range(0,len(fb),32):
    t=enc([int(j) for j in fb[i:i+32]])
    ciphertext+="".join([str(j) for j in t])
 
print(ciphertext)
"""
0111110000100101000001101011110111101100000010110011101111000101111110111111100100100010001011000101000110110011111101000001001000000101111000001110001111001001100100111000011011101111111101001011100000100100110011111101100111001100111111110001111011101100

"""

根据加密流程,可以构造等式:
P ( P ( P ( P ( P ( P m + k 0 ) + k 1 ) + k 2 ) + k 3 ) + k 4 ) + k 5 = m ∗ P 6 + ∑ P i k 5 − i P(P(P(P(P(Pm+k0)+k1)+k2)+k3)+k4)+k5=m*P^6+\sum P^ik_{5-i} P(P(P(P(P(Pm+k0)+k1)+k2)+k3)+k4)+k5=mP6+Pik5i
已知明文前四个字节 “flag”,和密文,也就是已知一组明密文,利用已知明文攻击,可以求出 ∑ P i k 5 − i \sum P^ik_{5-i} Pik5i
然后对应每一组明密文,就只有m不知道了,套方程求解即可
在当时做此题的时候,发现用了6组key,就没有头绪了,想着怎么也不可能恢复出来key了。现在懂得了列式子的重要性哈哈,题还是做少了。
exp:

from tqdm import tqdm
from Crypto.Util.number import *


c="0111110000100101000001101011110111101100000010110011101111000101111110111111100100100010001011000101000110110011111101000001001000000101111000001110001111001001100100111000011011101111111101001011100000100100110011111101100111001100111111110001111011101100"
cc=[]
for i in range(0,len(c),32):
    cc.append(c[i:i+32])
    
Pbox=[
[0, 3, 6, 9, 10, 11, 13, 16, 18, 19, 20, 24, 25, 27, 28, 29, 30, 31],
[0, 1, 3, 8, 9, 11, 12, 14, 16, 18, 19, 23, 24, 25, 26, 28, 29],
[0, 1, 2, 3, 9, 10, 11, 13, 19, 20, 22, 25, 27, 28, 29, 31],
[0, 2, 3, 5, 6, 7, 8, 13, 16, 19, 21, 25, 26, 27, 28],
[2, 4, 6, 7, 9, 11, 12, 13, 16, 17, 20, 21, 22, 23, 24, 25, 27, 31],
[2, 10, 13, 15, 16, 17, 21, 22, 23, 24, 29, 31],
[1, 2, 8, 11, 12, 13, 16, 17, 19, 21, 22, 24, 25, 26, 27, 28, 30, 31],
[0, 3, 6, 13, 14, 17, 19, 21, 22, 23, 26, 27, 28],
[1, 5, 7, 8, 11, 12, 14, 15, 19, 23, 25, 27, 31],
[0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 16, 18, 19, 22, 23, 24, 25, 26, 27, 28],
[0, 1, 6, 7, 10, 15, 16, 21, 24, 25, 29, 30],
[1, 4, 5, 6, 7, 12, 13, 15, 18, 19, 20, 22, 26, 27, 29, 31],
[0, 3, 5, 8, 9, 17, 21, 22, 24, 25, 26, 27, 30],
[0, 2, 3, 4, 5, 6, 7, 8, 11, 17, 19, 20, 24, 25, 26, 27, 30],
[2, 6, 7, 8, 11, 12, 14, 16, 20, 21, 22, 24, 29, 30, 31],
[0, 2, 5, 6, 7, 8, 9, 10, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 29, 31],
[0, 1, 2, 3, 4, 5, 8, 10, 11, 12, 13, 16, 17, 18, 20, 21, 22, 23, 25, 26, 28, 29, 30],
[3, 5, 6, 8, 10, 13, 14, 17, 19, 20, 21, 22, 24, 26, 27, 29, 30],
[1, 3, 6, 12, 14, 15, 16, 17, 18, 21, 24, 25, 26, 27, 28],
[0, 1, 2, 3, 5, 6, 7, 8, 9, 12, 13, 19, 20, 23, 26, 29, 30],
[3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 20, 21, 22, 25, 26, 27, 28, 29, 30],
[0, 1, 2, 4, 6, 7, 9, 10, 11, 13, 15, 16, 18, 19, 20, 21, 25, 31],
[0, 2, 7, 10, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 29, 31],
[1, 2, 3, 5, 7, 8, 18, 19, 21, 22, 23, 25, 31],
[3, 4, 7, 8, 10, 11, 13, 14, 17, 18, 19, 21, 22, 23, 24, 28, 29],
[0, 2, 6, 7, 8, 10, 11, 12, 13, 16, 18, 19, 21, 23, 31],
[0, 1, 3, 4, 8, 13, 14, 16, 18, 19, 21, 26, 27, 30, 31],
[5, 6, 7, 9, 13, 14, 15, 18, 19, 20, 21, 24, 25, 28],
[1, 3, 4, 5, 6, 7, 11, 14, 16, 17, 19, 20, 21, 22, 23, 25, 30, 31],
[2, 3, 4, 6, 7, 11, 13, 17, 18, 19, 20, 23, 24, 25, 26, 28, 29, 30, 31],
[0, 1, 2, 3, 4, 7, 9, 10, 13, 15, 16, 19, 22, 23, 24, 25, 27],
[0, 1, 3, 4, 12, 16, 18, 19, 26, 30]]

flag=bin(bytes_to_long(b"flag"))[2:].zfill(32)
m=[int(i)for i in flag]
m=matrix(GF(2),1,32,m)
P=Matrix(GF(2),32,32)
j=0
for box in Pbox:
    for i in range(32):
        if i in box:
            P[i,j]=1
    j+=1
c=[int(i)for i in cc[0]]
c=matrix(GF(2),1,32,c)

sum_=c-m*P^6

for c in cc:
    c=[int(i)for i in c]
    c=matrix(GF(2),1,32,c)
    m=(c-sum_)*(P^6)^-1
    m=m.list()
    str_=""
    for i in m:
        str_+=str(i)
    mm=int(str_,2)
    print(long_to_bytes(int(mm)).decode(),end="")
#flag{P_has_no_Semantic_Security}

语义安全,科普一下:
语义安全(英语:Semantic Security)是密码学中的术语。如果已知某段未知文段的密文不会泄露任何该文段的其余信息,那么则称该密文是语义安全的。该概念相似于香农的完善保密性定义。完善保密性意味密文不会泄露任何明文的信息,而语义安全侧重表示被揭露的信息不会被实际窃取。
(源自维基百科。)

中学数学

还是不太擅长做这种题T_T
总体思路就是构造n的等式,q的范围缩小,找到q
我们知道 q = ( 1 + 1 2 500 ) p + r n = p q = ( 1 + 1 2 500 ) p 2 + r p q=(1+\frac{1}{2^{500}})p+r\\ n =pq=(1+\frac{1}{2^{500}})p^2+rp q=(1+25001)p+rn=pq=(1+25001)p2+rp
写到这里应该是基本操作。


先来讲讲 我的思路

看到 ( 1 + 1 2 500 ) p 2 + r p (1+\frac{1}{2^{500}})p^2+rp (1+25001)p2+rp,我想到的是 p 2 p^2 p2和rp差距较大,直接对n开2次方应该可以得到p的高位。但是 ( 1 + 1 2 500 ) (1+\frac{1}{2^{500}}) (1+25001)我们没法开方,于是想到左边乘上 ( 1 + 1 2 500 ) (1+\frac{1}{2^{500}}) (1+25001),这样右边成了
( 1 + 1 2 500 ) 2 p 2 + ( 1 + 1 2 500 ) r p (1+\frac{1}{2^{500}})^2p^2+(1+\frac{1}{2^{500}})rp (1+25001)2p2+(1+25001)rp
( 1 + 1 2 500 ) n (1+\frac{1}{2^{500}})n (1+25001)n 开方,大概能得到 ( 1 + 1 2 500 ) p (1+\frac{1}{2^{500}})p (1+25001)p的高位
那么 nextprime( ( 1 + 1 2 500 ) p (1+\frac{1}{2^{500}})p (1+25001)p)按理应该可以得到q的高位,利用高位攻击恢复q。
我们自己生成几个数来测试一下:
在这里插入图片描述
可以发现直接就能找出来q,连高位攻击都不需要
刚刚的式子是
( 1 + 1 2 500 ) 2 p 2 + ( 1 + 1 2 500 ) r p (1+\frac{1}{2^{500}})^2p^2+(1+\frac{1}{2^{500}})rp (1+25001)2p2+(1+25001)rp
可以发现
( 1 + 1 2 500 ) 2 p 2 的阶数是 ( 1 + 1 2 500 ) r p 的两倍 (1+\frac{1}{2^{500}})^2p^2的阶数是(1+\frac{1}{2^{500}})rp的两倍 (1+25001)2p2的阶数是(1+25001)rp的两倍
我们是不是可以推断,至少在1024和2048位的大数上,如果一个数是一个1024位的数和一个2048位的数之和,直接对2048开方有很大概率可以忽略低位的1024位,等同于直接对2048位的大数进行开方呢?这个显然还需要实践不断证明。
利用此方法,写exp:

from Crypto.Util.number import *
from sympy import nextprime
import gmpy2

n= 13776679754786305830793674359562910178503525293501875259698297791987196248336062506951151345232816992904634767521007443634017633687862289928715870204388479258679577315915061740028494078672493226329115247979108035669870651598111762906959057540508657823948600824548819666985698501483261504641066030188603032714383272686110228221709062681957025702835354151145335986966796484545336983392388743498515384930244837403932600464428196236533563039992819408281355416477094656741439388971695931526610641826910750926961557362454734732247864647404836037293509009829775634926600458845832805085222154851310850740227722601054242115507

c= 6253975396639688013947622483271226838902346034187241970785550830715516801386404802832796746428068354515287579293520381463797045055114065533348514688044281004266071342722261719304097175009672596062130939189624163728328429608123325223000160428261082507446604698345173189268359115612698883860396660563679801383563588818099088505120717238037463747828729693649297904035253985982099474025883550074375828799938384533606092448272306356003096283602697757642323962299153853559914553690456801745940925602411053578841756504799815771173679267389055390097241148454899265156705442028845650177138185876173539754631720573266723359186

q=nextprime(gmpy2.iroot(n+(n>>500),2)[0])
p=n//q
phi=(p-1)*(q-1)
d=gmpy2.invert(65537,phi)
m=pow(c,d,n)
print(long_to_bytes(m))

看了下官方wp,理解起来更复杂一点,但是按照他的这个思路,写出来的exp和我的是一模一样的,虽然说做法不一样。

上面我们说到:
( 1 + 1 2 500 ) n = ( 1 + 1 2 500 ) 2 p 2 + ( 1 + 1 2 500 ) r p (1+\frac{1}{2^{500}})n =(1+\frac{1}{2^{500}})^2p^2+(1+\frac{1}{2^{500}})rp (1+25001)n=(1+25001)2p2+(1+25001)rp
可以得到:
( 1 + 1 2 500 ) n = ( 1 + 1 2 500 ) 2 p 2 + ( 1 + 1 2 500 ) r p > ( 1 + 1 2 500 ) 2 p 2 (1+\frac{1}{2^{500}})n =(1+\frac{1}{2^{500}})^2p^2+(1+\frac{1}{2^{500}})rp>(1+\frac{1}{2^{500}})^2p^2 (1+25001)n=(1+25001)2p2+(1+25001)rp>(1+25001)2p2
又:
q = ( 1 + 1 2 500 ) p + r q 2 = [ ( 1 + 1 2 500 ) p ] 2 + r 2 + 2 [ ( 1 + 1 2 500 ) p r ] > ( 1 + 1 2 500 ) n q=(1+\frac{1}{2^{500}})p+r\\ q^2=[(1+\frac{1}{2^{500}})p]^2+r^2+2[(1+\frac{1}{2^{500}})pr]>(1+\frac{1}{2^{500}})n q=(1+25001)p+rq2=[(1+25001)p]2+r2+2[(1+25001)pr]>(1+25001)n
所以
( 1 + 1 2 500 ) p < ( 1 + 1 2 500 ) n < ( 1 + 1 2 500 ) p + r (1+\frac{1}{2^{500}})p<\sqrt{(1+\frac{1}{2^{500}})n}<(1+\frac{1}{2^{500}})p+r (1+25001)p<(1+25001)n <(1+25001)p+r
到这一步了,找q就简单了,wp上点到为止,那我也点到为止。嘿嘿
(其实也没剩什么东西了)

COA_RSA

from Crypto.Util.number import getStrongPrime, inverse, bytes_to_long
from secret import get_e, message, flag


def gen_parameter():
    p = getStrongPrime(1024)
    q = getStrongPrime(1024)
    N = p*q
    phi_N = (p-1) * (q-1)
    e = get_e(N, phi_N)
    d = inverse(e, phi_N)

    print(f"N:{N}\n")
    print(f"e:{e}\n")
    return N, e


def enc(m, e, N):
    return pow(m, e, N)


if __name__ == '__main__':
    assert(len(message) == 18)
    assert(flag == b"NepCTF{" + message + b"}")

    N, e = gen_parameter()
    m = bytes_to_long(message)
    c = enc(m, e, N)

    print(f"c:{c}")

"""
N:21584562909016222405243274981318074723609424537864138818516166296882870952596923388616896100179452902343709246295468740335682613555154383977025156631083258377497353559709636001246215851357586251697339782140616762844466658464225538929007012548727508420837702781524936615164070353418037478517089569783507555024418411710631778073941565485748505472232785028182423960096719453903248061164351629862893495202801853287312265865916733075532360012981688805080299664971109817752216823608878051817158172160419426189481753020154076548471877219325637944601624370884640014467928928695819398270014246851395540617763179525187009648347

e:3083508987002317486463324997331153531944203505409162688359452328126124421799560484088128014311350414620529892327924105762240373365022054853860736661583322625356764794244233714463745121622512321671048540305802394692066665494889362704143858935532501202976814683074990945023438621916862496931012795683358222146303422749958603642494190335211644060403826011553655733835479351434022282429633638548092493767861402690047591624267078125799219896130381280612151738318061042109602694447606410766564110264194828211637692086652912524063147129310052531789754620749834783108524619617738417203269142329494376062347356009094704271801

c:4350922598266339224026193891975078418170168801819772034264767820713898265684630717877568988722980203827973605369872324389093738872638952249759042892387749292013137399168284781320083750172563798340746904451459359268152189983004829803065811628192447436195347501480101582498240174872791092809557067538800318476384792579696811714128502969704568997167132768453189707964546102168532460995624338582249438596921314744866700025932162716723912105789085167524526913197129852605391260182535842364550510703599942313178440055016324692710162447783500265413604087721982116926701740447032328198647967485123967003172122184982695286738
"""

没什么特别的,就是get_e函数没有给出,所以肯定和e的生成有关系。
提示是目标向量长度满足 Minkowski’s bound
不得不惊讶于出这道题师傅的知识储备,感觉自己好菜啊。
根据官方wp:
已知
[ A m ] \begin{bmatrix} A\\ m \end{bmatrix} [Am]满足Minkowski’s bound,有:
A 2 + m 2 = ( m 1 − e  mod  n ) 2 + m 2 ≤ 2 n A^{2} + m^2 = ( m^{1-e} \ \text{mod}\ n )^2 + m^2 \leq 2n A2+m2=(m1e mod n)2+m22n
(我不知道怎么出来的但我觉得有些道理)
所以
1. m 1 − e   ( m o d   n ) 的量级为 n 1.m^{1-e}~(mod~n)的量级为\sqrt{n} 1.m1e (mod n)的量级为n ,


画一条分割线,因为从这里开始我就看不懂了!
(刚刚问了一下出题师傅,我发现涉及到了数论的一些基础知识,还是抽空补习一下吧。。)

2.可以猜测出 1 − e 1-e 1e 很接近 m m m 的阶,进而有 e = ϕ ( n ) / a − b e = \phi(n)/a - b e=ϕ(n)/ab ,其中 a   ∣   ϕ ( n ) a \,|\, \phi(n) aϕ(n) 并且 a , b a, b a,b 很小.

3. n / / e n//e n//e 算出a,对 b b b 进行穷举。已知 $ \phi(n) = (p-1)(q-1) $ 分解 n = p q n=pq n=pq 是容易的(中学数学),用 n ( m o d p ) = 0 n \pmod{p} = 0 n(modp)=0 来判断当前分解是否正确即可。之后再做常规RSA解密就好了。
(理论出自 NepCTF随缘师傅)


可以,很强,看来学习之路比我想象中的还要长好多。。。
根据这道题可以发现,当
A = m 1 − e   m o d   n A=m^{1-e}~mod~n A=m1e mod n
[ A m ] \begin{bmatrix}A\\m\end{bmatrix} [Am]是最短向量的时候,可以构造此攻击进行攻击。

# sagemath

from Crypto.Util.number import long_to_bytes

    N = 21584562909016222405243274981318074723609424537864138818516166296882870952596923388616896100179452902343709246295468740335682613555154383977025156631083258377497353559709636001246215851357586251697339782140616762844466658464225538929007012548727508420837702781524936615164070353418037478517089569783507555024418411710631778073941565485748505472232785028182423960096719453903248061164351629862893495202801853287312265865916733075532360012981688805080299664971109817752216823608878051817158172160419426189481753020154076548471877219325637944601624370884640014467928928695819398270014246851395540617763179525187009648347
    e = 3083508987002317486463324997331153531944203505409162688359452328126124421799560484088128014311350414620529892327924105762240373365022054853860736661583322625356764794244233714463745121622512321671048540305802394692066665494889362704143858935532501202976814683074990945023438621916862496931012795683358222146303422749958603642494190335211644060403826011553655733835479351434022282429633638548092493767861402690047591624267078125799219896130381280612151738318061042109602694447606410766564110264194828211637692086652912524063147129310052531789754620749834783108524619617738417203269142329494376062347356009094704271801
    c = 4350922598266339224026193891975078418170168801819772034264767820713898265684630717877568988722980203827973605369872324389093738872638952249759042892387749292013137399168284781320083750172563798340746904451459359268152189983004829803065811628192447436195347501480101582498240174872791092809557067538800318476384792579696811714128502969704568997167132768453189707964546102168532460995624338582249438596921314744866700025932162716723912105789085167524526913197129852605391260182535842364550510703599942313178440055016324692710162447783500265413604087721982116926701740447032328198647967485123967003172122184982695286738

def fact_N(phi, N):
    p_plus_q = N - phi + 1
    p_minus_q = isqrt(p_plus_q ** 2 - 4 * N)
    p = int(p_plus_q + p_minus_q) // 2
    if(N % p) != 0:
        print(f"Current phi is incorrect\n")
        return None
    else:
        print(f"Correct. Find p = {p}")
        q = p_plus_q - p
        print(f"q = {q}.\n")
        return p, q

    
def get_m(c, p, q, N):
    def dec(c, d, N):
        return pow(c, d, N)

    phi_N = (p-1) * (q-1)
    d = pow(e, -1, phi_N)
    return dec(c, d, N)


if __name__ == '__main__':
    a = N // e
    print(f"a = {a}")

    for b in range(20):
        phi = (e + b) * a
        fact = fact_N(phi, N)
        if fact is None:
            continue
        else:
            print(f"b = {b}")
            p, q = fact
            print(f"p = {p}")
            print(f"q = {q}")
            break

    m = get_m(c, p, q, N)
    message = long_to_bytes(m)
    flag = b"NepCTF{" + message + b"}"
    print(flag)

"""
b"NepCTF{N0t_4lw4ys_l4tt1ce}"
"""

回去补数学基础了,明天再去研究那个椭圆曲线的伪随机数发生器了。
————————————————————————————————————————————

芜湖 来喽

bd_key

from Crypto.Util.number import long_to_bytes, bytes_to_long, getPrime
from FLAG import flag

"""
 Note: I have modified Dual_EC a little bit.
    It would be EASIER for you to exploit it.
"""
class Dual_EC():
    def __init__(self, s_0=None):
        from Crypto.Util.number import getRandomNBitInteger
       
        # Init curve P-256
        self.p = 115792089210356248762697446949407573530086143415290314195533631308867097853951
        self.n = 115792089210356248762697446949407573529996955224135760342422259061068512044369
        self.b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b
        self.curve = EllipticCurve(GF(self.p), [-3, self.b])
        
        # Init P, Q
        self.Qx = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296
        self.Qy = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5
        self.Q = self.curve(self.Qx, self.Qy)
        self.d = getRandomNBitInteger(256)
        self.P = self.d * self.Q      
        
        # Init state, h_adin
        if s_0 == None:
            self.s_i = int(floor((2^16-1)*random()))
        else:
            self.s_i = s_0
        self.h_adin = 0
        
        self.__leak_par()

        
    def __leak_par(self):
        print(f"curve = {self.curve}")
        print(f"P = {self.P}")
        print(f"d = {self.d}")
        print(f"Q = {self.Q}")

    
    # Output 32bytes now.          
    def __Dual_EC_DRBG(self, h_adin = 0): 
        t_i = self.s_i ^^ h_adin
        self.s_i = (t_i*self.P)[0].lift()
        r_i = (self.s_i*self.Q)[0].lift()
        return r_i

    
    def getRandomNBytes(self, N:int) -> bytes:
        result = 0
        req = (N/32).ceil()

        for i in range(req):
            if(i == 0):
                result = (result << (32*8)) | self.__Dual_EC_DRBG(self.h_adin)
            else:
                result = (result << (32*8)) | self.__Dual_EC_DRBG()

        self.s_i = (self.s_i * self.P)[0].lift()

        result = result >> ((32*req - N)*8)
        return long_to_bytes(result)

    
def encrypt_flag(key:bytes) -> bytes:
    from Crypto.Cipher import AES
    from Crypto.Util.Padding import pad
    from FLAG3 import flag
    
    cipher = AES.new(key, AES.MODE_ECB)
    flag_pad = pad(flag, 16)
    ct = cipher.encrypt(flag_pad)
    return ct


def do_schnorr_identification(dbrg):
    p = getPrime(256)
    Zp = Zmod(p)
    g = Zp(2)
    q = g.multiplicative_order()
    Zq = Zmod(q)
    k = 10
    
    
    class SchnorrProver(object):
        def __init__(self):
            self.secret_key = Zq.random_element()
            self.public_key = g^self.secret_key

        def commit(self):
            self.r = Zq.random_element()
            return g^self.r

        def respond(self, challenge):
            return self.r - challenge*self.secret_key

        
    class SchnorrVerifier(object):
        def __init__(self, prover_public_key):
            self.prover_public_key = prover_public_key

        def challenge(self, commitment):
            self.commitment = commitment
            while True:
                c = bytes_to_long(dbrg.getRandomNBytes(32))
                if c < p:
                    break
            self.challenge_number = c
            return self.challenge_number

        def check(self, response):
            check_value = g^response*self.prover_public_key^self.challenge_number
            return self.commitment == check_value

        
    def run_protocol(iterations=1):
        for _ in range(iterations):
            prover = SchnorrProver()
            verifier = SchnorrVerifier(prover.public_key)
            t = prover.commit()
            c = verifier.challenge(t)
            s = prover.respond(c)
            assert(verifier.check(s))
            
            print(f"c = {c}")
            
    run_protocol()
    
    
def main():
    dbrg = Dual_EC()
    do_schnorr_identification(dbrg)
    key = dbrg.getRandomNBytes(16)
    ct = encrypt_flag(key)
    print(f"ct = {bytes_to_long(ct)}")

main()
output:
# curve = Elliptic Curve defined by y^2 = x^3 + 115792089210356248762697446949407573530086143415290314195533631308867097853948*x + 41058363725152142129326129780047268409114441015993725554835256314039467401291 over Finite Field of size 115792089210356248762697446949407573530086143415290314195533631308867097853951
# P = (72696788778535848020209987825324097844942627382508830211685965810687985426258 : 49180397040468497821240854375656422791216946832858522054245540263375986110762 : 1)
# d = 66604141534275704476445937214374130642068729921454877238730830814793201802544
# Q = (48439561293906451759052585252797914202762949526041747995844080717082404635286 : 36134250956749795798585127919587881956611106672985015071877198253568414405109 : 1)
# c = 59100197418944667413449341413044666843726352095054393072750502893110293231642
# ct = 25645992443585671366815910836517434170297823176311632150463962979581372384075859802765045877741181123347569267185176

考察Dual_EC的后门,先看伪随机数发生的原理
在这里插入图片描述
s0和adin0异或得到t0,t0P的x坐标作为s1。
产生随机数 s1
Q的x坐标即为随机数
若继续生成随机数,s1与adin1异或得到t1,t1P的x坐标作为s2,继续依照上述方式依次生成后续随机数。
攻击:
论文:https://eprint.iacr.org/2015/767.pdf
在这里插入图片描述
根据论文中描述,假设我们知道P、d、Q和由这个随机数随机生成的r1,那么我们可以知道r1是椭圆曲线上的一个x坐标,根据此x坐标来计算出y坐标,记此x、y坐标为点R,计算d
R,那么dR的X坐标即为s1P的X坐标,如此一来,我们就可以恢复所有随机数了!
看这一段代码:

    def __Dual_EC_DRBG(self, h_adin = 0): 
        t_i = self.s_i ^^ h_adin
        self.s_i = (t_i*self.P)[0].lift()
        r_i = (self.s_i*self.Q)[0].lift()
        return r_i

在本题中,adin默认为0,根据异或性质,可以忽略adin
可以发现,题目中先生成了一个c,然后再生成的key,也就是说key应该是生成的第二个随机数。
给了d相当于给了后门。根据上述内容构造exp:

借鉴一下官方wp,按照我的理解修改一下名字
# sagemath

from Crypto.Util.number import long_to_bytes
from Crypto.Cipher import AES

p = 115792089210356248762697446949407573530086143415290314195533631308867097853951
n = 115792089210356248762697446949407573529996955224135760342422259061068512044369
b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b
E = EllipticCurve(GF(p), [-3, b])
Qx = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296
Qy = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5
Q = E(Qx, Qy)
d = 66604141534275704476445937214374130642068729921454877238730830814793201802544
P = d * Q
c = 59100197418944667413449341413044666843726352095054393072750502893110293231642
ct = 25645992443585671366815910836517434170297823176311632150463962979581372384075859802765045877741181123347569267185176


def dec_flag(ct, key):
    cipher = AES.new(key, AES.MODE_ECB)
    ct = long_to_bytes(ct)
    flag = cipher.decrypt(ct)
    print(f"flag = {flag}")
    if b"NepCTF{" in flag:
        print("Success!")
    

def main():
    assert E.is_x_coord(c)
    s1_Q = E.lift_x(c)
    s1_P_x = Integer( (d * s1_Q)[0] )
    s2 = Integer( (s1_P_x * P)[0] )
    r2 = int( (s2 * Q)[0] )
    
    k = r2 >> ((32 - 16) * 8)#key是16位
    key = long_to_bytes(k)
    dec_flag(ct, key)
    
main()    
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Paintrain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值