由于要求最大公因子为正,所以 gcd(a, b) = gcd(a, -b) = gcd(-a, b) = gcd(-a, -b)。一般gcd(a, b) = gcd(|a|, |b|)。由任一非 0 整数能整除0,可得 gcd(a, 0) = |a|。如果将 a, b 都表示为素数的乘积,则 gcd(a, b) 极易确定。
# https://github.com/mimoo/RSA-and-LLL-attacks/blob/master/coppersmith.sage defcoppersmith_howgrave_univariate(pol, N, beta, mm, tt, XX): """ Coppersmith revisited by Howgrave-Graham finds a solution if: * b|N, b >= N^beta , 0 < beta <= 1 * |x| < XX """ # # init # dd = pol.degree() nn = dd * mm + tt
# # checks # ifnot0 < beta <= 1 : raise ValueError("beta should belongs in (0, 1]")
ifnot pol.is_monic(): raise ArithmeticError("Polynomial must be monic.")
# # Coppersmith revisited algo for univariate #
# change ring of pol and x polZ = pol.change_ring(ZZ) x = polZ.parent().gen()
# compute polynomials gg = [] for ii in range(mm): for jj in range(dd): gg.append((x * XX)**jj * N**(mm - ii) * polZ(x * XX)**ii) for ii in range(tt): gg.append((x * XX)**ii * polZ(x * XX)**mm)
# construct lattice B BB = Matrix(ZZ, nn)
for ii in range(nn): for jj in range(ii+1): BB[ii, jj] = gg[ii][jj]
print ("[+] Found factors of N:") print ("[+] p =" , factor1) print ("[+] q =" , factor2)
公钥指数e相关攻击
e很小且明文m比较小
以 e = 3 为例
m 较小的情况下,从小到大枚举 k,依次开 e 次方根,直到开出整数为止。
python
1 2 3 4 5 6 7 8
while1: res = gmpy2.iroot(c + i * n, 3) if(res[1] == True): m = res[0] print(binascii.unhexlify(hex(m)[2:].strip("L"))) break print"i=" + str(i) i = i + 1
e与模数N的一个或多个素数因子的欧拉函数不互素
N 可以直接分解,但由于 e 与一个或多个素数因子的欧拉函数不互素,而 e d - k phi(N) = 1 有解的充要条件是 gcd(e, phi(N)) = 1,因此无法直接通过扩展欧几里德算法求模反元素 d。
e=2,Rabin加密解密算法
破解 RSA 的关键即在于大整数的分解,只要 n 被成功分解,就能够破译。而 Rabin 密码体制是对 RSA 的一种修正。
c = p = q = n = p*q c_p = pow(c,(p+1)/4,p) c_q = pow(c,(q+1)/4,q) a = gmpy2.invert(p,q) b = gmpy2.invert(q,p) x = (b*q*c_p+a*p*c_q)%n y = (b*q*c_p-a*p*c_q)%n
dq1 = gmpy2.invert(e1/2, q1 - 1) dq2 = gmpy2.invert(e2/2, q2 - 1) cq1 = gmpy2.powmod(c1, dq1, q1) cq2 = gmpy2.powmod(c2, dq2, q2) m2 = libnum.solve_crt([cq1, cq2], [q1, q2]) m = gmpy2.iroot(m2, 2) if m[1]: # if not True, need to use Rabin to cal m print libnum.n2s(m[0])
e=3,模数N相同,且明文存在线性关系m1=a*m2+b mod N
python
1 2 3 4 5 6 7 8 9 10 11 12 13
defgetM2(a, b, c1, c2, n): a3 = pow(a,3,n) b3 = pow(b,3,n) first = c1 - a3 * c2 + 2 * b3 first = first % n second = 3 * b * (a3 * c2 - b3) second = second % n third = second * gmpy2.invert(first, n) third = third % n fourth = (third + b) * gmpy2.invert(a,n) return fourth % n m = getM2(a, b, c1, c2, n) - padding2 print libnum.n2s(m)
e 不为 3 时,利用 Coppersmith’s short-pad attack,即 padding 过短引起的攻击:
defcompositeModulusGCD(a, b): if(b == 0): return a.monic() else: return compositeModulusGCD(b, a % b)
defCoppersmithShortPadAttack(e,n,C1,C2,eps=1/30): import binascii P.<x,y> = PolynomialRing(ZZ) ZmodN = Zmod(n) g1 = x^e - C1 g2 = (x+y)^e - C2 res = g1.resultant(g2) P.<y> = PolynomialRing(ZmodN) rres = 0 for i in range(len(res.coefficients())): rres += res.coefficients()[i]*(y^(res.exponents()[i][1]))
diff = rres.small_roots(epsilon=eps) recoveredM1 = franklinReiter(n,e,diff[0],C1,C2) print(recoveredM1) print("Message is the following hex, but potentially missing some zeroes in the binary from the right end") print(hex(recoveredM1)) print("Message is one of:") for i in range(8): msg = hex(Integer(recoveredM1*pow(2,i))) if(len(msg)%2 == 1): msg = '0' + msg if(msg[:2]=='0x'): msg = msg[:2] print(binascii.unhexlify(msg))
deftestCoppersmithShortPadAttack(eps=1/25): from Crypto.PublicKey import RSA import random import math import binascii M = "flag{This_Msg_Is_2_1337}" M = int(binascii.hexlify(M),16) e = 3 nBitSize = 8192 key = RSA.generate(nBitSize) m = int(math.floor(nBitSize/(e*e))) - 400 assert (m < nBitSize - len(bin(M)[2:])) r1 = random.randint(1,pow(2,m)) r2 = random.randint(r1,pow(2,m)) M1 = pow(2,m)*M + r1 M2 = pow(2,m)*M + r2 C1 = Integer(pow(M1,e,key.n)) C2 = Integer(pow(M2,e,key.n)) CoppersmithShortPadAttack(e,key.n,C1,C2,eps)
deftestFranklinReiter(): p = random_prime(2^512) q = random_prime(2^512) n = p * q e = 11 m = randint(0, n) r = randint(0, n) c1 = pow(m + 0, e, n) c2 = pow(m + r, e, n) print(m) recoveredM = franklinReiter(n,e,r,c1,c2) print(recoveredM) assert recoveredM==m print("They are equal!") returnTrue
from __future__ import print_function import libnum
defcontinued_fractions_expansion(numerator,denominator):#(e,N) result=[] divident = numerator % denominator quotient = numerator / denominator result.append(quotient) while divident != 0: numerator = numerator - quotient * denominator tmp = denominator denominator = numerator numerator = tmp divident = numerator % denominator quotient = numerator / denominator result.append(quotient) return result defconvergents(expansion): convergents=[(expansion[0], 1)] for i in range(1, len(expansion)): numerator = 1 denominator = expansion[i] for j in range(i - 1, -1, -1): numerator += expansion[j] * denominator if j==0: break tmp = denominator denominator = numerator numerator = tmp convergents.append((numerator, denominator)) #(k,d) return convergents defnewtonSqrt(n): approx = n / 2 better = (approx + n / approx) / 2 while better != approx: approx = better better = (approx + n / approx) / 2 return approx defwiener_attack(cons, e, N): for cs in cons: k,d = cs if k == 0: continue phi_N = (e * d - 1) / k a = 1 b = -((N - phi_N) + 1) c = N delta = b * b - 4 * a * c if delta <= 0: continue x1 = (newtonSqrt(delta) - b)/(2 * a) x2 = -(newtonSqrt(delta) + b)/(2 * a) if x1 * x2 == N: return [x1, x2, k, d]
if __name__ == "__main__": n = e = c = expansion = continued_fractions_expansion(e, n) cons = convergents(expansion) p, q, k, d = wiener_attack(cons, e, n) m = pow(c, d, n) print(libnum.n2s(m))
以 e = 3 为例,3组不同的模数 N1,N2,N3 互素(如果不互素,可以通过欧几里德算法求最大公约数直接得到 p),则有:
C1 = me mod n1
C2 = me mod n2
C3 = me mod n3
python
1 2 3 4 5 6 7 8 9 10 11
import binascii,gmpy2
defCRT(mi, ai): assert(reduce(gmpy2.gcd,mi)==1) assert (isinstance(mi, list) and isinstance(ai, list)) M = reduce(lambda x, y: x * y, mi) ai_ti_Mi = [a * (M / m) * gmpy2.invert(M / m, m) for (m, a) in zip(mi, ai)] return reduce(lambda x, y: x + y, ai_ti_Mi) % M e=0x3 m=gmpy2.iroot(CRT(n, c), e)[0]
e非素数
e 非素数,e 和 phi 不互素
Code
1 2 3 4 5 6
e = 12 p = 58380004430307803367806996460773123603790305789098384488952056206615768274527 q = 81859526975720060649380098193671612801200505029127076539457680155487669622867 ciphertext = 206087215323690202467878926681944491769659156726458690815919286163630886447291570510196171585626143608988384615185921752409380788006476576337410136447460
算出的m转化成字符串
exp:
python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
import gmpy2 import libnum from Crypto.Util.number import *
e = 12 p = 58380004430307803367806996460773123603790305789098384488952056206615768274527 q = 81859526975720060649380098193671612801200505029127076539457680155487669622867 n=p*q c = 206087215323690202467878926681944491769659156726458690815919286163630886447291570510196171585626143608988384615185921752409380788006476576337410136447460 phi = (p-1)*(q-1) d4 = libnum.invmod(3,phi) m4 = pow(c,d4,n) print m4 m = gmpy2.iroot(m4,4)[0] print(m) print(long_to_bytes(m))
e = 0x3 b=0x666c6167206973203a746573743132313131313131313131313133343536000000000000000000 n = 0xf85539597ee444f3fcad07142ecf6eaae5320301244a7cedc50b2beed7e60ffa11ccf28c1a590fb81346fb16b0cecd046a1f63f0bf93185c109b8c93068ec02f c=0xa75c3c8a19ed9c911d851917e442a8e7b425e4b7f92205ca532a2ab0f5abe6cb86d164cc61374877f9e88e7bca606b43c79f1d59deadfcc68c3db52e5fc42f0 kbits=72#未知低位位数 PR.<x> = PolynomialRing(Zmod(n)) f = (x + b) ^ e - c x0 = f.small_roots(X=2^kbits, beta=1)[0] print"x: %s" %hex(int(x0))
defpartial_p(p0, kbits, n): PR.<x> = PolynomialRing(Zmod(n)) nbits = n.nbits() f = 2^kbits*x + p0 f = f.monic() roots = f.small_roots(X=2^(nbits//2-kbits), beta=0.3) # find root < 2^(nbits//2-kbits) with factor >= n^0.3 if roots: x0 = roots[0] p = gcd(2^kbits*x0 + p0, n) return ZZ(p)
deffind_p(d0, kbits, e, n): X = var('X')
for k in xrange(1, e+1): results = solve_mod([e*d0*X - k*X*(n-X+1) + k*n == X], 2^kbits) for x in results: p0 = ZZ(x[0]) p = partial_p(p0, kbits, n) if p: return p
if __name__ == '__main__': n =0x56a8f8cbc72ff68e67c72718bd16d7e98150aea08780f6c4f532d20ca3c92a0fb07c959e008cbcbeac744854bc4203eb9b2996e9cf630133bc38952a2c17c27d e = 0x3 d = 0x594b6c9631c4987f588399f22466b51fc48ed449b8aae0309b5736ef0b741893 beta = 0.5 epsilon = beta^2/7
该后门算法依赖于Coppersmith partial information attack算法, sage实现该算法。
python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
p = 0xd7e990dec6585656512c841ac932edaf048184bac5ebf9967000000000000000 n = 0xb50193dc86a450971312d72cc8794a1d3f4977bcd1584a20c31350ac70365644074c0fb50b090f38d39beb366babd784d6555d6de3be54dad3e87a93a703abdd
kbits = 60 PR.<x> = PolynomialRing(Zmod(n)) f = x + p x0 = f.small_roots(X=2^kbits, beta=0.4)[0] print"x: %s" %hex(int(x0)) p = p+x0 print"p: ", hex(int(p)) assert n % p == 0 q = n/int(p) print"q: ", hex(int(q)) #其中kbit是未知的p的低位位数 #x0为求出来的p低位
defgetd(n,e,dp): for i in range(1,e): if (dp*e-1)%i == 0: if n%(((dp*e-1)/i)+1)==0: p=((dp*e-1)/i)+1 phi = (p-1)*(q-1) d = gmpy2.invert(e,phi)%phi return d
import gmpy2 n = m = defquadratic(a,b,c): ga=gmpy2.mpz(a) gb=gmpy2.mpz(b) gc=gmpy2.mpz(c) delta=gb**2-4*ga*gc if delta<=0or a==0: return0 tmp=gmpy2.iroot(delta,2) if tmp[1]==True: x1 = (-gb + tmp[0]) / (2*ga) x2 = (-gb - tmp[0]) / (2 * ga) if x1>0and n%x1==0: print x1 return x1 if x2>0and n%x2==0: print x2 return x2 return0 defmain(): for x in range(1500): print x for y in range(1500): a=y b=x * y - m + n c=x * n if quadratic(a,b,c)!=0: return0 main()
LLL算法
通过三组 e 和 N(d 相同),我们可以将三组 e 和 N 进行基础矩阵的构造,使用 lattice 还原技术中的 LLL 可以求得该矩阵的最小基向量 b。
详细可见格密码。
实现参考论文:Factoring Polynomials with Rational Coefficients
def AMM(o, r, q): start = time.time() print('\n----------------------------------------------------------------------------------') print('Start to run Adleman-Manders-Miller Root Extraction Method') print('Try to find one {:#x}th root of {} modulo {}'.format(r, o, q)) g = GF(q) o = g(o) p = g(random.randint(1, q)) while p ^ ((q-1) // r) == 1: p = g(random.randint(1, q)) print('[+] Find p:{}'.format(p)) t = 0 s = q - 1 while s % r == 0: t += 1 s = s // r print('[+] Find s:{}, t:{}'.format(s, t)) k = 1 while (k * s + 1) % r != 0: k += 1 alp = (k * s + 1) // r print('[+] Find alp:{}'.format(alp)) a = p ^ (r**(t-1) * s) b = o ^ (r*alp - 1) c = p ^ s h = 1 for i in range(1, t): d = b ^ (r^(t-1-i)) if d == 1: j = 0 else: print('[+] Calculating DLP...') j = - dicreat_log(a, d) print('[+] Finish DLP...') b = b * (c^r)^j h = h * c^j c = c ^ r result = o^alp * h end = time.time() print("Finished in {} seconds.".format(end - start)) print('Find one solution: {}'.format(result)) return result
def findAllPRoot(p, e): print("Start to find all the Primitive {:#x}th root of 1 modulo {}.".format(e, p)) start = time.time() proot = set() while len(proot) < e: proot.add(pow(random.randint(2, p-1), (p-1)//e, p)) end = time.time() print("Finished in {} seconds.".format(end - start)) return proot
def findAllSolutions(mp, proot, cp, p): print("Start to find all the {:#x}th root of {} modulo {}.".format(e, cp, p)) start = time.time() all_mp = set() for root in proot: mp2 = mp * root % p assert(pow(mp2, e, p) == cp) all_mp.add(mp2) end = time.time() print("Finished in {} seconds.".format(end - start)) return all_mp
c = p = q = e = cp = c % p cq = c % q mp = AMM(cp, e, p) mq = AMM(cq, e, q) p_proot = findAllPRoot(p, e) q_proot = findAllPRoot(q, e) mps = findAllSolutions(mp, p_proot, cp, p) mqs = findAllSolutions(mq, q_proot, cq, q) print mps, mqs
def check(m): h = m.hex() if len(h) & 1: return False if h.decode('hex').startswith('NCTF'): print(h.decode('hex')) return True else: return False
start = time.time() print('Start CRT...') for mpp in mps: for mqq in mqs: solution = CRT_list([int(mpp), int(mqq)], [p, q]) if check(solution): print(solution) print(time.time() - start)
end = time.time() print("Finished in {} seconds.".format(end - start))