目录
[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()[