Cryptopals zestaw 4 ćwiczenie 28

przez | 24 marca 2021

Tym razem pozornie mało kreatywne ćwiczenie, żeby nie powiedzieć odtwórcze, ale warto jest się do niego przyłożyć i postarać się jak najlepiej zrozumieć co dokładnie robi algorytm który mamy zaimplementować.

W tym zadaniu naszym celem jest implementacja algorytmu SHA-1 i nie wystarczy użyć gotowej implementacji oferowanej przez język programowania, którego używamy do rozwiązywania tych ćwiczeń. Nasza implementacja przyda się nam później do rozwiązywania kolejnych ćwiczeń, ponieważ łatwiej będzie nam modyfikować sposób w jaki działa.

Co to w ogóle jest SHA-1 i do czego służy? Sama nazwa pochodzi od Secure Hash Algorithm, a numer 1 oznacza wersję algorytmu (więcej na temat temat rodziny algorytmów SHA można przeczytać na Wikipedii -> https://pl.wikipedia.org/wiki/SHA). Sam algorytm jest funkcją skrótu albo inaczej funkcją hashującą – na podstawie danych o dowolnej długości przy pomocy pewnych operacji na tych danych oblicza krótki ciąg wartości. Algorytm SHA-1 tworzy 160-bitowy ciąg skrótu danych wejściowych.

Moja implementacja SHA-1 bazuje na pseudokodzie dostępnym na Wikipedii i wygląda następująco:

def rotate32_left(val, n):
    val = val & 0xffffffff
    return (val<<n | val >> (32-n))&0xffffffff  

def custom_sha1(msg):
    h0 = 0x67452301
    h1 = 0xefcdab89
    h2 = 0x98badcfe
    h3 = 0x10325476
    h4 = 0xc3d2e1f0
    msg_len = len(msg)
    msg = msg + bytearray([0x80])
    # must append number of 0's that msg+0's+64-bit msg size is multiple of 64  
    pad_len = (56-len(msg))%64
    msg = msg + bytearray([0] * pad_len)
    print("Msg: ", msg)
    print("Len: ", len(msg), " AppLen: ", pad_len)
    # append bit size of the message
    msg = msg + struct.pack(">Q", msg_len*8)
    print("Msg: ", msg)
    print("Len: ", len(msg))
    chunks = [msg[i:i+64] for i in range(0, len(msg)-63, 64)]
    for chunk in chunks:
        print("Chunk size:", len(chunk), "Chunk: ", chunk)
        w = [0]*80
        for i in range(0, 16):
            w[i] = int.from_bytes(chunk[4*i:4*(i+1)], byteorder='big', signed=False)
            print("W[", i,"]= ", hex(w[i]), " raw= ", chunk[4*i:4*(i+1)])
        for i in range (16, 80):
            w[i] = w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]
            w[i] = rotate32_left(w[i], 1)
        
        a = h0
        b = h1
        c = h2
        d = h3
        e = h4
        
        for i in range(0, 80):
            if i <= 19:
                k = 0x5a827999
                tmp = rotate32_left(a, 5)+((b&c)|((~b)&d))+e+w[i]+k
                
            elif i <= 39:
                k = 0x6ed9eba1
                tmp = rotate32_left(a, 5)+(b^c^d)+e+w[i]+k
            elif i <= 59:
                k = 0x8f1bbcdc
                tmp = rotate32_left(a, 5)+((b&c)|(b&d)|(c&d))+e+w[i]+k
            else:
                k = 0xca62c1d6
                tmp = rotate32_left(a, 5)+(b^c^d)+e+w[i]+k
            tmp = tmp&0xffffffff
            e = d
            d = c
            c = rotate32_left(b, 30)
            b = a
            a = tmp
            
        h0 = (h0 + a)&0xffffffff
        h1 = (h1 + b)&0xffffffff
        h2 = (h2 + c)&0xffffffff
        h3 = (h3 + d)&0xffffffff
        h4 = (h4 + e)&0xffffffff
    print("Final state: ", hex(h0), hex(h1), hex(h2), hex(h3), hex(h4))
    return (struct.pack(">L", h0)+struct.pack(">L", h1)+
            struct.pack(">L", h2)+struct.pack(">L", h3)+
            struct.pack(">L", h4))

Mamy już funkcję hashującą teraz przy jej pomocy możemy stworzyć MAC – czyli Message Authentication Code czyli skrót dzięki któremu będziemy w stanie czy otrzymane przez nas dane nie zostały przez nikogo zmodyfikowane, albo innymi słowy je uwierzytelnić. Samo obliczenie skrótu nie jest wystarczające, ponieważ ktoś mógłby podmienić dane i obliczyć nowy skrót i dostarczyć je nam zamiast danych, które faktycznie mieliśmy otrzymać. Aby rozwiązać ten problem, do danych dodawany jest klucz. Jeżeli klucz jest znany jedynie nadawcy i odbiorcy to znacznie trudniej jest osobie trzeciej podrobić wartość skrótu tak aby po podmienieniu danych skrót nadal był prawidłowy.

Samo działanie jest banalne i polega na doklejeniu klucza przed danymi. Moja implementacja poniżej:

def s4challenge28():
    print("Challenge 28")
    test_message = b'Hello, World'
    tmp_mac1 = custom_sha1(b'secret_key1'+test_message)
    tmp_mac2 = custom_sha1(b'secret_key2'+test_message)
    tmp_mac3 = custom_sha1(b'secret_key3'+test_message)
    tmp_mac4 = custom_sha1(b'secret_key4'+test_message)
    print("Hash1: ", tmp_mac1.hex())
    print("Hash2: ", tmp_mac2.hex())
    print("Hash3: ", tmp_mac3.hex())
    print("Hash4: ", tmp_mac4.hex())

W ramach testu obliczane są wartości skrótów dla tej samej wiadomości, ale przy użyciu czterech różnych kluczy. Pomimo tego, że klucze różnią się zaledwie jednym bajtem to wartość samego skrótu jest za każdym razem zupełnie inna.

Kod dostępny na https://gitlab.com/akoltys/cryptopals.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *