目录
[HZNUCTF 2023 preliminary]easyDSA
基本原理与参数
针对随机数K的相关攻击
共享k
jarvisoj DSA
解压得到:
DSA的公钥(p,q,g,y)可以从dsa_public.pem文件中提取。
packet1:
message里面是待签名的信息,sign文件中是已经签名过的文件。可以从sign文件中提取出(r,s)
公钥,r,s的提取:
from Crypto.Util.number import *
import gmpy2
from Crypto.PublicKey import DSA
from Crypto.Signature import DSS
from Crypto.Hash import SHA1
from Crypto.Util.asn1 import DerSequence
#提取公钥
pubkey=DSA.import_key(open("dsa_public.pem","r").read())
files=[['./packet1/message1', './packet1/sign1.bin'],
['./packet2/message2', './packet2/sign2.bin'],
['./packet3/message3', './packet3/sign3.bin'],
['./packet4/message4', './packet4/sign4.bin']
]
#读取公钥
p=pubkey.p
q=pubkey.q
g=pubkey.g
y=pubkey.y
#创建DSS验证器对象
verifier=DSS.new(pubkey,'fips-186-3','der')
for file in files:
#SHA1对象
sha = SHA1.new(open(file[0], 'rb').read())
signature = open(file[1], 'rb').read()
#验证
verifier.verify(sha, signature)
#从签名中提取r,s
der_seq = DerSequence().decode(signature, strict=True)
print("==================")
print("sha1=",sha.hexdigest())
print("r=",der_seq[0])
print("s=",der_seq[1])
output:
可以发现,对message3和message4使用了相同的随机数签名,因此可以对message3和message4进行共享k攻击。
from Crypto.Util.number import *
import gmpy2
from Crypto.PublicKey import DSA
from Crypto.Signature import DSS
from Crypto.Hash import SHA1
from Crypto.Util.asn1 import DerSequence
#获取公钥
pubkey=DSA.import_key(open("dsa_public.pem","r").read())
files=[
['./packet3/message3', './packet3/sign3.bin'],
['./packet4/message4', './packet4/sign4.bin']
]
p=pubkey.p
q=pubkey.q
g=pubkey.g
y=pubkey.y
#参数列表
h=[]
r=[]
s=[]
for file in files:
sha=SHA1.new(open(file[0],'rb').read())
h.append(int(sha.hexdigest(),16))
signature=open(file[1],'rb').read()
#提取r,s
der_sep=DerSequence().decode(signature,strict=True)
r.append(der_sep[0])
s.append(der_sep[1])
k=(h[0]-h[1])*(gmpy2.invert(s[0]-s[1],q))%q
x=(s[0]*k-h[0])*(gmpy2.invert(r[0],q))%q
print("x=",x)
print(pow(g,x,p)==y)
output:
线性k(k2=ak1+b)
2023年春秋杯网络安全联赛冬季赛 not wiener
题目:
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))
gen_key函数生成DSA数字签名的公钥。Cry函数使用RSA将a加密了。使用DSA签名了两组信息,且随机数k满足k2=a*k1 + b。解密RSA得到a,b给出了,就可以使用线性k攻击得到DSA的私钥x了。
在RSA给出的n,e中发现n,e都是非常大的数。d是280位,n是1024位,280/1024约等于0.273,因此不能使用Wiener Attack,这里可以使用Boneh-Durfee,因为0.273<0.292。
将m调到7就可以得到d了。
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 = .273 # 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就可以进行解密了。
from Crypto.Util.number import *
import gmpy2
d=1493519932573300884636712093929290985070801830526216141153447882450934993737739146621
n = 98871082998654651904594468693622517613869880791884929588100914778964766348914919202255397776583412976785216592924335179128220634848871563960167726280836726035489482233158897362166942091133366827965811201438682117312550600943385153640907629347663140487841016782054145413246763816202055243693289693996466579973
e = 76794907644383980853714814867502708655721653834095293468287239735547303515225813724998992623067007382800348003887194379223500764768679311862929538017193078946067634221782978912767213553254272722105803768005680182504500278005295062173004098796746439445343896868825218704046110925243884449608326413259156482881
c = 13847199761503953970544410090850216804358289955503229676987212195445226107828814170983735135692611175621170777484117542057117607579344112008580933900051471041224296342157618857321522682033260246480258856376097987259016643294843196752685340912823459403703609796624411954082410762846356541101561523204985391564
a=pow(c,d,n)
print(a)
print(pow(a,e,n)==c)
线性k:
from Crypto.Util.number import *
import gmpy2
d=1493519932573300884636712093929290985070801830526216141153447882450934993737739146621
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=pow(c,d,n)
b=17474742587088593627
k=(h2*r1-h1*r2-s2*b*r1)*gmpy2.invert(s2*a*r1-s1*r2,q)%q
x=(s1*k-h1)*gmpy2.invert(r1,q)%q
print(long_to_bytes(x).decode())
关系K(k2=k1^2+b)
[HZNUCTF 2023 preliminary]easyDSA
题目:
hash.py:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from Crypto.Util.number import bytes_to_long
IV = 0x7380166f4914b2b9172442d7da8a0600a96f30bc163138aae38dee4db0fb0e4e
default_hm1 = b'HZNUCTFRound#1'
default_hm2 = b'HZNUCTFRound#1'
def solve_k(l):
for i in range(512):
if (l + 1 + i - 448) % 512 == 0:
return i
def padding(msg):
l = len(msg)
k = solve_k(l)
l64 = bin(l)[2:].rjust(64, '0') # what if msg is too long, how?
# so mention the condition in guideline of Algorithm
msg = msg + '1' + '0' * k + l64
assert len(msg) % 512 == 0
return msg, l, k
def iteration():
pass
def ring_shift_left(x, num):
x = bin(x)[2:].rjust(32, '0') # shift in 32 bit, take care of it
x = int(x[num:] + x[:num], 2)
return x
def p0(x):
return x ^ ring_shift_left(x, 9) ^ ring_shift_left(x, 17)
def p1(x):
return x ^ ring_shift_left(x, 15) ^ ring_shift_left(x, 23)
def extending(msg):
WW = []
for i in range(len(msg) // 512):
W = ['0' for _ in range(132)]
msgi = msg[i * 512:(i + 1) * 512]
# 512 ====> 16 * 32
for ii in range(len(msgi) // 32):
W[ii] = msgi[ii * 32:(ii + 1) * 32]
assert len(W[ii]) == 32
for j in range(16, 68):
# how to xor word, change to number or bytes?
# number, of course
W[j] = p1(int(W[j - 16], 2) ^ int(W[j - 9], 2) ^ ring_shift_left(int(W[j - 3], 2), 15)) ^ \
ring_shift_left(int(W[j - 13], 2), 7) ^ int(W[j - 6], 2)
W[j] = bin(W[j])[2:]
W[j] = W[j].rjust(32, '0')
for j in range(68, 132):
W[j] = int(W[j - 68], 2) ^ int(W[j - 68 + 4], 2)
W[j] = bin(W[j])[2:]
W[j] = W[j].rjust(32, '0')
WW.append(W)
return WW
def cons(j):
if 0 <= j <= 15:
return 0x79cc4519
elif 16 <= j <= 63:
return 0x7a879d8a
def bool_ff(param, j):
x, y, z = param
if 0 <= j <= 15:
return x ^ y ^ z
elif 16 <= j <= 63:
return (x & y) | (x & z) | (y & z)
def bool_gg(param, j):
x, y, z = param
if 0 <= j <= 15:
return x ^ y ^ z
elif 16 <= j <= 63: # take care of not
if len(bin(x)[2:]) < 32:
ans = ''
x = bin(x)[2:].rjust(32, '0')
y = bin(y)[2:].rjust(32, '0')
z = bin(z)[2:].rjust(32, '0')
for i in range(0, 32):
if x[i] == '0':
ans += str((int(x[i], 2) & int(y[i], 2)) | (1 & int(z[i], 2)))
elif x[i] == '1':
ans += str((int(x[i], 2) & int(y[i], 2)) | (0 & int(z[i], 2)))
return int(ans, 2)
elif len(bin(x)[2:]) == 32:
return (x & y) | (~x & z)
def cf(v, w):
W, W_ = w[:68], w[68:]
# for i in W:
# print(hex(int(i, 2)), end=' ')
# for i in W_:
# print(hex(int(i, 2)), end=' ')
# print()
v = bin(v)[2:].rjust(256, '0')
A, B, C, D, E, F, G, H = [v[_ * 32:(_ + 1) * 32] for _ in range(256 // 32)]
for j in range(64):
# be care of plus, it may surpass 32 bits
# remember j mod 32, or output will be wrong
tmp = (ring_shift_left(int(A, 2), 12) + int(E, 2) + ring_shift_left(cons(j), j % 32)) & 0xffffffff
SS1 = ring_shift_left(tmp, 7)
SS1 = bin(SS1)[2:].rjust(32, '0')
SS2 = int(SS1, 2) ^ ring_shift_left(int(A, 2), 12)
SS2 = bin(SS2)[2:].rjust(32, '0')
tmp = (bool_ff([int(A, 2), int(B, 2), int(C, 2)], j) + int(D, 2) + int(SS2, 2) + int(W_[j], 2)) & 0xffffffff
TT1 = bin(tmp)[2:].rjust(32, '0')
tmp = (bool_gg([int(E, 2), int(F, 2), int(G, 2)], j) + int(H, 2) + int(SS1, 2) + int(W[j], 2)) & 0xffffffff
TT2 = bin(tmp)[2:].rjust(32, '0')
D = C.rjust(32, '0')
C = ring_shift_left(int(B, 2), 9)
C = bin(C)[2:].rjust(32, '0')
B = A.rjust(32, '0')
A = TT1.rjust(32, '0')
H = G.rjust(32, '0')
G = ring_shift_left(int(F, 2), 19)
G = bin(G)[2:].rjust(32, '0')
F = E.rjust(32, '0')
E = p0(int(TT2, 2))
E = bin(E)[2:].rjust(32, '0')
# print(j, end=' ')
# for i in [A, B, C, D, E, F, G, H]:
# print(hex(int(i, 2)), end=' ')
# print()
ans = A + B + C + D + E + F + G + H
ans = int(ans, 2)
return ans
def SM3(msg):
msg = bytes_to_long(msg)
if msg.bit_length() % 4 != 0:
msg = (4 - (msg.bit_length() % 4)) * '0' + bin(msg)[2:]
msg, l, k = padding(msg)
# extend
W = extending(msg)
# iteration
vi = IV
n = (l + k + 65) // 512
for i in range(n):
res = cf(vi, W[i])
vi = vi ^ res
# compress
return hex(vi)[2:]
task.py:
from hash import *
from sage import *
from secrets import flag
from Crypto.Util.number import *
from gmpy2 import invert
def dsa(hmac, _pk, _sk, k):
_p, _q, _g, _y = _pk
x = _sk
r = pow(_g, k, _p) % _q
s = ((hmac + x * r) * invert(k, _q)) % _q
return int(r), int(s)
m = int(flag.hex(), 16)#私钥
p = getPrime(2048)
q = getPrime(256)
g = getRandomNBitInteger(2048)
y = pow(g, m, p)
pk = (p, q, g, y)#公钥
sk = m#私钥
hm1 = int(SM3(default_hm1), 16)
hm2 = int(SM3(default_hm2), 16)
print(hm1)
print(hm2)
print(hm1==hm2)
nonce = getPrime(64)
xxxx = getPrime(20)
print(f"(r1, s1) = {dsa(hm1, pk, sk, nonce)}")
print(f"(r2, s2) = {dsa(hm1, pk, sk, nonce ** 2 + xxxx)}")
print(f"p = {p}\nq = {q}\ng = {g}\ny = {y}")
# (r1, s1) = (43665657147136977892760835332544097729763754398125679419859037123212964274095, 11372107439153704547599978617809027960018057676066118055075660375442954789009)
# (r2, s2) = (29184887007213204285288676779168140587575609668559831035949650649308618592275, 5011738292572181542092375902756977363590922060964162373234404450451520414798)
# p = 31961141251107494919420190534228520246958409864267239760354623819192809291490262139213317490432416411403367763443527530375117617196123131270496004125231254335150221348901335274505489844222882171272650010562960614279185073793274638651086760235178963210965828168433516820007716846876686795459738332444629111764967204355463398049697867061034126529189537688874999118692225915790053920062142349951686250122300061810240375783724631961234942175580462986265098353263395579346466921241016500821787793395554444982717141449909744838267161237273856377774256250949274635575801148994817767751541256849860886577256992383324866941911
# q = 69375998045163628324086568160767337544901252262545889505892695427466730978301
# g = 23095306638137759877487469277470910487928442296144598697677211337473146684728707820084075779044942034329888686699655576145455963231144004571165817481066424910959951439014314776050521403558035997997820617824839889597136772108383034876458141163933312284054415480674388788905935457149956424898637134087874179010376667509489926236214865373552518669840236207944772752416668193786003948717604980584661094548997197117467440864460714843246250800575997370964173558788145639802963655916833143883799542309432910222224223561677245110195809587171802538978009246887077924173034608600837785506594525481696000424121705524449481831586
# y = 30195133393879069638917191223585579396119430591488890396938821804398771785068454607425044458865556053274470709839502680269466948174813926392729790863065933078609827279352860810689776644132512095691760326095517755483748554008211568781998662554432781285208646921699265866446498342049913829592480268053599307065979016922204438675164034767731708343084371572648019835171087671868322447023378942812010740490724160077164191297435291229504616686997442254543493394641023587237077429236872101951650325361004443988267286616139798736713430746804524113024341440435623834197278500144543476528466395780355874841379098027115073850819
从题目可以知道将flag作为DSA的私钥了。使用SM3对两条信息生成哈希值。SM3在Hash.py中,调用它就可以知道两条信息的哈希值了,因为这两条信息相同所以哈希值是一样的。在对这两条信息签名时的随机数k满足k2=k1^2+b这样的形式。
先得到hash值
使用二元coppersmith求小根:
from Crypto.Util.number import *
import gmpy2
import itertools
def small_roots(f, bounds, m=1, d=None):
if not d:
d = f.degree()
R = f.base_ring()
N = R.cardinality()
f /= f.coefficients().pop(0)
f = f.change_ring(ZZ)
G = Sequence([], f.parent())
for i in range(m + 1):
base = N ^ (m - i) * f ^ i
for shifts in itertools.product(range(d), repeat=f.nvariables()):
g = base * prod(map(power, f.variables(), shifts))
G.append(g)
B, monomials = G.coefficient_matrix()
monomials = vector(monomials)
factors = [monomial(*bounds) for monomial in monomials]
for i, factor in enumerate(factors):
B.rescale_col(i, factor)
B = B.dense_matrix().LLL()
B = B.change_ring(QQ)
for i, factor in enumerate(factors):
B.rescale_col(i, 1 / factor)
H = Sequence([], f.parent().change_ring(QQ))
for h in filter(None, B * monomials):
H.append(h)
I = H.ideal()
if I.dimension() == -1:
H.pop()
elif I.dimension() == 0:
roots = []
for root in I.variety(ring=ZZ):
root = tuple(R(root[var]) for var in f.variables())
roots.append(root)
return roots
return []
(r1, s1) = (43665657147136977892760835332544097729763754398125679419859037123212964274095, 11372107439153704547599978617809027960018057676066118055075660375442954789009)
(r2, s2) = (29184887007213204285288676779168140587575609668559831035949650649308618592275, 5011738292572181542092375902756977363590922060964162373234404450451520414798)
p = 31961141251107494919420190534228520246958409864267239760354623819192809291490262139213317490432416411403367763443527530375117617196123131270496004125231254335150221348901335274505489844222882171272650010562960614279185073793274638651086760235178963210965828168433516820007716846876686795459738332444629111764967204355463398049697867061034126529189537688874999118692225915790053920062142349951686250122300061810240375783724631961234942175580462986265098353263395579346466921241016500821787793395554444982717141449909744838267161237273856377774256250949274635575801148994817767751541256849860886577256992383324866941911
q = 69375998045163628324086568160767337544901252262545889505892695427466730978301
g = 23095306638137759877487469277470910487928442296144598697677211337473146684728707820084075779044942034329888686699655576145455963231144004571165817481066424910959951439014314776050521403558035997997820617824839889597136772108383034876458141163933312284054415480674388788905935457149956424898637134087874179010376667509489926236214865373552518669840236207944772752416668193786003948717604980584661094548997197117467440864460714843246250800575997370964173558788145639802963655916833143883799542309432910222224223561677245110195809587171802538978009246887077924173034608600837785506594525481696000424121705524449481831586
y = 30195133393879069638917191223585579396119430591488890396938821804398771785068454607425044458865556053274470709839502680269466948174813926392729790863065933078609827279352860810689776644132512095691760326095517755483748554008211568781998662554432781285208646921699265866446498342049913829592480268053599307065979016922204438675164034767731708343084371572648019835171087671868322447023378942812010740490724160077164191297435291229504616686997442254543493394641023587237077429236872101951650325361004443988267286616139798736713430746804524113024341440435623834197278500144543476528466395780355874841379098027115073850819
#哈希值
h = 19905280947443115569469777697852124038269468456842113763109865796452965095134
#定义多项式环
R.<k,b> = PolynomialRing(Zmod(q))
f=s2*(k^2+b)*r1-(s1*k*r2)-h*r1+h*r2
roots=small_roots(f,[2^64,2^20],d=2,m=1)
k=roots[0][0]
x=int((s1*k-h)*gmpy2.invert(r1,q)%q)
if pow(g,x,p)==y:
print(long_to_bytes(x).decode())
已知部分K(K的部分高位已知)
[NCTF 2021]dsa
题目:
from Crypto.Util.number import *
from secret import flag
from hashlib import sha256
import os
def keygen():
while True:
p = getPrime(522)
q = p//2
if isPrime(q):
break
g = 3
h = long_to_bytes(getPrime(256))
x = int.from_bytes(h*2, "big")
y = pow(g, x, p)
return g, p, q, y, x
def sign(h, x):
k = sha256(h.encode().hex().encode()).digest()+sha256(bytes.fromhex(h)+x.to_bytes(128, "big")).digest()
k = int.from_bytes(k, "big")
r = pow(g,k,p)
s = (r*x+int(h,16))*inverse(k,q)%q
return r, s
g, p, q, y, x = keygen()
flag = int(flag[5:-1],16)^int(sha256(x.to_bytes(128, "big")).hexdigest(),16)
r, s = sign(hex(flag)[2:], x)
print(q)
print(y)
print(flag)
print(r)
print(s)
'''
4065074330205980877463463424406813850154275302695361748314870346411329051948044450952905063182483477758495116696164996888846308775044737816809015524088898203
7743982251072012463264403932580827621959049035277930304818871889119878506480333248188293037455476433705911511645160292331990658781048396135284434991466243636
19480592192543881131267167328019941277106895469291691207381812905033306766991
962433004607153392099715322793248884218264181538005666659905851247468102959956625098831516046715446615198437005036117685792905736788216987378584513020215442
1861254747644911591100925843087118347161726578606012243057783788330822542299254180561801871884967022902307837045926190782819951409650425825871898890839825777
'''
keygen函数用于生成DSA的公钥,且q=p//2,因为'//'是向下取整,题目告诉了q,所以有p=2*q+1。h是256位的字节流,这里x是由h+h这样拼接起来的,其实h+h可以这样想,先将h左移256位,在加h。因此对于x的整数形式可以这样写:(2^256+1)*bytes_to_long(h)。
设bytes_to_long(h)=a
这里可以进行验证:
from Crypto.Util.number import *
tmp=getPrime(256)
h=long_to_bytes(tmp)
x=int(int.from_bytes(h*2, "big"))
x1=(2**256+1)*tmp
print(tmp)
print(x)
print(x1)
print(x==x1)
设导入的flag包裹的部分为m,即flag{m}。设哈希函数为h()
变量flag=m异或h(x)。变量flag的值在题目中给出了(设为b)。在签名时随机数k的生成为传入的变量flag(b)经过SHA256后的哈希值加私钥x经过SHA256后的哈希值。因为变量flag的值题目给出了,所以它经过SHA256后的哈希值也就知道了(设为H),因此随机数k的高256位也就知道了。这里k也是拼接,因此k可以看作将H左移256位在加上一个h'(x经过SHA256的哈希值),因此k可以写成(2^256)*H+h'。
从签名函数中可以知道这里签名的信息为传入的变量flag(b)的十进制值。
完整代码:
from Crypto.Util.number import *
from hashlib import sha256
import sympy
import itertools
def small_roots(f, bounds, m=1, d=None):
if not d:
d = f.degree()
R = f.base_ring()
N = R.cardinality()
f /= f.coefficients().pop(0)
f = f.change_ring(ZZ)
G = Sequence([], f.parent())
for i in range(m + 1):
base = N ^ (m - i) * f ^ i
for shifts in itertools.product(range(d), repeat=f.nvariables()):
g = base * prod(map(power, f.variables(), shifts))
G.append(g)
B, monomials = G.coefficient_matrix()
monomials = vector(monomials)
factors = [monomial(*bounds) for monomial in monomials]
for i, factor in enumerate(factors):
B.rescale_col(i, factor)
B = B.dense_matrix().LLL()
B = B.change_ring(QQ)
for i, factor in enumerate(factors):
B.rescale_col(i, 1 / factor)
H = Sequence([], f.parent().change_ring(QQ))
for h in filter(None, B * monomials):
H.append(h)
I = H.ideal()
if I.dimension() == -1:
H.pop()
elif I.dimension() == 0:
roots = []
for root in I.variety(ring=ZZ):
root = tuple(R(root[var]) for var in f.variables())
roots.append(root)
return roots
return []
q=4065074330205980877463463424406813850154275302695361748314870346411329051948044450952905063182483477758495116696164996888846308775044737816809015524088898203
y=7743982251072012463264403932580827621959049035277930304818871889119878506480333248188293037455476433705911511645160292331990658781048396135284434991466243636
flag=19480592192543881131267167328019941277106895469291691207381812905033306766991
r=962433004607153392099715322793248884218264181538005666659905851247468102959956625098831516046715446615198437005036117685792905736788216987378584513020215442
s=1861254747644911591100925843087118347161726578606012243057783788330822542299254180561801871884967022902307837045926190782819951409650425825871898890839825777
p=2*q+1
g=3
#签名的信息
b=int(hex(flag)[2:],16)
#求已知部分的HASH值,K的前256位
H=int(sha256(hex(flag)[2:].encode().hex().encode()).hexdigest(),16)
PR.<h_0, a> = PolynomialRing(Zmod(q))
f=s*(2^256*H+h_0)-(2^256+1)*a*r-b
roots=small_roots(f,[2^256,2^256],d=2,m=1)
a=roots[0][1]
x=int((2^256+1)*a)
#还原导入的flag
if pow(g,x,p)==y:
origin_flag=int(sha256(x.to_bytes(128, "big")).hexdigest(), 16)^^flag
print(hex(origin_flag)[2:])