huangx607087学习ECC的笔记3 0.About 一个密码学fw的学习笔记,很菜(
这一系列主要介绍几种ECC的加密方法,以及通过对RSA的类比,将RSA中常见的几种攻击方法应用于ECC上。
3月23日,自己进了X1c,感谢Am4师傅一路带我,自己后面要更加努力。然而自己还有6月的六级和期末考试,(我这个fw一定要过六级,期末考试一定要全过。。不过可以确定的是,自己会在暑假寻求一个突破。在9月能够成为Am4师傅那样(x
1.椭圆曲线上的除式 在椭圆曲线$y^2=x^3+ax+b$上,我们可以定义这样的除式: $$ F_{-1},F_0,F_1,F_2,F_3=-1,0,1,2y,3x^4+6ax^2+12bx-a^2 $$
$$ F_4=4y(x^6+5ax^4+20bx^3-5a^2x^2-4abx-8b^2-a^3) $$
$$ F_{2i}=F_{i+2}F_{i}^3-F_{i+1}^3F_{i-1} $$
$$ F_{2i+1}=\dfrac{F_i(F_{i+2}F_{i-1}^2-F_{i-2}F^2_{i+1})}{2y} $$
顺便我们在定义一下多项式$G,H$ $$ G_{i}=xF_i^2-F_{i+1}F_{i-1} $$
$$ H_i=(F_{i+2}F_{i-1}^2-F_{i-2}F^2_{i+1}) /4y $$
所以,我们可以得到以下的式子: $$ mP=(\dfrac{G_m(P)}{F_m^2(P)},\dfrac{H_m(P)}{F_m^3(P)}) $$ 而这个式子如果想实现的话,可以调用sagemath中的_multiple_x_numerator
,_multiple_x_denominator
两个函数来实现对$x$坐标的数乘的多项式(最终用$x$表示)
1 2 3 4 5 6 E=EllipticCurve(GF(10007 ),[5362 ,7045 ]) Fz=E._multiple_x_numerator(7 ) Fz''' x^49 + 9573*x^47 + 1673*x^46 + 9230*x^45 + 7088*x^44 + 2559*x^43 + 1491*x^42 + 1730*x^41 + 1886*x^40 + 8655*x^39 + 9529*x^38 + 2785*x^37 + 3587*x^36 + 3847*x^35 + 3439*x^34 + 1171*x^33 + 7340*x^32 + 2033*x^31 + 5346*x^30 + 3021*x^29 + 6843*x^28 + 1123*x^27 + 6261*x^26 + 4150*x^25 + 5110*x^24 + 9659*x^23 + 8409*x^22 + 6218*x^21 + 1501*x^20 + 4233*x^19 + 4240*x^18 + 5172*x^17 + 2625*x^16 + 1767*x^15 + 5499*x^14 + 7768*x^13 + 308*x^12 + 1527*x^11 + 2631*x^10 + 6193*x^9 + 6584*x^8 + 9405*x^7 + 1509*x^6 + 7683*x^5 + 3994*x^4 + 8777*x^3 + 2600*x^2 + 3465*x + 993 '''
经过数据的观察可以看出,$mP$中$x$坐标的分子表达式次数总是$m^2$,分母次数总是$m^2-1$,这也印证了之前笔记1 中提到的$\ln u$正比于$m^2$的结论。
一般情况下,对椭圆曲线上的点加密的时候,我们可以选择横坐标$x$,然后在椭圆曲线上找点。
我们可以选取一个整数$k$,明文$m$,我们可以计算$m^3+am+b$是否是模$q$的二次剩余。如果是的话,直接对结果开平方即可(见我之前数论笔记)。如果不是的话,我们计算$m+1$,直到找到符合的$m$值。根据二次剩余的分布可知,我们找不到的期望值为$\dfrac 1 {2^k}$。
2.几种ECC加密算法 之前我们讲过DH密钥交换和Elgamal加密系统,这里我们多介绍几个基于ECC的加密系统。
0x01 Demytko加密 1.加密 寻找两个大素数$p,q$,然后计算$n=pq$。然后确定椭圆曲线的系数$a,b$,并保证$\gcd(4a^3+27b^2,n)=1$。
然后我们计算$N_1,N_2,N_3,N_4=|E_p|,2p+2-N_1,|E_q|,2q+2-N_3$。 由于自己博客中某些符号无法打出来,因此我们借用符号$|D|$表示集合$D$中的元素个数而不是我打不出来的那个符号(因为打那个符号博客处理时会出严重问题导致markdown混乱
接着选取$e$保证$\gcd(e,N_i)=1$即可。其中$i$为$1$到$4$。同时计算$d_1,d_2,d_3,d_4$分别为$e$在模$\dfrac{N_1N_3}{\gcd(N_1N_3)},\dfrac{N_1N_4}{\gcd(N_1N_4)},\dfrac{N_2N_3}{\gcd(N_2N_3)},\dfrac{N_2N_4}{\gcd(N_2N_4)}$下的逆元。
计算出这些数值之后,我们把$n,e,u$作为公钥,$p,q,d_1,d_2,d_3,d_4$作为私钥。
加密时,选取点$P$满足其横坐标为$m$,然后计算$c=(eP)_x$。
2.解密 解密方计算$w=c^3+ac+b$,然后在$d_1$到$d_4$中选择一个这样的私钥用于解密,方法如下。为与分数的出发区分开,以下用$\mathrm{lgd}(\dfrac{a}{b})$来表示$a$是否是$b$的二次剩余,若是值为$1$,不是的话值为$-1$。相关内容可见0xGame Div 4 。
$\mathrm{lgd}(\dfrac{w}{p})$
$\mathrm{lgd}(\dfrac{w}{q})$
选取私钥$d$
$1$
$1$
$d_1$
$1$
$-1$
$d_2$
$-1$
$1$
$d_3$
$-1$
$-1$
$d_4$
然后选取点$Q$使得其横坐标为$c^3+ac+b$,然后计算$dQ$即可,其横坐标就是$m$。
为了快速地算出$|E_p|,|E_q|$,我们可以选取$p\equiv q\equiv 2 \pmod 3$且$a=0$或者$p\equiv q\equiv 3 \pmod 4$且$a=0$,那么$|E_p|=p+1,|E_q|=q+1$。
0x02 KMOV算法 1.加密 寻找两个大素数$p,q$,然后计算$n=pq$。然后确定椭圆曲线的系数$a,b$,并保证$\gcd(4a^3+27b^2,n)=1$。
然后计算$N_1,N_2=|E_p|,|E_q|$。并选择$e$使得$\gcd(e,N_1)=\gcd(e,N_2)=1$。同时计算$d$是$e$模$\dfrac{N_1N_2}{\gcd{N_1N_2}}$的逆元。
其中公钥为$n,e,a,b$,私钥为$p,q,d$。
加密时,直接计算$C=eP$的值即可。
2.解密 直接计算$dC=P$。
然而,这个加密算法有一个比较大的缺点,就是除非使用了特殊的曲线和特殊的素数,不然计算$|E_p|,|E_q|$式一件非常难的事情。因此,1991年有人对这个系统进行了改进,给出了KMOV91。
3.KMOV91 在KMOV91中,直接选取$p\equiv q\equiv 2 \pmod 3$,使得$N_1,N_2=p+1,q+1$。其中$n,e$为公钥,$p,q,d$为私钥。而使用椭圆曲线$y^2=x^3+b,b=P_y^2-P_x^3$。最后保证$P_x^3 \not \equiv P_y^2 \pmod n$即可进行加密。其他过程与原来一样。
3.近期CTF中基于椭圆曲线的攻击方法 1. ECC低指数广播攻击 NepCTF lowExpoent 0o01.分析题目 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 from Crypto.Util.number import *from secret import flagdef Encrypt (e, nbits, msg ): p, q = [getPrime(int (nbits)) for _ in range (2 )] N = p*q x = int (msg) while True : a, b = [getRandomRange(1 , N) for _ in range (2 )] P.<Yp> = PolynomialRing(Zmod(p)) fp = x^3 + a*x + b - Yp^2 P.<Yq> = PolynomialRing(Zmod(q)) fq = x^3 + a*x + b - Yq^2 try : yp, yq = int (fp.roots()[0 ][0 ]), int (fq.roots()[0 ][0 ]) y = crt([yp, yq], [p, q]) E = EllipticCurve(IntegerModRing(N), [a, b]) msg_point = E.point((x, y)) Ep = EllipticCurve(IntegerModRing(p), [a, b]) Eq = EllipticCurve(IntegerModRing(q), [a, b]) N1 = Ep.order() N2 = 2 *p+2 -N1 N3 = Eq.order() N4 = 2 *q+2 -N3 d = { ( 1 , 1 ): inverse_mod(e, lcm(N1, N3)), ( 1 ,-1 ): inverse_mod(e, lcm(N1, N4)), (-1 , 1 ): inverse_mod(e, lcm(N2, N3)), (-1 , -1 ): inverse_mod(e, lcm(N2, N4)) } break except : pass cip_point = e*msg_point ciphertext = cip_point.xy()[0 ] privKey = (d, p, q) pubKey = (a, b, N) return (ciphertext, pubKey, privKey)def Decrypt (ciphertext, pubKey, privKey ): d, p, q = privKey a, b, N = pubKey x = ciphertext w = x^3 + a*x + b % N P.<Yp> = PolynomialRing(Zmod(p)) fp = x^3 + a*x + b -Yp^2 yp = fp.roots()[0 ][0 ] P.<Yq> = PolynomialRing(Zmod(q)) fq = x^3 + a*x + b -Yq^2 yq = fq.roots()[0 ][0 ] y = crt([int (yp), int (yq)], [p, q]) E = EllipticCurve(IntegerModRing(N), [a, b]) cip_point = E.point([x, y]) legendre_symbol_p = legendre_symbol(w, p) legendre_symbol_q = legendre_symbol(w, q) msg_point = d[(legendre_symbol_p, legendre_symbol_q)]*cip_point return msg_point.xy()[0 ] msg = bytes_to_long(flag) f = open ("data" , "w" )for i in range (70 ): current_st = time() cipher = Encrypt(3 , 256 , msg) f.writelines("{}, {}, {}, {}\n" .format (cipher[0 ],*cipher[1 ])) f.close()
然后题目最终给出了$70$组不同的$(c,a,b,n)$的四元组,均为对同一明文$x$的加密。
那么这道题,我们可以首先构造关于$x$的多项式。我们刚才看到了,$F_3(x)=3x^4+6ax^2+12bx-a^2$,那么我们可以得到分子的值和分母的值 $$ G=x(3x^4+6ax^2+12bx-a^2)^2-8(x^3+ax+b)(x^6+5ax^4+20bx^3-5ax^2-4ab-8b^2-a^3) $$
$$ F^2=(3x^4+6ax^2+12bx-a)^2 $$
然后我们由$c\equiv\dfrac G {F^2} \pmod n$,那么有$G-cF^2\equiv 0 \pmod n$。由于$F^2$是八次式,$G$是九次式,因此我们可以构造出$70$个九次式(不过最好保证构造出来的$G$均为monic
,也就是最高次项九次项为$1$。
然后,我们继续类比RSA中低指数广播攻击的方法,可以分别对常数项到九次项分别进行中国剩余定理,并将所有的模数相乘,得到一个所有的系数都很大的一个多项式。(当然九次项系数还是$1$)。然后我们可以看到,这个$N=\prod_{i=1}^{70} n$,由于$n$的比特数大概是$512$,也就是$\ln n=355$,那么$\ln N=24842$,而我们需要密文的规模不超过$\ln m=355$。次数为$9$次式,因此根据Copeersmith定理:只要$\ln x<2760 $,就可以求出这个式子的一个根。很显然$m$的值是符合条件的。
因此这个过程就转化成了求多项式的小根,直接用small_roots
就可以解出来了。不过Sagemath中的这些命令也并不是特别好用的我还是调程序调了半天。因此建议通过LLL算法手写一个求多项式小根的脚本。。。。。。
0o02.自己的exp 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 c=[] a=[] b=[] n=[] Fzlist=[] Fmlist=[] Dlist=[]for i in range (70 ): R.<x>=PolynomialRing(Zmod(n[i])) Fz=x*(3 *x^4 +6 *a[i]*x^2 +12 *b[i]*x-a[i]^2 )^2 -8 *(x^3 +a[i]*x+b[i])*(x^6 +5 *a[i]*x^4 +20 *b[i]*x^3 -5 *a[i]^2 *x^2 -4 *a[i]*b[i]*x-8 *b[i]^2 -a[i]^3 ) Fm=(3 *x^4 +6 *a[i]*x^2 +12 *b[i]*x-a[i]^2 )^2 Fzlist.append(Fz) Fmlist.append(Fm*c[i]) Dlist.append(Fz-c[i]*Fm) Crtlist=[[0 for _ in range (70 )] for __ in range (10 )]for i in range (70 ): for j in range (10 ): Crtlist[j][i]=ZZ(Dlist[i][j]) A=[]for i in range (10 ): A.append(CRT(Crtlist[i],n)) N=1 for i in n: N*=i O.<y>=PolynomialRing(Zmod(N)) S=0 for i in range (10 ): S+=A[i]*y^iprint ('Finding Roots' ) oo,oooo,ooooo=1 ,2 ,3 S.small_roots(epsilon=1 /16 ) [3088969433059681806521206959873975785377227976800172674306727155831805513908352148702210247662586117242206183337522557 ]
0o03 出题人Am4的官方wp 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 from functools import reducefrom Crypto.Util.number import * f = open ("data" , "r" ) ciphertext = [] a, b, n = [], [], []for i in range (70 ): ci, ai, bi, ni = [int (num) for num in f.readline().strip().split(", " )] ciphertext.append(ci) a.append(ai) b.append(bi) n.append(ni) e = 3 deg = 9 coeffi = []for i in range (70 ): E = EllipticCurve(IntegerModRing(n[i]), [a[i], b[i]]) P.<m> = PolynomialRing(Zmod(n[i])) f = ciphertext[i]*E._multiple_x_denominator(e, m) - E._multiple_x_numerator(e, m) coeffi.append(f.coefficients(sparse=False )) large_coeffi = [crt([int (coeffi[j][i]) for j in range (70 )], [n[j] for j in range (70 )]) for i in range (deg+1 )] N_bitlength = sum ([n[i].bit_length() for i in range (70 )]) min_n = min (n) N = reduce(lambda x, y: x*y, n) Sc = large_coeffivar("x" ) assume(x, 'integer' ) f = Sc[9 ]*x^9 +Sc[8 ]*x^8 +Sc[7 ]*x^7 +Sc[6 ]*x^6 +Sc[5 ]*x^5 +Sc[4 ]*x^4 +Sc[3 ]*x^3 +Sc[2 ]*x^2 +Sc[1 ]*x+Sc[0 ] lat = [] lat.append([large_coeffi[i]*min_n**i for i in range (deg+1 )]+[1 /(deg+1 )])for i in range (deg+1 ): lat.append([((min_n**j)*N if (i==j) else 0 ) for j in range (deg+1 )]+[0 ]) Mat = matrix(lat) Mat_LLL = Mat.LLL()for lin in range (deg): Sc = [int (i) for i in Mat_LLL[lin]] Sc = [(Sc[i]//(min_n**i)) for i in range (deg+1 )] var("x" ) assume(x, 'integer' ) f = Sc[9 ]*x^9 +Sc[8 ]*x^8 +Sc[7 ]*x^7 +Sc[6 ]*x^6 +Sc[5 ]*x^5 +Sc[4 ]*x^4 +Sc[3 ]*x^3 +Sc[2 ]*x^2 +Sc[1 ]*x+Sc[0 ]print (factor(f))break ''' m = 3088969433059681806521206959873975785377227976800172674306727155831805513908352 148702210247662586117242206183337522557 print(long_to_bytes(m)) '''
2.ECC上的相关明文攻击 0o01 分析题目 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 from Crypto.Util.number import bytes_to_long, long_to_bytes, getPrime, getRandomRangefrom secret import flag flag = bytes_to_long(flag) p, q = [getPrime(int (256 )) for _ in range (2 )] a, b = [getRandomRange(1 , p*q) for _ in range (2 )]class Task (): def __init__ (self, a, b, p, q, e ): self .p, self .q = p, q self .a, self .b = a, b self .N = self .p*self .q self .e = e self .Kbits = 8 Ep = EllipticCurve(IntegerModRing(self .p), [self .a, self .b]) Eq = EllipticCurve(IntegerModRing(self .q), [self .a, self .b]) N1 = Ep.order() N2 = 2 *self .p+2 -N1 N3 = Eq.order() N4 = 2 *self .q+2 -N3 self .d = { ( 1 , 1 ): inverse_mod(self .e, lcm(N1, N3)), ( 1 ,-1 ): inverse_mod(self .e, lcm(N1, N4)), (-1 , 1 ): inverse_mod(self .e, lcm(N2, N3)), (-1 , -1 ): inverse_mod(self .e, lcm(N2, N4)) } self .E = EllipticCurve(IntegerModRing(self .N), [self .a, self .b]) def Enc (self, plaintext ): try : msg_point = self .msg_to_point(plaintext) cip_point = self .e*msg_point return cip_point.xy()[0 ] except : return None def Dec (self, ciphertext ): x = ciphertext w = x^3 + self .a*x + self .b % self .N P.<Yp> = PolynomialRing(Zmod(self .p)) fp = x^3 + self .a*x + self .b -Yp^2 yp = fp.roots()[0 ][0 ] P.<Yq> = PolynomialRing(Zmod(self .q)) fq = x^3 + self .a*x + self .b -Yq^2 yq = fq.roots()[0 ][0 ] y = crt([int (yp), int (yq)], [self .p, self .q]) cip_point = self .E.point([x, y]) legendre_symbol_p = legendre_symbol(w, self .p) legendre_symbol_q = legendre_symbol(w, self .q) msg_point = self .d[(legendre_symbol_p, legendre_symbol_q)]*cip_point return msg_point.xy()[0 ] >> self .Kbits def msg_to_point (self, x, shift=False ): if shift: x <<= self .Kbits checkPoint = None for i in range (2 <<self .Kbits): P.<Yp> = PolynomialRing(Zmod(self .p)) fp = x^3 + self .a*x + self .b - Yp^2 P.<Yq> = PolynomialRing(Zmod(self .q)) fq = x^3 + self .a*x + self .b - Yq^2 try : yp, yq = int (fp.roots()[0 ][0 ]), int (fq.roots()[0 ][0 ]) y = crt([yp, yq], [self .p, self .q]) E = EllipticCurve(IntegerModRing(self .p*self .q), [self .a, self .b]) checkPoint = E.point((x, y)) break except : x += 1 return checkPoint delta = 28552609273 e = 137 cip = Task(a, b, p, q, e)print (f"a = {a} " )print (f"b = {b} " )print (f"n = {p*q} " ) plaintext1 = cip.msg_to_point(flag, shift=True ).xy()[0 ] ciphertext1 = cip.Enc(plaintext1)print (f"ciphertext1 = {ciphertext1} " ) plaintext2 = cip.msg_to_point(flag+delta, shift=True ).xy()[0 ] ciphertext2 = cip.Enc(plaintext2)print (f"ciphertext2 = {ciphertext2} " ) delta = plaintext2 - plaintext1print (f"delta = {delta} " )
很显然,这道题给出了$m$的明文和$m+d$的明文,加密指数为$137$,那么我们就可以用我们刚才提到的函数构造出多项式,然后求多项式的GCD来确定最后的根。
不过整个代码的实现还是挺困难的,因为里面的微操很多,比如要把求出来的多项式先转成list再转回多项式。不然程序运行会出现错误。。
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 a = 4281014323581546488462714122303747203636223358897123235803046898862939653328802115362584316327572195541081125920528501180620492421895128401613948866529122 b = 1504110610934153564757355169781343270879282969971532470271782059859117769089994716068562704547770368420258743734175281689611986131092394954948339191589449 n = 6638798722521613809421411597209101115203859862340555482590990067056543831415553727351714220257486793657912537305448979625073630917241320204281256125412671 c1= 6327639450575093157999054915625304951894564605402541939450801256931875815282143921161475586010526883609974743159835451980804875847625527741681757415519394 c2= 3275348139763310265438126795688591830796510682708632201044899744259822398076574133105844638686347122066389056025294466297206704146167073441486603569471235 d = 7309467973885 E=EllipticCurve(Zmod(n),[a,b]) Fz=E._multiple_x_numerator(137 ) Fm=E._multiple_x_denominator(137 )print ('[+] Proof Work Finished' ) Fmlist=list (Fm) Fzlist=list (Fz) R.<t>=PolynomialRing(Zmod(n)) Fm1,Fm2,Fz1,Fz2=0 ,0 ,0 ,0 for i in range (len (Fmlist)): Fm1+=Fmlist[i]*(t^i) Fm2+=Fmlist[i]*((t+d)^i)for i in range (len (Fzlist)): Fz1+=Fzlist[i]*(t^i) Fz2+=Fzlist[i]*((t+d)^i) G1,G2=c1*Fm1-Fz1,c2*Fm2-Fz2def gcdpro (g1, g2 ): while g2: g1, g2 = g2, g1 % g2 return g1.monic() H=(gcdpro(G1,G2))print (H)from Crypto.Util.number import *print (long_to_bytes(n-H[0 ]))
当然,ECC中也可以通过使用共模攻击和RSA中的Coppersmith来出题。可以预测的是,到目前为止,CTF中ECC的题目的数量并不是特别多。这就。】说明了ECC有可能会成为后面CTF的一个出题热门点。