只解出一个题,签到。。。。赛后复现,学习新知识。

img

收到,义父(bushi

ECC学了好久,拖了好久。。。我真该死啊

CF is Crypto Faker

学过AI的都知道,这题不是“纯密码”,密码假面人要披上AI的战衣,但他永远不会卸下密码的假面。
https://pan.baidu.com/s/1nOe2NlWdCVfES6I1U5RzPg
提取码:GAME

附件给了一大堆,有用的没几句.(老奶奶的裹脚布)

Please firstly pay attention to the file named as "task.py".
The real flag is a little strange.
However, there is no need to be messy in your mind just because of the "appearance" of the flag.
Just be self-confident!
from Crypto.Util.number import *
import initialize
import train
import valid
import test
import rec
from secret import message, flag_point

flag = b"flag{" + long_to_bytes(message) + long_to_bytes(flag_point) + b".}"

p = getPrime(512)
q = getPrime(512)
n = p * q
print("The significant parameter n: %s" % hex(n))
phi0 = (p - 1) * (q - 1)
r = rec.rec(p, q)
print("The unique parameter r: %s" % hex(r))

parameters = initialize.initialize(p, q)
wild_phi = parameters[0]
wild_e = parameters[1]
print("------")

print("Parameters are initialized to: \n phi:%s\n" % hex(wild_phi), " e:%s" % hex(wild_e))
print("But they are wild and crazy!")
print("We have to give them a lesson!")
print("------")

parameters = train.train(wild_phi, wild_e, n, r, phi0)
trained_phi = parameters[0]
trained_e = parameters[1]
print("Parameters are trained to: \n phi:%s\n" % hex(trained_phi), " e:%s" % hex(trained_e))
print("After training, the two naughty parameters are more and more normal.")
print("It's closer to your target!")
print("------")

parameters = valid.valid(trained_phi, trained_e, n)
y_valid = parameters[0]
print("The encrypted output in validation set is %s" % hex(y_valid))
print("After validation, the model is more and more stable.")
print("To test the real flag!")
print("------")

parameters = test.test(trained_phi, trained_e, n)
y_hat_cipher1 = parameters[0]
y_hat_cipher2 = parameters[1]
print("The final output is \n%s" % hex(y_hat_cipher1), "\n%s" % hex(y_hat_cipher2))
print("------")

看起来真的很吓人,但是仔细看看好像啥都没变。。该知道的都给了,直接梭哈。

import gmpy2
from Crypto.Util.number import *

phi = 0x81c5f040bfaea676120cd62c36ba7afb303561504bbf8609afa3da60fb6202ca875b0bd2a06143ebcd16fa615557ff159d97909160d68e1938b3ecaf57709b3bb712fdcba325655f111918472d4353a66854ccda50b63a1047278c15a4b39cde898d054db87092958c7c05f8fa566dcd969b1ff4b7d1935c375a4af3bfc341b0
e = 0x2c22193ad9abcca2f67552fc76dd07b3ef883f3d755c95119cdf82bb6a07c970fd37e582bb49250d8efaa29b8a59c82059165c654206a9d7261f6b45a90dc69
c1 = 0x29289e3d9275147b885b5061637564cbee3e4d9f48e52694e594f020e49da9b24d9246b2437fb2221fa86ca1a277f3fdd7ab5cad4738a02b66d47703ef816844a84c6c209c8251e8961c9ba2c791649e022627f86932d9700c3b1dc086e8b2747d0a5604955387a935464d3866dd4100b2f3d57603c728761d1d8ef7fdbdcbee
c2 = 0x2b0059f88454e0e36269c809b5d5b6b28e5bab3c87b20f9e55635239331100a0a582241e7a385034698b61ebf24b519e868617ff67974cc907cc61be38755737f9a6dbeb7890ff55550b1af1ecf635112fcaaa8b07a3972b3c6728cbcf2a3973a4d7bd92affec7e065e0ae83cd36858e6d983785a3668a8b82709d78a69796af
n = 0x81c5f040bfaea676120cd62c36ba7afb303561504bbf8609afa3da60fb6202ca875b0bd2a06143ebcd16fa615557ff159d97909160d68e1938b3ecaf57709b3d2698476b6dd203811b6a2ec6a6e2a7e213ab719bcd3ab49bb864b10e9c78ea3f501c0e2213dfe431043bb6f0cc2e8d77bfb43869b843af1a99ae81b87811e101

d = gmpy2.invert(e,phi)
m1 = pow(c1,d,n)
m2 = pow(c2,d,n)
print(long_to_bytes(m1))
print(long_to_bytes(m2))
flag = b"flag{" + long_to_bytes(m1) + long_to_bytes(m2) + b".}"
print(flag)
#b"flag{With the method of machine learning, it is available for Crypto-er to develop the modern cryptography.Don't give up learning crypto.}"

第一次看的时候以为它是一个很难很难的题,我就弃了,后来发现好多解啊,试试?。。

not_wiener

比赛期间这个题目做了一点,但是在求a卡住了,boneh_durfee试遍了delta,就是求不出d(wo shi dai bi

https://pan.baidu.com/s/1iPaUBGJ2hvIYruvo-1FV_Q
提取码:GAME

以下为附件内容

from Crypto.Util.number import *
from gmpy2 import *
import random, os
from hashlib import sha1
from random import randrange
flag=b''
x = bytes_to_long(flag)

def gen_key():
while True:
q = getPrime(160)
p = 2 * getPrime(1024-160) * q+1
if isPrime(p):
break
h = random.randint(1, p - 1)
g = powmod(h,(p-1)//q, p)
y=pow(g,x,p)
return p,q,g,y
def cry():
a =
p = getPrime(512)
q = getPrime(512)
d = getPrime(280)
n = p * q
e = inverse(d, (p - 1) * (q - 1))
c = pow(a, e, n)
return n,e,c

p,q,g,y=gen_key()
k1 = random.randint(1, q-1)
h1 = bytes_to_long(sha1(os.urandom(20)).digest())
r1 = pow(g, k1, p) % q
s1 = ((h1 + x*r1) * invert(k1, q))% q

n,e,c= cry()

a=
b= 17474742587088593627
k2 = a*k1 + b
h2 = bytes_to_long(sha1(os.urandom(20)).digest())
r2 = pow(g, k2, p) % q
s2 = ((h2 + x*r2) * invert(k2, q)) % q
print(n,e,c)
print(p,q,g,y)
print("h1:%s r1:%s s1:%s"%(h1,r1,s1))
print("h2:%s r2:%s s2:%s"%(h2,r2,s2))
n = 98871082998654651904594468693622517613869880791884929588100914778964766348914919202255397776583412976785216592924335179128220634848871563960167726280836726035489482233158897362166942091133366827965811201438682117312550600943385153640907629347663140487841016782054145413246763816202055243693289693996466579973
e = 76794907644383980853714814867502708655721653834095293468287239735547303515225813724998992623067007382800348003887194379223500764768679311862929538017193078946067634221782978912767213553254272722105803768005680182504500278005295062173004098796746439445343896868825218704046110925243884449608326413259156482881
c = 13847199761503953970544410090850216804358289955503229676987212195445226107828814170983735135692611175621170777484117542057117607579344112008580933900051471041224296342157618857321522682033260246480258856376097987259016643294843196752685340912823459403703609796624411954082410762846356541101561523204985391564

p= 161310487790785086482919800040790794252181955976860261806376528825054571226885460699399582301663712128659872558133023114896223014064381772944582265101778076462675402208451386747128794418362648706087358197370036248544508513485401475977401111270352593919906650855268709958151310928767086591887892397722958234379
q= 1115861146902610160756777713087325311747309309771
g= 61073566757714587321114447684333928353300944355112378054603585955730395524359123615359185275743626350773632555967063692889668342544616165017003197599818881844811647270423070958521148291118914198811187731689123176313367399492561288350530256722898205674043032421874788802819858438796795768177550638273020791962
y= 23678147495254433946472657196764372220306841739888385605070426528738230369489739339976134564575544246606937803367113623097260181789372915552172469427842482448570540429192377881186772226796452797182435452490307834205012154495575570994963829345053331967442452842152258650027916313982835119514473311305158299360
(h1, r1, s1) = 535874494834828755542711401117152397489711233142, 117859946800380767356190121030392492081340616512, 26966646740134065096660259687229179143947213779
(h2, r2, s2) = 236574518096866758760287021848258048065293279716, 863199000523521111517835459866422731857447792677, 517924607931342012033031470185302567344725962419

看过题目后,发现a的值被删掉了,后半部分加密时dsa数字签名,第一部分则是RSA加密结果,而在这个RSA加密中由于d较小,所以可以用boneh and durfee攻击得到d

boneh and durfee脚本:RSA-and-LLL-attacks/boneh_durfee.sage at master · mimoo/RSA-and-LLL-attacks (github.com)

#sage
from __future__ import print_function
import time

############################################
# Config
##########################################

"""
Setting debug to true will display more informations
about the lattice, the bounds, the vectors...
"""
debug = True

"""
Setting strict to true will stop the algorithm (and
return (-1, -1)) if we don't have a correct
upperbound on the determinant. Note that this
doesn't necesseraly mean that no solutions
will be found since the theoretical upperbound is
usualy far away from actual results. That is why
you should probably use `strict = False`
"""
strict = False

"""
This is experimental, but has provided remarkable results
so far. It tries to reduce the lattice as much as it can
while keeping its efficiency. I see no reason not to use
this option, but if things don't work, you should try
disabling it
"""
helpful_only = True
dimension_min = 7 # stop removing if lattice reaches that dimension

############################################
# Functions
##########################################

# display stats on helpful vectors
def helpful_vectors(BB, modulus):
nothelpful = 0
for ii in range(BB.dimensions()[0]):
if BB[ii,ii] >= modulus:
nothelpful += 1

print(nothelpful, "/", BB.dimensions()[0], " vectors are not helpful")

# display matrix picture with 0 and X
def matrix_overview(BB, bound):
for ii in range(BB.dimensions()[0]):
a = ('%02d ' % ii)
for jj in range(BB.dimensions()[1]):
a += '0' if BB[ii,jj] == 0 else 'X'
if BB.dimensions()[0] < 60:
a += ' '
if BB[ii, ii] >= bound:
a += '~'
print(a)

# tries to remove unhelpful vectors
# we start at current = n-1 (last vector)
def remove_unhelpful(BB, monomials, bound, current):
# end of our recursive function
if current == -1 or BB.dimensions()[0] <= dimension_min:
return BB

# we start by checking from the end
for ii in range(current, -1, -1):
# if it is unhelpful:
if BB[ii, ii] >= bound:
affected_vectors = 0
affected_vector_index = 0
# let's check if it affects other vectors
for jj in range(ii + 1, BB.dimensions()[0]):
# if another vector is affected:
# we increase the count
if BB[jj, ii] != 0:
affected_vectors += 1
affected_vector_index = jj

# level:0
# if no other vectors end up affected
# we remove it
if affected_vectors == 0:
print("* removing unhelpful vector", ii)
BB = BB.delete_columns([ii])
BB = BB.delete_rows([ii])
monomials.pop(ii)
BB = remove_unhelpful(BB, monomials, bound, ii-1)
return BB

# level:1
# if just one was affected we check
# if it is affecting someone else
elif affected_vectors == 1:
affected_deeper = True
for kk in range(affected_vector_index + 1, BB.dimensions()[0]):
# if it is affecting even one vector
# we give up on this one
if BB[kk, affected_vector_index] != 0:
affected_deeper = False
# remove both it if no other vector was affected and
# this helpful vector is not helpful enough
# compared to our unhelpful one
if affected_deeper and abs(bound - BB[affected_vector_index, affected_vector_index]) < abs(bound - BB[ii, ii]):
print("* removing unhelpful vectors", ii, "and", affected_vector_index)
BB = BB.delete_columns([affected_vector_index, ii])
BB = BB.delete_rows([affected_vector_index, ii])
monomials.pop(affected_vector_index)
monomials.pop(ii)
BB = remove_unhelpful(BB, monomials, bound, ii-1)
return BB
# nothing happened
return BB

"""
Returns:
* 0,0 if it fails
* -1,-1 if `strict=true`, and determinant doesn't bound
* x0,y0 the solutions of `pol`
"""
def boneh_durfee(pol, modulus, mm, tt, XX, YY):
"""
Boneh and Durfee revisited by Herrmann and May

finds a solution if:
* d < N^delta
* |x| < e^delta
* |y| < e^0.5
whenever delta < 1 - sqrt(2)/2 ~ 0.292
"""

# substitution (Herrman and May)
PR.<u, x, y> = PolynomialRing(ZZ)
Q = PR.quotient(x*y + 1 - u) # u = xy + 1
polZ = Q(pol).lift()

UU = XX*YY + 1

# x-shifts
gg = []
for kk in range(mm + 1):
for ii in range(mm - kk + 1):
xshift = x^ii * modulus^(mm - kk) * polZ(u, x, y)^kk
gg.append(xshift)
gg.sort()

# x-shifts list of monomials
monomials = []
for polynomial in gg:
for monomial in polynomial.monomials():
if monomial not in monomials:
monomials.append(monomial)
monomials.sort()

# y-shifts (selected by Herrman and May)
for jj in range(1, tt + 1):
for kk in range(floor(mm/tt) * jj, mm + 1):
yshift = y^jj * polZ(u, x, y)^kk * modulus^(mm - kk)
yshift = Q(yshift).lift()
gg.append(yshift) # substitution

# y-shifts list of monomials
for jj in range(1, tt + 1):
for kk in range(floor(mm/tt) * jj, mm + 1):
monomials.append(u^kk * y^jj)

# construct lattice B
nn = len(monomials)
BB = Matrix(ZZ, nn)
for ii in range(nn):
BB[ii, 0] = gg[ii](0, 0, 0)
for jj in range(1, ii + 1):
if monomials[jj] in gg[ii].monomials():
BB[ii, jj] = gg[ii].monomial_coefficient(monomials[jj]) * monomials[jj](UU,XX,YY)

# Prototype to reduce the lattice
if helpful_only:
# automatically remove
BB = remove_unhelpful(BB, monomials, modulus^mm, nn-1)
# reset dimension
nn = BB.dimensions()[0]
if nn == 0:
print("failure")
return 0,0

# check if vectors are helpful
if debug:
helpful_vectors(BB, modulus^mm)

# check if determinant is correctly bounded
det = BB.det()
bound = modulus^(mm*nn)
if det >= bound:
print("We do not have det < bound. Solutions might not be found.")
print("Try with highers m and t.")
if debug:
diff = (log(det) - log(bound)) / log(2)
print("size det(L) - size e^(m*n) = ", floor(diff))
if strict:
return -1, -1
else:
print("det(L) < e^(m*n) (good! If a solution exists < N^delta, it will be found)")

# display the lattice basis
if debug:
matrix_overview(BB, modulus^mm)

# LLL
if debug:
print("optimizing basis of the lattice via LLL, this can take a long time")

BB = BB.LLL()

if debug:
print("LLL is done!")

# transform vector i & j -> polynomials 1 & 2
if debug:
print("looking for independent vectors in the lattice")
found_polynomials = False

for pol1_idx in range(nn - 1):
for pol2_idx in range(pol1_idx + 1, nn):
# for i and j, create the two polynomials
PR.<w,z> = PolynomialRing(ZZ)
pol1 = pol2 = 0
for jj in range(nn):
pol1 += monomials[jj](w*z+1,w,z) * BB[pol1_idx, jj] / monomials[jj](UU,XX,YY)
pol2 += monomials[jj](w*z+1,w,z) * BB[pol2_idx, jj] / monomials[jj](UU,XX,YY)

# resultant
PR.<q> = PolynomialRing(ZZ)
rr = pol1.resultant(pol2)

# are these good polynomials?
if rr.is_zero() or rr.monomials() == [1]:
continue
else:
print("found them, using vectors", pol1_idx, "and", pol2_idx)
found_polynomials = True
break
if found_polynomials:
break

if not found_polynomials:
print("no independant vectors could be found. This should very rarely happen...")
return 0, 0

rr = rr(q, q)

# solutions
soly = rr.roots()

if len(soly) == 0:
print("Your prediction (delta) is too small")
return 0, 0

soly = soly[0][0]
ss = pol1(q, soly)
solx = ss.roots()[0][0]

#
return solx, soly

def example():
############################################
# How To Use This Script
##########################################

#
# The problem to solve (edit the following values)
#

# the modulus
N = 98871082998654651904594468693622517613869880791884929588100914778964766348914919202255397776583412976785216592924335179128220634848871563960167726280836726035489482233158897362166942091133366827965811201438682117312550600943385153640907629347663140487841016782054145413246763816202055243693289693996466579973
# the public exponent
e = 76794907644383980853714814867502708655721653834095293468287239735547303515225813724998992623067007382800348003887194379223500764768679311862929538017193078946067634221782978912767213553254272722105803768005680182504500278005295062173004098796746439445343896868825218704046110925243884449608326413259156482881

# the hypothesis on the private exponent (the theoretical maximum is 0.292)
delta = 0.274 # this means that d < N^delta

#
# Lattice (tweak those values)
#

# you should tweak this (after a first run), (e.g. increment it until a solution is found)
m = 7 # size of the lattice (bigger the better/slower)

# you need to be a lattice master to tweak these
t = int((1-2*delta) * m) # optimization from Herrmann and May
X = 2*floor(N^delta) # this _might_ be too much
Y = floor(N^(1/2)) # correct if p, q are ~ same size

#
# Don't touch anything below
#

# Problem put in equation
P.<x,y> = PolynomialRing(ZZ)
A = int((N+1)/2)
pol = 1 + x * (A + y)

#
# Find the solutions!
#

# Checking bounds
if debug:
print("=== checking values ===")
print("* delta:", delta)
print("* delta < 0.292", delta < 0.292)
print("* size of e:", int(log(e)/log(2)))
print("* size of N:", int(log(N)/log(2)))
print("* m:", m, ", t:", t)

# boneh_durfee
if debug:
print("=== running algorithm ===")
start_time = time.time()

solx, soly = boneh_durfee(pol, e, m, t, X, Y)

# found a solution?
if solx > 0:
print("=== solution found ===")
if False:
print("x:", solx)
print("y:", soly)

d = int(pol(solx, soly) / e)
print("private key found:", d)
else:
print("=== no solution was found ===")

if debug:
print(("=== %s seconds ===" % (time.time() - start_time)))

if __name__ == "__main__":
example()
# d = 1493519932573300884636712093929290985070801830526216141153447882450934993737739146621

求出来d,就可以求出a

d = 1493519932573300884636712093929290985070801830526216141153447882450934993737739146621
c = 13847199761503953970544410090850216804358289955503229676987212195445226107828814170983735135692611175621170777484117542057117607579344112008580933900051471041224296342157618857321522682033260246480258856376097987259016643294843196752685340912823459403703609796624411954082410762846356541101561523204985391564
n = 98871082998654651904594468693622517613869880791884929588100914778964766348914919202255397776583412976785216592924335179128220634848871563960167726280836726035489482233158897362166942091133366827965811201438682117312550600943385153640907629347663140487841016782054145413246763816202055243693289693996466579973
a = pow(c,d,n)
print(a)
# 24601959430759983424400804734518943158892550216065342062971649989571838687333

接下来就是DSA数字签名过程了,先简单学学,下方是糖醋小鸡块师傅的讲解,真的很详细!力荐!(这不是广告!)

其他加密算法 | Lazzaro (lazzzaro.github.io)

现在有:

发现$h1,h2,s1,s2,r1,r2,a,b$都是已知的,那么未知数只有$x,k1$,消参即可求出来

移项,让等式左边都变成$xr1r2$

然后做差

解出k1后,代入$(h1+xr1)k1^{-1}=s1(modq)$,即可求出x

exp:

from Crypto.Util.number import *
import gmpy2

a = 24601959430759983424400804734518943158892550216065342062971649989571838687333
p= 161310487790785086482919800040790794252181955976860261806376528825054571226885460699399582301663712128659872558133023114896223014064381772944582265101778076462675402208451386747128794418362648706087358197370036248544508513485401475977401111270352593919906650855268709958151310928767086591887892397722958234379
q= 1115861146902610160756777713087325311747309309771
g= 61073566757714587321114447684333928353300944355112378054603585955730395524359123615359185275743626350773632555967063692889668342544616165017003197599818881844811647270423070958521148291118914198811187731689123176313367399492561288350530256722898205674043032421874788802819858438796795768177550638273020791962
y= 23678147495254433946472657196764372220306841739888385605070426528738230369489739339976134564575544246606937803367113623097260181789372915552172469427842482448570540429192377881186772226796452797182435452490307834205012154495575570994963829345053331967442452842152258650027916313982835119514473311305158299360
(h1, r1, s1) = 535874494834828755542711401117152397489711233142, 117859946800380767356190121030392492081340616512, 26966646740134065096660259687229179143947213779
(h2, r2, s2) = 236574518096866758760287021848258048065293279716, 863199000523521111517835459866422731857447792677, 517924607931342012033031470185302567344725962419

b = 17474742587088593627

k = ((h1*r2+b*s2*r1-r1*h2)*(gmpy2.invert(s1*r2-a*r1*s2,q)))%q
x = ((k*s1-h1)*(gmpy2.invert(r1,q)))%q
print(long_to_bytes(x))
# b'l1near_k1s_unsafe'
# flag{l1near_k1s_unsafe}

ez_ECC

这个题真的很难,参考了糖醋小鸡块师傅的文章才懂(懂得不多),道阻且长啊。。。

ECDSA算法确实是一种奇妙的算法,它已经在现实世界中得到了广泛的应用。但由于操作问题,我不小心在我的签名中泄露了太多的数据。即使我泄露了这么多数据,也应该是安全的吧?
from sage.all import *
import itertools
from hashlib import *
from Crypto.Util.number import *
from random import randint
import ecdsa
from math import ceil
from secret import secret,hint,flag,my_own_prime

assert flag.startwith(b'flag{')
assert bytes_to_long(secret).bit_length() == 384

curves = ecdsa.curves
NIST256p = curves.NIST256p
NIST384p = curves.NIST384p

class Random_EC():
def __init__(self,state = None,Gen_Curve = True):
if state == None:
self.state = getRandomNBitInteger(512)
else:
self.state = state
if Gen_Curve:
self.int_curve()

def int_curve(self,CURVE = None):
if CURVE == None:
self.Curve = NIST256p
self.g = self.Curve.generator * self.state
num1 = getRandomNBitInteger(256)
num2 = getRandomNBitInteger(256)
self.P = num2*self.g
self.Q = num1*self.g

# get the confirmed curve
else:
Curve, P, Q = CURVE
self.Curve = Curve
self.g = Curve.gen(0)
self.P = P
self.Q = Q

def int_getkey(self):
# To get the right random key
t = int((self.state * self.Q).x())
self.int_updade()
return t%(2**250)

def RSA_reinforce(self,key: int):
p = my_own_prime(512)
q = my_own_prime(512)
n = p * q
leak = p + q
e = 16542764952
c = pow(key, e, n)
return (c, n, leak)

def int_updade(self):
self.state = int((self.state * self.P).x())

def Random_key(self, n:int):
out = 0
number = ceil(n/250)
for i in range(number):
out = (out<<250) + self.int_getkey()
return out % (2^n)


class ECDSA():
def __init__(self):
self.Random = Random_EC()
self.Curve_length = 384
self.curve = NIST384p
self.gen = self.curve.generator
self.pri_key = self.Random.Random_key((self.Curve_length*125)//192)
self.pub_key = self.pri_key * self.gen
self.order = self.curve.order

def set_curve(self, curve) -> None:
self.Random = Random_EC()
self.Curve_length = 384
self.curve = curve
self.gen = curve.generator
self.order = self.curve.order

def set_pri_key(self, d: int) -> None:
self.pri_key = d
self.pub_key = d * self.gen

def sign(self, msg: bytes, K_time:int) -> tuple:

if K_time == 0:
K = long_to_bytes(self.Random.Random_key(self.Curve_length))
k = int(sha256(K).hexdigest(), 16)
else:
k = K_time

P = k * self.gen
r = int(P.x())
k_v = int(inverse(k, self.order))
e = int(sha256(msg).hexdigest(), 16)
s = (e + self.pri_key * r) * k_v % self.order
# print('k = ',k)
return r, s, k

def verify(self, msg: bytes, signature: tuple) -> bool:
r, s = signature

assert 0 < r < self.order
assert 0 < s < self.order

e = int(sha256(msg).hexdigest(), 16)
w = int(inverse(s, self.order))
u1 = e * w % self.order
u2 = r * w % self.order
P = u1 * self.gen + u2 * self.pub_key
return int(r) == int(P.x())

def into_secret(self, msg: int) -> tuple:
# sage
E = EllipticCurve(GF(int(NIST384p.curve.p())),[int(NIST384p.curve.a()), int(NIST384p.curve.b())])
Point = E.lift_x(msg)
Key = self.Random.Random_key(self.Curve_length)
return int(Key)*Point


if __name__ == '__main__':
hint = b"****************************************************"
flag = bytes_to_long(flag)
hint = bytes_to_long(hint)
secret = bytes_to_long(secret)

for i in range(250):
SIGN = ECDSA()

Hint = SIGN.Random.RSA_reinforce(hint)
m1 = b'A typical Dual ec prng vulnerability applied to NSA, you can find its source in past papers.'
m2 = b'Hope you have learned a good foundation of ECDSA, it can help you better answer and understand this problem.'
C = flag^^secret
into_secret = SIGN.into_secret(secret)
sign1 = SIGN.sign(m1,0)
sign2 = SIGN.sign(m2,sign1[2])

print(f'C = {C}')
print(f'Hint = {Hint}')
print(f'pub_key = {SIGN.pub_key.x(),SIGN.pub_key.y()}')
print(f'P = {int(SIGN.Random.P.x()),int(SIGN.Random.P.y())}')
print(f'Q = {int(SIGN.Random.Q.x()),int(SIGN.Random.Q.y())}')
print(f'sign1 = {sign1[:2]}')
print(f'sign2 = {sign2[:2]}')
print(f'into_secret = {into_secret}')
print(f'-------------------------------------------------------------------')

数据有好多,不放了。。

m1,m2中给了提示说题目与Dual EC伪随机数发生器相关。相关的知识可以参考tl2cents师傅的文章,很详细的讲了这个伪随机数发生器的原理。

Dual EC其实就是一个伪随机数发生器,他选取E=NIST256p作为用于产生伪随机数的椭圆曲线,其产生随机数的步骤是:

选取E上的两个随机点P、Q
初始化一个起始状态state(256bit)
每次要获取一个随机数时,就计算当前的stateQ的横坐标,并按要求截取对应比特,当作本次产生的伪随机数;产生伪随机数后,更新state为stateP的横坐标

如果攻击者掌握了一个d值,这个d值满足:

那么对于任意一个产生的随机数,攻击者相当于知道了对应的stateQ点,那么又因为他知道d,所以他就有state*dQ点,也就是stateP点。也就是说,攻击者就能获取以后的所有伪随机数,这就是Dual EC的后门。

原理知道了看看题目

发现有一个hint,以下是对hint的加密部分:

def RSA_reinforce(self,key: int):
p = my_own_prime(512)
q = my_own_prime(512)
n = p * q
leak = p + q
e = 16542764952
c = pow(key, e, n)
return (c, n, leak)

正常求p,q就可以,但是发现e和p-1及q-1均不互素,还需要有限域开根或者AMM才能解出hint。

得到hint为:Anything greater than 2^15 is really that’s too big

接着分析题目。接下来可以发现的一点是:所有签名的r值都相同。这一点看代码也能看出,对m2的签名用的是签名m1时的k,所以存在一个k复用的问题。而签名的流程是:

而k相同会导致r相同,所以两式只需要简单作差消去k,就能求出pri的值:

既然能求出pri的值,而pri是250bit数量级,是一个state*Q的坐标的低250位,因此只需要爆破6位就可以得到这个点的完整坐标。而根据Dual EC的后门可以知道,如果我们有P=dQ的d这个值,就可以完全掌控后面所有的state,也就可以拿到之后用于加密secret的key值。那么问题就在于如何求出这个d。

而P,Q生成过程是:

def __init__(self,state = None,Gen_Curve = True):
if state == None:
self.state = getRandomNBitInteger(512)
else:
self.state = state
if Gen_Curve:
self.int_curve()

def int_curve(self,CURVE = None):
if CURVE == None:
self.Curve = NIST256p
self.g = self.Curve.generator * self.state
num1 = getRandomNBitInteger(256)
num2 = getRandomNBitInteger(256)
self.P = num2*self.g
self.Q = num1*self.g

这个时候我们就可以用到之前解出来的hint了,这个意思难道是在说这么多组数据之中,存在一个小于2^15的d值?一旦有了这个想法,马上就能找到一个证明这个思路的依据:

  • 一共只有232组数据,和题目循环的250次不一样,显然动了手脚

那么题目给这么多组明明就没有关系的数据也就说得通了。

那么接下来要做的是就是对每一组依次试一下看存不存在小于2^15的d满足P=dQ,然后用存在的那一组解密即可。这里也有一点小的优化技巧,就是不要这样写:

for i in range(2^15):
if(i*Q == p):
print(i)

这样是很自然的写法,但是会做很多多余的点加法计算,就比如你计算9Q的时候,其实在前面计算8Q时已经算过8Q的值,只需要用那个8Q的值求8Q+Q就能得到9Q了。所以更好的方式是写成:

P = NIST_256_CURVE(P)
Q = NIST_256_CURVE(Q)
q0 = Q
for i in range(2^15):
if(q0 == P):
print(j,i)
print(P)
q0 += Q

这样会减少很多时间,本来爆破完所有数据可能要两小时,但这样五六分钟就可以搞定。

求出d过后就是控制Dual EC生成我们需要的伪随机数key,然后求逆并作点乘得到secret,并与C异或就得出flag了。

exp:

from Crypto.Util.number import *
from gmpy2 import iroot
from hashlib import sha256
from tqdm import *
from math import ceil

#part1 get hint
if(0):
c,n,leak = (12351774260625362799610458605055557349668978169954248709224197283033722650641969191523420968152180626844781310599988085824706330561484553939064653937267598659731237154241515349280967639128615784193195725170343153328247947729351564102666060302013802359410482179324719156651832539747622404434096773119440083329, 122908984806235457892414635852036332676574434804833208576141668077475917235411535511069143359388659946159724740722593181625712110278669227640297497485641848470391938438894136564046632275052354217712453952532772368082733299695485759213723305738760035793602060420638500827652832664161031815519780589765520132973, 22333858470427703056488469739724305644350178024239032705153649807913901449803887198889611591209527103787726531081225700412575986297091811550954958064297166)
e = 16542764952
p_q = iroot(leak^2-4*n,2)[0]
p = (leak + p_q) // 2
q = n // p
cq = pow(c,inverse(e//24,q-1),q)
PR.<x> = Zmod(q)[]
f = x^24 - cq
resq = f.roots()
for i in resq:
print(long_to_bytes(int(i[0])))
#hint: Anything greater than 2^15 is really that's too big


#part2 get d
# NIST P-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 P-384
NIST_384_P = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff
NIST_384_K = GF(NIST_384_P)
NIST_384_A = NIST_384_K(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc)
NIST_384_B = NIST_384_K(0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef)
NIST_384_CURVE = EllipticCurve(NIST_384_K, (NIST_384_A, NIST_384_B))
NIST_384_GEN = NIST_384_CURVE(0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7, 0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f)
NIST_384_ORDER = 0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973 * 0x1


#结合hint在2^15内爆破,发现第201组数据有异常:32309*Q = P
if(0):
with open(r"output.txt","r") as f:
for j in trange(250):
for t in range(6):
exec(f.readline())
f.readline()
P = NIST_256_CURVE(P)
Q = NIST_256_CURVE(Q)
q0 = Q
for i in range(2^15):
if(q0 == P):
print(j,i)
print(P)
q0 += Q


#part3 get pri
C = 28370462154406144789913243909256020527531135264361458510233553021695306448248185548876492600895007348961847185821989
pub_key = (23063531651133054044852146745751828065565652508316078757465526964945889829041322577333868291426745685755660447945768 , 38213774544479557349161813523787259744626407961805163166366401345392394160489749007540120063131112167181615738936602)
P = (99376526638506705902714648195970871631891150648967956889439656483745513799077, 49286347987713888387330453808791204691290920954467279402308313223992253330920)
Q = (95979043517822469787247449840043678535429394578371796773017425344169131078878, 94108817998367868965643129518919867199519351407755146103464278196693149407197)
r1,s1 = (32594514971850903210957109229029032596013744664454613348001968983388557886022626485537652491952756966982681985873826, 33822531453262141722363336331985371357432586409495680846477948429687072595338746861106767975593900234281162130620713)
r2,s2 = (32594514971850903210957109229029032596013744664454613348001968983388557886022626485537652491952756966982681985873826, 30963116078493244587396392437563800584802728459796510243236725881496588593849796130551914519859427419450232539678279)
into_secret = (12219467168510963191933866108724307399115854676119007622103880230537250338471252977689989738440080704914043187805666 , 15140655916234412794356873631237108843402057349206472869810848889771783263614142207164313273971781373595696054566462)
e1 = int(sha256(b"A typical Dual ec prng vulnerability applied to NSA, you can find its source in past papers.").hexdigest(), 16)
e2 = int(sha256(b"Hope you have learned a good foundation of ECDSA, it can help you better answer and understand this problem.").hexdigest(), 16)
P = NIST_256_CURVE(P)
Q = NIST_256_CURVE(Q)
into_secret = NIST_384_CURVE(into_secret)

#shared-k attack
r = r1 = r2
o = NIST_384_ORDER
pri = (e2*s1-e1*s2)*inverse(r1*s2-r2*s1,o) % o

d = 32309
assert d*Q==P


#part4 Dual_EC backdoor
def int_getkey(state):
# To get the right random key
t = int((state * Q)[0])
return t%(2**250)

def Random_key(n,state):
temp = state
out = 0
number = ceil(n/250)
for i in range(number):
out = (out<<250) + int_getkey(temp)
temp = int((temp*P)[0])
return out % (2^n)

for i in range(2^6):
r = i*2^250 + pri
try:
R = NIST_256_CURVE.lift_x(r)
S = d*R
state = int(S[0])
key = Random_key(384,state)
secret = int((inverse(key,o)*into_secret)[0])
flag = long_to_bytes(int(secret^^C))
if(b"flag" in flag):
print(flag)
except:
pass

#b'flag{EC_PRNG_DUAL_NSA_18saL_0dh1_fh2ass0j_happy}'

吐槽

我好菜,题目没有问题,我有问题。

我这一生如履薄冰,你说我能走到对岸吗。。。

image-20240129010233640