DSA-针对随机数K的攻击

目录

基本原理与参数

 针对随机数K的相关攻击

共享k

 jarvisoj  DSA

线性k(k2=ak1+b)

2023年春秋杯网络安全联赛冬季赛 not wiener

关系K(k2=k1^2+b)

 [HZNUCTF 2023 preliminary]easyDSA

 已知部分K(K的部分高位已知)

[NCTF 2021]dsa


基本原理与参数

 针对随机数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:])



  • 19
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值