W tym poście opiszę sposób rozwiązania zadania EVEN RSA CAN BE BROKEN z sekcji Cryptography picoCTF.
- from sys import exit
- from Crypto.Util.number import bytes_to_long, inverse
- from setup import get_primes
- e = 65537
- def gen_key(k):
- """
- Generates RSA key with k bits
- """
- p,q = get_primes(k//2)
- N = p*q
- d = inverse(e, (p-1)*(q-1))
- return ((N,e), d)
- def encrypt(pubkey, m):
- N,e = pubkey
- return pow(bytes_to_long(m.encode('utf-8')), e, N)
- def main(flag):
- pubkey, _privkey = gen_key(1024)
- encrypted = encrypt(pubkey, flag)
- return (pubkey[0], encrypted)
- if __name__ == "__main__":
- flag = open('flag.txt', 'r').read()
- flag = flag.strip()
- N, cypher = main(flag)
- print("N:", N)
- print("e:", e)
- print("cyphertext:", cypher)
- exit()
Na samym początku, po deklaracji potrzebnych bibliotek mamy zdefiniowaną wartość e. Oznacza ona wartość eksponenta publicznego dla RSA. Najczęściej jest to liczba całkowita dodatnia. Wprowadzona liczba 65537, jest najczęściej stosowaną wartością, dla większości implementacji.
Wartość 65537 jest często stosowana ponieważ jest liczbą pierwszą, ma mało jedynek dla zapisu binarnego (10000000000000001), co przyśpiesza obliczenia. Dzięki dostatecznie dużej wartości zapewnia odpowiedni poziom bezpieczeństwa.
Następna jest funkcja odpowiedzialna za generowanie kluczy. Na samym początku losowane są dwie liczby pierwsze p oraz q o długości k/2. Dalej obliczany jest N czyli tzw. moduł RSA. Po nim następuje obliczenie Następnie następuje obliczenie funkcji Eulera:
φ(n)=(p-1)(q-1)
Który jest wykorzystywany do obliczenia d, czyli odwrotności modularnej czyli e dzielone przed funkcje Eulera.
Po uruchomieniu tego programu otrzymuje następujący wynik działania:
- N: 22321130498067817633914018534979291148516929113331749263752822928968742441808984572395615385890948558208946804442522443015782843622882350503183464582229078
- e: 65537
- cyphertext: 7429146572356614276991625770288474207334266336657246449826510284146138168542796284195883914974571879854693673736977402030651149358945018430121241138148897
Teraz należy rozłożyć otrzymane N. Korzystam ze strony factordb.com. Po wklejeniu do niej N otrzymuję następującą informację:
Z tego wynika, że liczba ma 155 cyfr dziesiętnych. Standardowo, gdy wykorzystywane jest 1024 bitowe N to otrzymujemy około 310 cyfr. Więc to N jest krótsze od klasycznego RSA.
Faktoryzacja pokazała, że N nie jest iloczynem dwóch dużych liczb pierwszych nieparzystych. Jedną z tych liczb jest 2 (czyli najmniejsza liczba pierwsza), druga to duża liczba pierwsza składająca się z 155 cyfr.
Powoduje to uproszczenie funkcji Eulera do postaci:
φ(n)=(2-1)(q-1)=(q-1)
Status FF oznacza, że liczba została w pełni rozłożona na czynniki pierwsze.
Teraz wystarczy przygotować program w Python, który odwróci operacje:
- def long_to_bytes(n):
- return n.to_bytes((n.bit_length() + 7) // 8, 'big')
- N = 22321130498067817633914018534979291148516929113331749263752822928968742441808984572395615385890948558208946804442522443015782843622882350503183464582229078
- e = 65537
- c = 7429146572356614276991625770288474207334266336657246449826510284146138168542796284195883914974571879854693673736977402030651149358945018430121241138148897
- q = N // 2
- phi = (2 - 1) * (q - 1)
- d = pow(e, -1, phi)
- m = pow(c, d, N)
- print(long_to_bytes(m).decode())
Wynik działania programu:
- picoCTF{<flaga>}
- ** Process exited - Return Code: 0 **
Można to też napisać w C. Należy dołączyć bibliotekę gmp,h w celu obsługi typów GMP do przechowywania dużych liczb.
- #include <stdio.h>
- #include <gmp.h>
- #include <stdint.h>
- #include <stdlib.h>
- #include <string.h>
- int main() {
- //zmienne dla dużych liczb
- mpz_t N, e, c, p, q, phi, d, m;
- mpz_inits(N, e, c, p, q, phi, d, m, NULL);
- // Inicjalizacja liczb z tekstu dziesietnego
- mpz_set_str(N, "22321130498067817633914018534979291148516929113331749263752822928968742441808984572395615385890948558208946804442522443015782843622882350503183464582229078", 10);
- mpz_set_str(e, "65537", 10);
- mpz_set_str(c, "7429146572356614276991625770288474207334266336657246449826510284146138168542796284195883914974571879854693673736977402030651149358945018430121241138148897", 10);
- //p jako 2 bo wyszło z wyliczeń na stronie
- mpz_set_ui(p, 1);
- //q = N / 2
- mpz_fdiv_q_ui(q, N, 2);
- //q = q - 1
- mpz_sub_ui(q, q, 1);
- // phi = (2-1)*(q-1)
- mpz_mul(phi, p, q);
- //d = e^-1 mod phi
- if (mpz_invert(d, e, phi) == 0) {
- printf("ERROR\n");
- return 1;
- }
- //m = c^d mod N
- mpz_powm(m, c, d, N);
- size_t count;
- char *str = mpz_export(NULL, &count, 1, 1, 1, 0, m);
- printf("Flag: %.*s\n", (int)count, str);
- free(str);
- mpz_clears(N, e, c, p, q, phi, d, m, NULL);
- return 0;
- }
Otrzymany wynik pokrywa się z tym wygenerowanym z pierwszego programu.