密码学实战 - HTB signup

141 阅读14分钟

概述

signup是来自于HTB(hackthebox.com)的一个中级密码学挑战,完成该挑战所需要掌握的知识点在于数字签名算法(DSA)。

题目分析

相关的任务文件包括app.py源代码和output.txt文本文件。

app.py内容如下

from Crypto.Util.number import getPrime, getRandomRange, isPrime, inverse, long_to_bytes, bytes_to_long
from hashlib import sha512
from random import SystemRandom
from FLAG import flag

L = 2048
N = 256

def repeating_xor_key(message, key):

    repeation = 1 + (len(message) // len(key))
    key = key * repeation
    key = key[:len(message)]
    
    msg = bytes([c ^ k for c, k in zip(message, key)])
    return msg

def domain_params_generation():

    q = getPrime(N)

    print(f"[+] q condition is satisfied : {N} bit")
    print(q)

    p = 0
    while not (isPrime(p) and len(bin(p)[2:]) == L):
        factor = getRandomRange(2**(L-N-1), 2**(L-N))
        p = factor*q + 1
        
    print(f"[+] p condition is satisfied : {L} bit")
    print(p)

    g = 1
    while g == 1:
        h = getRandomRange(2, p-2)
        g = pow(h, factor, p)

    print(f"[+] g condition is satisfied ")
    print(g)

    
    return(p, q, g)


def key_generation(p, q, g):

    x = getRandomRange(1, q-1)
    y = pow(g, x, p)

    # print(f"[+] private key : {x}")
    print(f"[+] public key  : {y}")

    return(x, y)


def sign(message, private_key, parameters):

    p, q, g = parameters
    k = getRandomRange(1, q-1)

    r = 0
    while r == 0:
        r = pow(g, k, p) % q

    s = 0
    while s == 0:
        Hm = int(sha512(message.encode('utf-8')).hexdigest(), 16)
        s = (inverse(k, q) * (Hm + private_key*r)) % q

    return (r, s)


def verify(message, signature, public_key, parameters):

    p, q, g = parameters
    r, s = signature

    if 0 < r < q and 0 < s < q:
        
        w = inverse(s, q)
        Hm = int(sha512(message.encode('utf-8')).hexdigest(), 16)
        u1 = (Hm * w) % q
        u2 = (r * w) % q
        v = ( (pow(g, u1, p) * pow(public_key, u2, p)) % p ) % q

        if v == r:
            print(f"[+] Valid signature")
            return True
        else:
            print("[!] Invalid signature")
            return False


parameters = domain_params_generation()
p, q, g = parameters

keys = key_generation(p, q, g)
x, y = keys

print()
print("=============================================================================================")
print("===================================== Let's signup ==========================================")
print("=============================================================================================")
print()

with open('./messages', 'r') as o:

    messages = o.readlines()

    for message in messages:
        
        message = message.rstrip()
        print("********************************************************************************")
        print(f"message : {message}")
        signature = sign(message, x, parameters)
        r, s = signature
        print(f"signature: {signature}")

        # verify(message, signature, y, parameters)

    o.close()


key = long_to_bytes(x)
ctf = repeating_xor_key(flag, key)
print()
print("[+] Cipher Text Flag (CTF) : ")
print(hex(bytes_to_long(ctf))[2:])

output.txt节选如下

[+] q condition is satisfied : 256 bit
82349764091980216703243528787846721157571379253101971061732427939554681522787
[+] p condition is satisfied : 2048 bit
22597614376179368541927291014351181151227386315855982530475290304066480549601872481125000412312232411854471891135859606462198144416258790805203892259557432117585853522644532244043882866950543772453962473369978828348563267281282352402870551728874298860312184560283971631533453430210347478794479982659822354364676170461394200151617927348650724770337556315374296708920869883704844016422867256502854967833552352244354161923370938186895496059235837548125257119775236437293842236838420824347387394024112360489719718482356279060719080630335848636921250967340335457343005624222097640480295331833551512059471129216303573167509
[+] g condition is satisfied 
1522212929726315022276463307120954560926577974082882818811797965963142559037271005677110576098054722157527674127902562772533441779935566082403254092732974888203331702770403906546118095393659134079664766399608052006354212408035095060235607110586791971501473747663375215625574865706205289870455822949113508277054039439561937834562865074493156250996591912564352116714825726533396169890605271470775008065174813981767038809048518345994705536020788635479039463106099323099699225053825245500316037480436011003338091199884975066597695902083102085310246580054607257558459623691521498166469718547232285924180800892984387501802
[+] public key  : 10301551349126329107088362265646928131049491442403482867496980836175873288506342576259707854365987702240527285572213439531723683002024146579359208402373918640506155640001430423630072829735081526301031527635977510509965942632354962864260004282149309957641462890526767244115715946314304320419688533957853639652278917522214541525010449214567907170562229712395613781822381944931215562795920884762599015541122869232934183665333423423899053585371134224417264749221110182731383922695086302701203378645475314119934887765168566160180674997559835947912322992312116118206408320274722250233256553213251576546136974127152356743543

=============================================================================================
===================================== Let's signup ==========================================
=============================================================================================

********************************************************************************
message : AUSSMKJVUNUPKBQ9X10U15409YQT79I6XNF7U0K5YI1568Q42L
signature: (56550035924113170505830328735387144247082155512866957659656089559388264283806, 81360207435150401587545818478565496253332501404776627654827674339875574853292)
********************************************************************************
message : TXNJSIVFXYVZL7SO393MU20XJAK9TN75M0VV29RYI1TMORLJB3
signature: (59454243776673042612495706274074683779593162173045377254234644102590461254174, 69901126149177379418089041430016833757460549835486806478242279543570064790515)
********************************************************************************

...
********************************************************************************
message : VRE2H23VMQ2ORK8WNRTLBEASQF3GY9VK05CEYQ74KGO44B8QJK
signature: (4221419519268981734259482267401150235000986484722820856700522542664903741971, 39042686015475285979469968400043348530602713981922607759813976003431964156934)

[+] Cipher Text Flag (CTF) : 
caaa08c4332e5701a9105ab701cc830b9ddbe18f6612c999f82a344bdc597819fba00f81772e4001bc584cff06d287089d8085a3123bdca5e7706612d7641f66a2b6228a67336c60975719ef04e1b55edad4e6850126ee92bc25692bd15e274db3b214973f224001af5f46bb40d2930cd69bae

我们可以看到以上代码实现了数字签名算法(DSA),而输出文件中给出了p,q,gy等参数,以及100个字串及其签名结果。 我们必须得到私钥x的值以获得Flag明文。

解题过程

在给出的100个字串中,我们发现有两个字串其签名结果的r值相同。

message : 2XKSQ0LYL0ASEKH5ROEB02AKNEKGRWEQCMBE6UUCJO1GK0MXP5
signature: (14238399380669249177320087107027881078478542661146360644630244485798709030798, 31010083525559078114799933393024424305929989859345011192619627339807386206438)

message : EISZZOL199OKHQB4JMJB7A3Z1W9S0P0ENI98V7RFS8K9RPYQ1V
signature: (14238399380669249177320087107027881078478542661146360644630244485798709030798, 33299491256343856509045723633358260063303030066061530037315886172050511280880)

那么根据以下链接中给出的公式,我们可以获得kx

rdist.root.org/2010/11/19/… rdist.root.org/2009/05/17/…

from hashlib import sha512

messageA = "2XKSQ0LYL0ASEKH5ROEB02AKNEKGRWEQCMBE6UUCJO1GK0MXP5"
hA = int(sha512(messageA.encode('utf-8')).hexdigest(), 16)
signatureA = (14238399380669249177320087107027881078478542661146360644630244485798709030798, 31010083525559078114799933393024424305929989859345011192619627339807386206438)

messageB = "EISZZOL199OKHQB4JMJB7A3Z1W9S0P0ENI98V7RFS8K9RPYQ1V"
hB = int(sha512(messageB.encode('utf-8')).hexdigest(), 16)
signatureB = (14238399380669249177320087107027881078478542661146360644630244485798709030798, 33299491256343856509045723633358260063303030066061530037315886172050511280880)

k = (hA - hB) * pow(signatureA[1] - signatureB[1], -1, q) % q

r = pow(g, k, p) % q
assert r == signatureA[0]

x = (((signatureA[1] * k) - hA) * pow(r, -1, q)) % q

然后使用x获得Flag明文

from Crypto.Util.number import long_to_bytes, bytes_to_long

ctf = bytes.fromhex("caaa08c4332e5701a9105ab701cc830b9ddbe18f6612c999f82a344bdc597819fba00f81772e4001bc584cff06d287089d8085a3123bdca5e7706612d7641f66a2b6228a67336c60975719ef04e1b55edad4e6850126ee92bc25692bd15e274db3b214973f224001af5f46bb40d2930cd69bae")
key = long_to_bytes(x)

flag = repeating_xor_key(ctf, key)
print("flag =", flag)