Guida Crittografia Cosa è l'algoritmo RSA

St3ve

Utente Jade
12 Ottobre 2011
2,442
5
1,849
686
Ultima modifica:
Introduzione
RSA è uno dei primi algoritmi di crittografia asimmetrica, prende il nome da Rivest, Shamir e Adleman, le tre persone che l'hanno definito. La crittografia asimmetrica si basa sul concetto che i messaggi criptati con una chiave dovranno essere decriptati con una chiave diversa, decriptare un messaggio usando la stessa chiave utilizzata nella fase di encryption non ci riporta al messaggio originale. In un crittosistema a chiave pubblica si hanno dunque due tipi di chiave: pubblica (conosciuta da tutti) e privata (segreta). Chi vuole inviare un messaggio dovrà utilizzare la chiave pubblica associata alla persona che vuole contattare, mentre il destinatario, che è l'unico in possesso della propria chiave privata, può decriptare il crittotesto utilizzando la chiave privata.Sebbene sia stato attribuito da Rivest, Shamir e Adleman, che l'hanno pubblicato nel 1977, è risaputo che un algoritmo molto simile fosse già in uso da alcune agenzie governative che, ovviamente, non l'hanno reso pubblico.

Tra i principali vantaggi di questo sistema è lampante il fatto che non è necessaria una chiave condivisa per poter comunicare, di conseguenza non si pone il problema di come scambiarsi questa chiave: le chiavi pubbliche possono essere visibili a chiunque senza influire sulla sicurezza dell'algoritmo. Tra i principali svantaggi vi è il fatto che sono molto lenti, approssimativamente tra un crittosistema simmetrico e uno asimmetrico c'è un fattore di $$\(10^3$$\), per fare un esempio: il tempo che impiegate a cifrare qualche megabyte utilizzando RSA è paragonabile al tempo in cui impieghereste a cifrare qualche gibabyte utilizzando AES (l'attuale standard per la crittografia simmetrica). Ne consegue che la crittografia asimmetrica è principalmente utilizzata per cifrare informazioni molto piccole, come ad esempio la chiave condivisa che andremo ad utilizzare in un algoritmo di crittografia simmetrica.



Problema della fattorizzazione
RSA basa la sua sicurezza sul problema matematico della fattorizzazione: dal teorema fondamentale dell'algebra sappiamo che ogni intero $$\(n$$\) può essere scritto in modo essenzialmente unico come prodotto di numeri primi. Il problema della fattorizzazione lo possiamo esporre nel modo seguente: data una coppia di numeri primi $$\(p, q \in \text{Prime}$$\), grandi a piacere, è facile moltiplicarli per calcolare $$\(n=pq$$\) ma, dato un intero composto $$\(n=pq$$\), trovare uno dei fattori $$\(p,q \in \text{Prime}$$\) che formano $$\(n$$\) è difficile. Se vogliamo dirla in modo un po' più formale, con "facile" definiamo i problemi risolubile in tempo al più polinomiale e con "difficile" definiamo i problemi risolubili in almeno un tempo esponenziale, dev'essere un problema intrattabile anche nel caso medio e dev'essere difficilmente approssimabile, ma "facile" e "difficile" rende bene l'idea. Dall'ultima frase è chiaro che il problema della fattorizzazione è in $$\(\mathcal{F}_{\text{NP}}$$\), che dal nostro punto di vista comporta: è facile costruire un istanza del problema della fattorizzazione di cui conosciamo la risposta, ma è difficile per gli altri trovare quale sia questa risposta.


L'algoritmo
Per descrivere l'algoritmo, supponiamo che Bob voglia trasmettere un messaggio $$\(m$$\), rappresentato come numero, ad Alice.

Generazione della coppia di chiavi:
  1. Alice sceglie due numeri $$\(p, q \in \text{Prime}$$\) e calcola $$\(n = pq$$\);
  2. Alice sceglie un esponente di encryption $$\(e$$\) tale che $$\(\gcd((q-1)(p-1), e)=1$$\), un valore tipico per questo esponente è $$\(2^{16}+1$$\) perché non è troppo piccolo e permette ad un computer di velocizzare i conti;
  3. Alice calcola l'esponente di decryption, ovvero un valore $$\(d$$\) tale che $$\(de \equiv 1 \bmod{(p-1)(q-1)}$$\);
  4. Alice ha finalmente la sua coppia di chiavi, quella pubblica la posta online, quella privata la tiene al sicuro: $$\(\text{A}_\text{pub} = (n, e)$$\) e $$\(\text{A}_\text{prv} = (p, q, d)$$\).

Encryption e Decryption:
  1. Bob va su internet a scaricare la chiave pubblica di Alice $$\(\text{A}_\text{pub} = (n, e)$$\);
  2. Bob vuole cifrare un messaggio $$\(m$$\) che, senza perdita di generalizzazione, assumiamo essere un numero $$\(0 \le m < n$$\);
  3. per cifrare il messaggio $$\(m$$\), Bob calcola $$\(c \equiv m^n \bmod n$$\) e invia ad Alice $$\(c$$\);
  4. per decifrare il messaggio $$\(c$$\), Alice calcola $$\(m \equiv c^d \bmod n$$\).
Il punto 2 si può fare perché, come ben sappiamo, in un computer tutto è rappresentato da una stringa binaria. Una sequenza binaria non è nient'altro che un numero intero espresso in base due. Se questo numero è maggiore di $$\(n$$\) lo possiamo spezzare in più messaggi.


Dimostrazione
Com'è possibile che $$\(m \equiv c^d \bmod n$$\)? Facciamo prima un paio di premesse.

Funzione $$\(\phi$$\) di Eulero
La funzione $$\(\phi(n)$$\) conta i numeri $$\(1 \le k \le n$$\) tali che $$\(\gcd(k, n) = 1$$\). Per ogni intero $$\(n > 0$$\) vale
$$\[\phi(n) = n \prod_\limits{p|n} \left(1 - \frac{1}{p}\right)$$\]quindi, se abbiamo $$\(n = pq$$\) con $$\(p,q \in \text{Prime}$$\) possiamo dire $$\(\phi(n) = (p-1)(q-1)$$\). Questo concetto lo utilizziamo al punto 3 della generazione della coppia di chiavi.

Teorema di Eulero
Se $$\(\gcd(a, n) = 1$$\) allora $$\(a^{\phi(n)} \equiv 1 \bmod n$$\).

Piccolo teorema di Fermat
Per ogni intero $$\(a$$\) e ogni $$\(p \in \text{Prime}$$\) abbiamo $$\(a^{p-1} \equiv 1 \bmod p$$\).


Adesso abbiamo abbastanza informazioni per dimostrare che $$\(m \equiv c^d \bmod n$$\). Prima di tutto ci ricordiamo che $$\(c \equiv m^e \bmod n$$\) per via del punto 3 nella fase di encryption e decryption, quindi possiamo riscrivere la stessa equazione come $$\(m \equiv \left(m^e\right)^d \bmod n$$\). Poi ci ricordiamo che la potenza di una potenza è uguale al prodotto degli esponenti, quindi abbiamo $$\(m \equiv m^{de} \bmod n$$\).

Nel punto 3 della fase di generazione delle chiavi avevamo detto $$\(de \equiv 1 \bmod{(p-1)(q-1)}$$\) e, sapendo come funziona l'aritmetica modulare, possiamo dire $$\(de = 1 + k(p-1)(q-1)$$\) per qualche $$\(k \in \mathbb{N}$$\). Sostituiamo a $$\(de$$\) quanto abbiamo trovato e, ricordandoci che $$\((p-1)(q-1) = \phi(n)$$\), otteniamo $$\(m \equiv m^{1+k\phi(n)} \bmod n$$\).

A questo punto ci ricordiamo un'altra proprietà delle potenze: nel prodotto di potenze con la stessa base si sommano gli esponenti. Così riscriviamo ciò che avevamo ottenuto come $$\(m \equiv m \cdot m^{k \phi(n)} \bmod n$$\).

In fine, applichiamo il teorema di Eulero e otteniamo $$\(m \equiv m \cdot 1^k \bmod n$$\) che è esattamente dove volevamo arrivare: partendo da $$\(m \equiv c^e \bmod n$$\) siamo arrivati a $$\(m \equiv m \bmod n$$\).

$$\[m \equiv c^e \equiv m^{de} \equiv m^{1+k(q-1)(p-1)} \equiv m \cdot m^{k\phi(n)} \equiv m \cdot 1^k \equiv m \bmod n$$\]

Come troviamo i numeri primi?
Al punto 1 nella fase di generazione delle chiavi abbiamo parlato di scegliere due numeri $$\(p, q \in Prime$$\) grandi a piacere. Ovviamente più sono grandi e più è difficile fattorizzare, ma è facile trovare dei numeri primi?

Teorema dei numeri primi
Sia $$\(\pi(n)$$\) la quantità di numeri primi inferiori a $$\(n$$\), il teorema dei numeri primi ci dice che $$\(\pi(n) \sim \frac{n}{\ln n}$$\).
Quel simbolo lì si chiamo asintotico, ha un significato ben preciso, ma in questo momento ce ne sbattiamo le palle e approssimiamo il significato di quel simbolo con "circa uguale". Supponiamo che vogliamo cercare due numeri primi rappresentati da 300 cifre e, per farlo, li scegliamo a caso e verifichiamo se sono primi. Abbiamo una buona probabilità di successo? I numeri primi di 300 cifre sono tanti o sono pochi? Utilizzando questo teorema possiamo dire:
$$\[\pi(10^{301}) - \pi(10^{299}) \approx \frac{10^{301}}{\ln(10^{301})} - \frac{10^{300}}{\ln(10^{300})} \approx \frac{10^{301}}{301 \ln(10)} - \frac{10^{300}}{300 \ln(10)} \approx 1.29 \cdot 10^{298}$$\]Ovvero se prendiamo la quantità di primi formati da numeri più piccoli di 301 cifre decimali e gli sottraiamo la quantità di numeri primi formati da numeri più piccoli di 300 cifre decimali, otteniamo un numero astronomico. Conclusione: abbiamo tantissimi numeri primi grandi.

A differenza del problema della fattorizzazione, il problema di decidere se un numero è primo o composto appartiene a $$\(\text{P}$$\) (algoritmo AKS), quindi è facile. Per questioni di praticità, l'estrazione dei numeri primi random non la facciamo utilizzando AKS (che è più rilevante dal punto di vista teorico che pratico), ma utilizzando Miller-Rabin: un algoritmo probabilistico che si basa sul piccolo teorema di Fermat, con un ulteriore accorgimento che permette di beccare anche i composti che ingannano questo test a prescindere dalla base $$\(a$$\) (numeri di Carmichael). Grazie al teorema dei numeri primi, generando numeri random grandi non impieghiamo molto tempo per trovarne uno che sia anche primo. Inoltre, è molto improbabile che due persone utilizzino la stessa coppia di numeri primi.


Come calcolo l'esponente di decryption?
Al punto 3 della fase di generazione delle chiavi abbiamo parlato di calcolare il $$\(d$$\) tale che
$$\[de \equiv 1 \bmod{(p-1)(q-1)}$$\]ovvero, l'inversa moltiplicativa di $$\(e \bmod \phi(n)$$\). L'esistenza di questa inversa è data da $$\(\gcd(e, (p-1)(q-1))$$\), che è una condizione che avevamo imposto nel passo precedente. Per calcolarla possiamo utilizzare l'algoritmo di Euclide esteso. Questo argomento è già stato trattato su questo forum, quindi mi limito a riproporvi questi due post: 1 e 2.


Attacchi ad RSA
Possiamo procedere in due modi: cercare di fattorizzare $$\(n$$\), che ci porterebbe ad avere le stesse informazioni che ha Alice, oppure assumere di non essere in grado di fattorizzare e procedere per vie traverse. Calcolare $$\(\phi(n)$$\) è tanto difficile quanto fattorizzare (spiegazione).
Di seguito vi propongo alcuni degli attacchi a questo crittosistema:
  • Trial division: Se $$\(p$$\) e $$\(q$$\) sono vicini, quindi sono in un intorno (relativamente) piccolo di $$\(\sqrt{n}$$\), possiamo partire dall'intero dispari più vicino a $$\(\sqrt{n}$$\) e provare tutti i numeri da li in avanti fino a trovare un fattore di $$\(n$$\), ovvero un numero $$\(p$$\) tale per cui il resto della divisione $$\(\frac{n}{p}$$\) sia 0. Per evitare quest'attacco, $$\(p$$\) e $$\(q$$\) li scegliamo distanti tra loro (eg. uno con un bit in meno dell'altro).
  • Timing attack: Nella fase di decryption, oltre a ricordarci l'esponente $$\(d$$\), precomputiamo alcuni valori che ci permettono di sfruttare il Teorema cinese del resto per velocizzare i calcoli. Avendo accesso alla macchina che decripta e potendo decriptare diversi crittotesti da noi scelti (attacco chosen-ciphertext), cronometrando il tempo di decryption di ciascun messaggio, è possibile effettuare delle statistiche che ci permettono di ottenere delle informazioni su $$\(p$$\) e $$\(q$$\). Per evitare questo tipo di attacco, si aggiunge un calcolo random che sballa i tempi misurati.
  • Short plaintext: Se il messaggio da cifrare è troppo piccolo, è possibile scoprire cosa Bob sta dicendo ad Alice senza fattorizzare $$\(n$$\). Per evitare questo tipo di attacco, i messaggi cifrati contengono un padding, generalmente calcolato utilizzando lo schema OAEP.
  • Low exponent attack: Esiste un attacco teorico effettuabile se si usa un esponente di encryption $$\(e$$\) troppo piccolo (eg. 3), si consiglia di utilizzare $$\(2^{16}+1$$\) come esponente di encryption perché è un numero primo (quindi garantisce $$\(\gcd(e, \phi(n)) = 1$$\) indipendentemente dal valore di $$\(n$$\)) abbastanza grande e veloce da utilizzare (va incontro all'aritmetica binaria utilizzata dai computer). Si è sotto attacco anche in caso in cui l'esponente di decryption $$\(d$$\) è troppo piccolo, in questo caso bisogna cambiare $$\(e$$\).
  • Common modulus attack: Cifrare lo stesso messaggio $$\(m$$\) con tanti esponenti di encryption diversi, permette di costruire un sistema di equazioni che, con un po' di fortuna, ci permette di risalire ad $$\(m$$\).
  • Algoritmo rho di Pollard: Una descrizione del funzionamento la potete trovare in questo post.
  • Algoritmo p-1: Se $$\(p-1$$\), dove $$\(p$$\) è uno dei nostri primi, è un numero smooth (un composto formato da fattori piccoli) allora possiamo fattorizzare $$\(n$$\) utilizzando questo algoritmo. Per evitare questo algoritmo dobbiamo provare a fattorizzare $$\(p-1$$\) e verificare che non sia smooth. Una descrizione dell'algoritmo la potete trovare in questo post.
  • Algoritmo di Lanstra: Fa uso delle curve ellittiche $$\(\bmod n$$\), ovvero su un campo, ed è simile all'algoritmo p-1. Prendendo una curva ellittica random e un punto $$\(\text{P}$$\) su questa curva, se nel calcolo di $$\(kP$$\) (dove $$\(k$$\) è un numero smooth) raggiungiamo il punto all'infinito, abbiamo fattorizzato $$\(n$$\). Se $$\(n$$\) è molto grande, la probabilità di riuscire fattorizzare con questo algoritmo è bassa.
  • Setaccio quadratico: Si basa sulla fattorizzazione di Fermat ovvero se troviamo due interi $$\(x, y$$\) tali che $$\(x^2 \equiv y^2 \bmod n$$\) allora $$\(\gcd(x-y, n)$$\) è un fattore non banale di $$\(n$$\). È uno degli algoritmi più veloci per fattorizzare numeri inferiori a $$\(10^{100}$$\).
  • Crivello dei campi di numeri generale: Attualmente è l'algoritmo più efficiente per fattorizzare interi molto grandi senza proprietà particolari (eg. p-1 funziona molto bene, ma l'intero da fattorizzare deve avere delle caratteristiche ben precise). La complessità in tempo è data da $$\(O\left(e^{(c+o(1))(\log n)^{1/3}(\log \log n)^{2/3}}\right)$$\).
  • Algoritmo di Shor: Fattorizzare è facile... ma dobbiamo costruire un quantum computer. Ovviamente non è va considerato tanto come un attacco al crittosistema, quanto ad un traguardo teorico non indifferente. Di fatto non siamo nemmeno lontanamente vicini a costruire un quantum computer (non siamo nemmeno sicuri che possa essere costruito) e siamo ancora meno vicini a costruire un quantum computer capace di far girare questo algoritmo su dei numeri con una così considerevole dimensione.


Dalla teoria alla pratica
Adesso, utilizzando openssl, cerchiamo un riscontro di quanto abbiamo detto fino ad ora.
  1. Con openssl genrsa -out private_key.pem 2048 generiamo un file chiamato private_key.pem contenente una chiave di RSA da 2048 bit, ovviamente possiamo scegliere una dimensione a nostro piacimento.
  2. Con openssl rsa -pubout -in private_key.pem -out public_key.pem generiamo un file public_key.pem a partire dalla chiave privata precedentemente generata.
  3. Con openssl rsa -pubin -in public_key.pem -text -noout mostriamo a schermo la chiave pubblica, ovvero la coppia $$\((n, e)$$\)

Nel mio caso la chiave pubblica è:
Codice:
Public-Key: (2048 bit)                                        
Modulus:
    00:c1:c1:74:85:6b:62:6f:fc:62:fe:91:6e:0c:71:
    6b:a3:53:df:9c:22:c4:16:bd:df:86:92:ef:81:1f:
    0a:b9:57:29:4e:e3:05:97:5f:28:47:13:91:96:84:
    23:f9:7a:ae:37:89:7b:02:40:0c:c7:5c:84:b4:c1:
    e2:9b:17:e0:68:68:78:3d:60:ef:3a:73:4d:1b:29:
    af:84:83:5f:0c:78:0f:cb:43:bc:13:06:24:59:7f:
    37:48:bf:ed:df:45:40:69:cb:99:ec:f7:49:f9:ee:
    59:d4:3e:9d:89:7c:ea:db:b0:2d:c0:08:69:e2:f4:
    70:bd:2b:17:73:28:c5:91:4b:7b:56:36:c7:c4:e7:
    11:9f:43:8e:11:2a:78:16:2b:79:27:b4:80:5b:a3:
    23:5d:c2:29:27:34:10:27:40:5a:66:32:28:0e:96:
    90:14:85:2a:0e:fd:1c:6b:9e:54:81:5a:a1:a2:06:
    a3:2b:9b:c6:e5:bc:c3:83:f9:92:f7:02:2b:d8:ef:
    8a:76:08:17:9e:0d:2f:87:a4:b6:e9:90:a2:33:ba:
    e6:18:9b:08:c6:63:b8:d9:3c:af:06:06:44:75:db:
    c1:87:29:d3:9f:cf:e2:dd:e0:2f:7a:de:e7:d8:ad:
    bb:84:e2:46:0a:5a:e0:2d:28:98:d2:07:4b:e4:dd:
    69:c1
Exponent: 65537 (0x10001)
Il modulo è quello che precedentemente avevamo chiamato $$\(n$$\), espresso in formato esadecimale per aumentare la leggibilità, mentre l'esponente è la nostra $$\(e$$\). Poco sorprendentemente $$\(e = 65537 = 2^{16}+1$$\), avevamo detto che questo era un valore tipico.

Sempre nel mio caso, la chiave privata è:
Codice:
Private-Key: (2048 bit)
modulus:
    00:c1:c1:74:85:6b:62:6f:fc:62:fe:91:6e:0c:71:
    6b:a3:53:df:9c:22:c4:16:bd:df:86:92:ef:81:1f:
    0a:b9:57:29:4e:e3:05:97:5f:28:47:13:91:96:84:
    23:f9:7a:ae:37:89:7b:02:40:0c:c7:5c:84:b4:c1:
    e2:9b:17:e0:68:68:78:3d:60:ef:3a:73:4d:1b:29:
    af:84:83:5f:0c:78:0f:cb:43:bc:13:06:24:59:7f:
    37:48:bf:ed:df:45:40:69:cb:99:ec:f7:49:f9:ee:
    59:d4:3e:9d:89:7c:ea:db:b0:2d:c0:08:69:e2:f4:
    70:bd:2b:17:73:28:c5:91:4b:7b:56:36:c7:c4:e7:
    11:9f:43:8e:11:2a:78:16:2b:79:27:b4:80:5b:a3:
    23:5d:c2:29:27:34:10:27:40:5a:66:32:28:0e:96:
    90:14:85:2a:0e:fd:1c:6b:9e:54:81:5a:a1:a2:06:
    a3:2b:9b:c6:e5:bc:c3:83:f9:92:f7:02:2b:d8:ef:
    8a:76:08:17:9e:0d:2f:87:a4:b6:e9:90:a2:33:ba:
    e6:18:9b:08:c6:63:b8:d9:3c:af:06:06:44:75:db:
    c1:87:29:d3:9f:cf:e2:dd:e0:2f:7a:de:e7:d8:ad:
    bb:84:e2:46:0a:5a:e0:2d:28:98:d2:07:4b:e4:dd:
    69:c1
publicExponent: 65537 (0x10001)
privateExponent:
    00:8b:45:87:80:2f:c4:3e:42:e8:d5:9b:ab:c2:fd:
    f4:25:1e:b0:e9:06:84:74:e4:5e:bb:d8:fa:97:91:
    bc:9f:a4:eb:68:6c:ff:23:e3:9e:8c:18:a0:d9:d4:
    7a:17:65:fb:bc:a7:f1:e7:98:2a:97:53:05:80:f8:
    ac:ee:56:a7:53:e5:64:28:9a:78:db:46:ed:f1:cc:
    71:01:8b:7c:d2:f5:aa:44:ad:97:8f:05:27:33:bd:
    a1:fb:85:1a:73:16:d0:4a:3b:b3:95:05:dc:02:ef:
    35:5a:be:f7:76:50:78:71:19:9e:89:1e:83:1e:44:
    1e:95:9b:57:eb:cb:2e:d8:54:6b:83:51:08:94:5b:
    0e:5c:64:49:f5:a6:58:6b:3c:0c:5f:18:1d:12:20:
    02:b8:06:64:fb:b8:67:37:bd:ef:17:7c:86:9b:70:
    9a:5c:a1:78:34:95:4a:4a:47:f0:45:37:68:ee:38:
    ef:f8:fd:90:cd:2c:48:e0:3e:1c:7f:9a:fe:5e:a3:
    bc:63:10:a8:13:1a:4d:32:ec:a6:d0:e1:5d:51:4c:
    d8:fc:d5:0b:6c:0d:2a:96:10:7e:4c:f0:f6:ec:d2:
    a8:1e:d2:94:fb:da:df:42:9e:26:b5:36:91:ba:70:
    2e:28:52:ea:34:fe:11:24:5e:aa:c0:5d:1e:c2:7a:
    cc:01
prime1:
    00:f7:a9:cd:72:b9:5f:93:18:de:a7:4b:5f:31:93:
    aa:e0:15:df:71:25:62:7f:f5:bb:f0:85:51:c9:a1:
    0a:99:fc:05:8e:b0:3d:ba:51:b0:83:2c:29:ae:8e:
    bd:6e:f0:33:e4:51:d8:04:30:05:9e:aa:18:d9:51:
    3f:41:2a:d3:a0:5d:ad:8a:d5:94:07:9c:a8:3b:93:
    88:6c:21:54:49:92:7f:a5:e7:dd:8e:7d:b6:c5:4b:
    d4:ce:0c:e8:6c:86:4d:bc:b6:4b:04:a7:9a:63:9c:
    99:a6:0a:c3:f5:4b:bc:00:f9:3f:70:82:92:43:12:
    ee:fe:31:41:6e:cf:94:49:d1
prime2:
    00:c8:47:1c:dc:6a:d3:d7:b1:be:e1:8b:8e:e6:55:
    df:86:db:c4:dd:3f:5a:c3:0e:3e:46:b4:e5:7f:95:
    76:99:ab:d2:a9:1b:45:21:98:82:54:08:ba:43:74:
    a8:68:cf:79:4e:ca:bf:9e:57:86:e3:4a:06:43:d1:
    6f:ee:35:b2:85:dd:48:11:8b:78:a1:cc:79:0e:ec:
    40:d8:43:07:56:b9:64:3c:1d:cb:c1:f7:ad:e4:bb:
    51:e6:82:21:73:de:8c:30:10:a7:93:38:e6:60:d0:
    8f:5f:0d:bc:b3:4a:22:9c:d3:ed:ae:fd:9e:b6:de:
    9f:56:db:3f:34:b5:54:2c:f1
exponent1:
    00:cc:81:dd:d1:bf:3b:29:b7:5e:9c:5c:83:d5:e7:
    6f:31:ad:3e:1e:2b:55:c3:fe:41:dc:21:ea:e0:89:
    67:bc:b1:bc:51:10:3d:58:ae:7e:08:43:1e:84:33:
    00:40:2c:7f:5d:29:2c:2c:81:0f:12:ee:b8:a8:33:
    0b:fd:9f:04:b3:a6:c7:58:1e:77:27:35:f8:41:81:
    d9:24:18:fd:85:e7:40:1a:da:75:3c:65:98:d8:20:
    7f:30:4a:be:5e:67:24:a8:11:0b:af:63:4a:fc:b8:
    62:b4:16:ad:ab:cf:87:06:72:cb:2c:f7:d5:19:27:
    f8:4b:d4:96:88:8f:46:8f:11
exponent2:
    00:aa:c1:de:fc:9b:64:95:61:ea:12:99:8f:b4:19:
    81:64:95:1a:4b:5d:4a:00:46:b3:98:4b:81:e4:fe:
    c5:49:0e:73:a5:55:27:e3:16:0a:00:a6:14:51:91:
    34:49:70:a3:c8:27:1b:ab:60:8b:14:5d:37:ec:38:
    b4:8b:50:63:2e:b9:55:4a:08:35:49:16:72:1c:e4:
    18:01:8b:5c:c5:77:79:db:bb:b7:cf:60:62:76:43:
    b3:f7:a9:f9:e5:b5:d8:a6:de:7c:52:0c:d0:77:b9:
    3a:94:5b:bd:08:cc:6b:a8:75:15:10:ff:12:84:6e:
    59:42:8f:da:5b:11:43:7c:b1
coefficient:
    04:57:4a:37:8a:d1:96:86:01:04:9f:a3:c0:aa:ec:
    17:bf:6e:5e:e0:63:8b:18:b9:ab:5c:22:45:6e:83:
    5f:cd:80:a5:de:08:b9:35:b9:b3:29:34:36:f0:11:
    91:c9:99:9e:6d:b4:ee:27:65:9a:92:15:a9:fc:84:
    0e:e9:8c:01:83:a8:14:51:93:23:45:be:66:33:ba:
    89:8b:7d:7a:d5:1f:91:cf:60:e4:ec:10:75:32:65:
    34:c0:70:82:a9:62:7e:36:a3:3e:cc:c6:d3:80:14:
    e8:fe:bd:c9:d8:4a:43:d0:6c:45:5a:41:ed:1b:38:
    a0:02:11:29:32:11:46:7e

Avevamo detto che la chiave privata era data dalla tripla $$\((p,q,d)$$\), ma qui abbiamo molte più informazioni:
  • modulus è la nostra $$\(n$$\)
  • publicExponent è la nostra $$\(e$$\)
  • privateExponent è la nostra $$\(d$$\)
  • prime1 e prime2 sono rispettivamente $$\(p$$\) e $$\(q$$\)
  • exponent1, exponent2 e coefficientsono dei dati che vengono utilizzati dal teorema cinese del resto per velocizzare la fase di decryption
    • $$\(\text{exponent1} = d \bmod{p-1}$$\);
    • $$\(\text{exponent2} = d \bmod{q-1}$$\);
    • $$\(\text{coefficient} = q^{-1} \bmod p$$\).

Per completezza, ecco come cifrare e decifrare utilizzando le nostre due chiavi:
  1. Supponiamo che vogliamo cifrare "Ciao inforge", anzitutto creiamo un file con: echo "Ciao inforge" > plaintext.txt
  2. Come ormai ben sappiamo, per cifrare si utilizza la chiave pubblica, quindi facciamo: openssl rsautl -encrypt -pubin -inkey public_key.pem -oaep -in plaintext.txt -out ciphertext.enc
  3. Con il comando precedente abbiamo detto di cifrare usando RSA, utilizzando la chiave pubblica contenuta in public_key.pem, utilizzando il padding OAEP (Optimal Asymmetric Encryption Padding), il contenuto del file chiamato plaintext.txt e di salvare il crittotesto nel file ciphertext.enc.
  4. Se vogliamo decifrare, utilizzando la chiave privata, facciamo: openssl rsautl -decrypt -inkey private_key.pem -oaep -in ciphertext.enc e otteniamo in output Ciao inforge.

Firma digitale con RSA
Una firma digitale di un messaggio $$\(m$$\) è una stringa di bit che associa un messaggio con l'entità che l'ha originato. Tale firma dev'essere verificabile, ovvero chiunque dev'essere in grado di verificare l'autenticità di una firma senza l'intervento del firmatario. Lo scopo di una firma digitale è garantire:
  • Autenticità: Il destinatario può verificare l'identità del mittente.
  • Integrità: Nessuno dev'essere in grado di modificare un documento firmato senza compromettere la validità della firma.
  • Non ripudio: Il mittente non può disconoscere un documento da lui firmato.

Esistono due tipologie di firma: con appendice e con recupero di messaggio. Le firme con appendice sono in allegato al messaggio stesso, quelle con recupero di messaggio invece ti permettono di ottenere il messaggio nel momento in cui effettui la verifica.

Per firmare si fa uso degli algoritmi crittografici a chiave pubblica, l'idea è quella di cifrare con la propria chiave privata e di decifrare con la chiave pubblica (l'opposto di quello che si fa normalmente). Vediamo come utilizzare RSA per effettuare una firma con recupero di messaggio:
  1. Alice genera la sua coppia di chiave $$\(\text{A}_\text{pub} = (n,e)$$\) e $$\(\text{A}_\text{prv} = (p,q,d)$$\) nel modo descritto precedentemente.
  2. Alice firma un messaggio $$\(m$$\) calcolando $$\(f \equiv m^d \bmod n$$\) e lo pubblica.
  3. Bob recupera il messaggio calcolando $$\(m \equiv f^e \bmod n$$\), è consapevole che si tratta di un messaggio di Alice perché ha utilizzato la coppia $$\((n,e)$$\) presenti nella chiave pubblica di Alice.

La dimostrazione è analoga a quella precedente. Firmare un messaggio in questo modo è poco conveniente, abbiamo detto che la crittografia asimmetrica è generalmente più lenta di un fattore di $$\(10^3$$\) rispetto alla crittografia simmetrica, firmare un messaggio molto grande (eg. un filmato) richiederebbe troppe risorse sia per la fase di firma che per quella di verifica. Per ovviare a questo inconveniente si fa uso delle funzioni di hash: un hash è una stringa di pochi bit che rappresenta un messaggio, lo possiamo generare molto velocemente e non richiede alcuna chiave. Ha delle proprietà tali per cui è difficile falsificare un hash. Procediamo nel modo seguente
  1. Alice genera $$\(h(m)$$\) (lo hash del messaggio $$\(m$$\)).
  2. Alice firma $$\(h(m)$$\) come descritto in precedenza ed pubblica il messaggio originale allegando la firma dell'hash.
  3. Bob genera $$\(h(m)$$\) (non è richiesta alcuna chiave) e verifica che tale hash corrisponde alla firma decriptata utilizzando la chiave pubblica di Alice.
In questo caso abbiamo una firma digitale con appendice: ricordiamo che nascondere il messaggio non è un requisito della firma digitale, noi vogliamo soltanto certificare l'identità del mittente.

Con RSA è anche possibile effettuare una blinded signature, ovvero possiamo firmare qualcosa che non ci appartiene e di cui non conosciamo il contenuto. Visto che la firma digitale voleva solo essere un argomento marginale di questa discussione, per il momento non mi dilungo in ulteriori spiegazioni. Se qualcuno fosse interessato al suo funzionamento, me lo faccia sapere e modificherò questo post spiegandone il funzionamento.


PGP / GPG
Se vogliamo comunicare, normalmente non faremo uso di Openssl, ma PGP (Pretty Good Privacy). PGP è uno dei più famosi programmi per cifrare, decifrare, firmare ed effettuare altre operazioni legate al mondo della privacy, GPG (Gnu Privacy Guard) è l'implementazione free (as in freedom) di PGP. Vediamo come utilizzare GPG, da linea di comando, per generare la nostra coppia di chiavi.
  1. Con gpg2 --full-gen-key avvia una sessione interattiva per generare la chiave.
  2. GPG supporta molti algoritmi, la prima cosa che ci viene chiesto è il tipo di chiave che vogliamo: con 1 sceglimo RSA and RSA (master key per la firma e chiave subordinata per la cifratura).
  3. Dopodiché ci viene chiesta la dimensione della chiave, possiamo scegliere un valore tra 1024 e 4096 (in futuro potremo creare chiavi anche più grandi), se non ci sono motivi particolari possiamo andare sul sicuro utilizzando la chiave più grande possibile: 4096.
  4. Adesso ci viene chiesta la validità della chiave: questo parametro è solo una formalità, dal punto di vista algoritmico non c'è niente che possa scadere. Se qualcuno prova a mandarci qualcosa utilizzando una chiave pubblica scaduta, gli verrà notificato che l'utente di quella chiave pubblica potrebbe non essere più attivo. Visto che noi non siamo interessati a questa cosa mettiamo 0 almeno la chiave non scade mai. In ogni caso, potremmo modificare questo valore successivamente.
  5. Dovremo inserire i nostri dati: nome, email e (opzionalmente) un commento, questi dati saranno associati alla nostra chiave pubblica. Io ho inserito: "Fake St3ve (this is a comment) <[email protected]>".
  6. Ci viene chiesta una passphrase, ovvero la password mnemonica che ci permette di non doverci ricordare la tripla $$\(p, q, d$$\) per poter utilizzare la chiave.
  7. Viene generata la chiave, ovvero tutto ciò che abbiamo descritto in "Generazione della coppia di chiavi" dell'algoritmo. Per avere una buona randomizzazione ci viene chiesto di fare altro nel frattempo, come ad esempio muovere il mouse, pigiare qualche tasto e cose di questo tipo. Visto che abbiamo chiesto una chiave di 4096 bit, potrebbe volerci qualche minuto per generare una chiave sicura, nel frattempo continuiamo ad utilizzare il computer per fare altro: più utilizziamo il computer e meno tempo ci vorrà, dobbiamo aumentare l'entropia. Finito questo processo, abbiamo la nostra coppia di chiavi.
  8. Con gpg --list-keys possiamo controllare la presenza delle nostre chiavi.

E adesso vediamo come firmare un messaggio utilizzando le nostre chiavi. È un procedimento che possiamo fare anche con openssl, lo facciamo con gpg semplicemente per vedere il funzionamento di questo software che è più adatto agli usi pratici.
  1. Vogliamo creare una firma con appendice, il messaggio può essere grande a piacere (eg. possiamo firmare un video di diversi gigabyte), GPG si occuperà di generare l'hash e di firmarlo. Per riportarvi gli output, utilizzerò il file "plaintext.txt" ottenuto con il comando: echo "Ciao inforge" > plaintext.txt.
  2. Firmiamo facendo gpg --clearsign plaintext.txt, che genera il file plaintext.txt.asc.
  3. Verifichiamo la firma utilizzando gpg --verify plaintext.txt.asc.

Il file plaintext.txt.asc conterrà:
Codice:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

Ciao inforge
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2

iQIcBAEBCgAGBQJXjk6ZAAoJEM48TTliuSd34zEP/3N1b1qDLAV5fJURkcqzwYYy
PjMRf/jkCm2EU38L96iSZpcOAx3tRvKoWSbuojIAKg8UDERDf6PZsuvLTU2kEVOs
wWkBUqYetOoM5c/h8jMw3+S+IBjc0iRpd49kIQ9NPoKxQvNWCpIYkl3+6NfyqxAe
e/brXkFrqpabHYZ/h1SSGbvJ/3GKUEInhgfm0Rp+1EDg7IzMFJoRtG2ioBaeMsqS
GyUMM7mBlfiDupzG6jfjcZlRnOJduFv4LRmbq3RK7hUD5ZDuStTKNV98Z/dutit9
UMgSavSMkay0zEsXAk6g4uVgKre25TKiFphq/WosCsACUlmtAlUiXYy6p/fIXzb6
hq0CutM2O6L8OOjr1+USy8WTS08+8MBJTpcBtXX2tuph+Qwgpr/C2ssRQRgZMK3K
UoJIfRGErgm3EUoW4TWluYLhjdW82NaMYkeDClCad5KPRbuSw0KrzCr0Do9wovEh
kZ2OPrC9F17JUyv0rOWEb5/QGMIawUbYc9q43IakMBYuXatSCcUTbXgRHnckx4U7
A7P77a3ze8FsE40AYE21b50pOHYCMhec3BF6UCmjKK3OwyyoizL2ADxlOchlYHWZ
JvayZSicv60t32xNW/7aIBew7xvdt9MlEghk7adHsqJd7vnb1h1bJCrh7afNwnkT
WzoxqMFQwjwdXXrYFHFV
=y8w6
-----END PGP SIGNATURE-----

E quando controlleremo a chi appartiene questa firma, nel punto 3, otterremo:
Codice:
gpg: Signature made Tue 19 Jul 2016 06:00:25 PM CEST using RSA key ID CE3C4D3962B92777
gpg: Good signature from "Fake St3ve (this is a comment) <[email protected]>" [ultimate]
gpg: WARNING: not a detached signature; file 'plaintext.txt' was NOT verified!

Il warning che otteniamo serve solamente ad avvisarci che è una firma con appendice: non siamo sicuri che il contenuto di plaintext.txt sia esatto, ma abbiamo verificato a chi appartiene il messaggio "Ciao inforge" che appare nel file plaintext.txt.asc. Provando a falsificare il messaggio contenuto in plaintext.txt.asc, per esempio scrivendo "Ciamo inforge!" otterremmo:
Codice:
gpg: Signature made Tue 19 Jul 2016 06:00:25 PM CEST using RSA key ID CE3C4D3962B92777
gpg: BAD signature from "Fake St3ve (this is a comment) <[email protected]>" [ultimate]


Attacchi ad RSA: l'implementazione
Ecco alcuni attacchi implementati in C, ho utilizzato la libreria GMP per svolgere le operazioni matematiche sui big numbers. Se volete compilarli dovrete linkare questa libreria. Su GCC è sufficiente aggiungere come parametro -lgmp.

Trial division:
C:
#include <stdio.h>
#include <gmp.h>

int main(int argc, char *argv[])
{
    mpz_t n, p, q, rest;
    mpz_inits(n, p, q, rest, NULL);

    if (argc != 2) {
        fprintf(stderr, "Wrong parameters: ./trial_division <decimal number>\n");
        return 1 ;
    }

    mpz_set_str(n, argv[1], 10);
    mpz_sqrt(p, n);

    if (mpz_even_p(p))
        mpz_add_ui(p, p, 1);

    do {
        mpz_tdiv_qr(q, rest, n, p);
        mpz_sub_ui(p, p, 2);
    } while (mpz_cmp_ui(rest, 0) != 0);

    mpz_add_ui(p, p, 2);

    mpz_mul(n, p, q);
    gmp_printf("p = %Zd\nq = %Zd\np*q = %Zd\n", p, q, n);

    return 0;
}

Fermat:
C:
#include <stdio.h>
#include <gmp.h>

int main(int argc, char *argv[])
{
    mpz_t n, a, b, p, q;
    mpz_inits(n, a, b, p, q, NULL);

    if (argc != 2) {
        fprintf(stderr, "Wrong parameters: ./fermat <decimal number>\n");
        return 1 ;
    }

    mpz_set_str(n, argv[1], 10);
    mpz_sqrt(a, n);

    do {
        mpz_add_ui(a, a, 1);
        mpz_mul(b, a, a);
        mpz_sub(b, b, n);
    } while (!mpz_perfect_square_p(b));

    mpz_sqrt(b, b);
    mpz_sub(p, a, b);
    mpz_add(q, a, b);

    mpz_mul(n, p, q);
    gmp_printf("p = %Zd\nq = %Zd\np*q = %Zd\n", p, q, n);

    return 0;
}

Downloads:


Se trovate qualche errore o se volete delle informazioni aggiuntive, avvisatemi pure!
 
Gran bell'articolo. Una piccola precisazione:
  • Algoritmo p-1: Se $p-1$, dove $p$ è uno dei nostri primi, è un numero smooth (un composto formato da fattori piccoli) allora possiamo fattorizzare $n$ utilizzando questo algoritmo. Per evitare questo algoritmo dobbiamo provare a fattorizzare $p-1$ e verificare che non sia smooth. Una descrizione del funzionamento la potete trovare in questo post.
Nel post si parla dell'algoritmo rho di Pollard (che ha d'altronde inventato anche l'algoritmo p-1) che, nonostante sia anch'esso importante soprattutto se utilizzato tramite l'iterazione di Floyd, è distinto dall'algoritmo p-1.

L'algoritmo p-1, a grandi linee, è il seguente:

1) Sia $p-1$ $B$-smooth, ovvero sia fattorizzabile completamente tramite potenze prime tutte $\leq B$. Determiniamo $k$ in modo tale da essere divisibile per tutti gli interi $\leq B$ (tale numero può essere determinato effettuando il minimo comune multiplo di tutti i numeri minori o uguali a $B$). Allora $k$ è un multiplo di $p-1$.
2) Scegliamo casualmente $a$ intero positivo, e supponiamolo coprimo con $n=p*q$, allora basta determinare $\gcd(a^k-1, n)$ sperando che sia diverso da $1$ o da $n$, che sarà dunque, per quanto detto precedentemente, uguale ad un fattore di $n$.
 
Nel post si parla dell'algoritmo rho di Pollard (che ha d'altronde inventato anche l'algoritmo p-1) che, nonostante sia anch'esso importante soprattutto se utilizzato tramite l'iterazione di Floyd, è distinto dall'algoritmo p-1.
Hai ragione. Quando ho visto quel post non avevo ancora terminato di scrivere questo thread, ho letto Pollard e ho buttato dentro il link senza pensarci.
Grazie per la segnalazione, ho corretto il link aggiungendo un riferimento al tuo post.
 
Come ti avevo già scritto su skype, un esempio di implementazione sul setaccio quadratico, che alla fine è efficiente solo per numeri dai 40 ai 100, andrebbe solo bene se aggiunto, anche se forse un po' complesso.
Alla fine è una delle guide più complete che io abbia visto, complimenti.
Approvo.
 
Ultima modifica:
Un'osservazione e un paio di correzioni (di errori di battitura), iniziando con l'osservazione.

E' chiaro che fattorizzando $n$ si possa risalire immediatamente a $\varphi(n)$, con la formula presente nel post originale, che è quello che serve per decrittare dei messaggi, ma resta la possibilità che ci siano altri metodi per calcolare $\varphi(n)$ che aggirano la fattorizzazione e sono più efficenti, questo non è il caso, e si vede facilmente.
Supponiamo $n=pq$ quindi $\varphi(n)=(p-1)(q-1)$, possiamo risalire facilmente alla fattorizzazione di $n$ con queste informazioni?
Sì, posso costruire un'equazione di secondo grado, le cui radici sono $p$ e $q$ che so risolvere:
$\begin{align}
(x-p)(x-q) & = x^2-(p+q)x+pq \\
& = x^2+((p-1)(q-1)-pq-1)x+pq \\
& = x^2+(\varphi(n)-n-1)x+n \\
\end{align}
$

Dunque calcolare $\varphi(n)$ o fattorizzare $n$ sono problemi con la stessa difficoltà (facile, difficile e così via sono stati usandi in modo impreciso ma formalizzabile fino a qua).


Per l'algoritmo Rho di Pollard nella discussione che hai linkato cercavo più di fare un esempio che di spiegare l'algoritmo, ma pensavo di scriverne una fatta bene in questi giorni, magari anche una sull'algoritmo di Euclide


Qualche errore di battitura:
Nella parte sul piccolo teorema di Fermat c'è uno spazio di troppo prima di "in" nella formula k \ in \mathbb{N} e non è per ogni k, ma per qualche k

In questa frase manca un non: "Per questioni di praticità, l'estrazione dei numeri primi random la facciamo utilizzando AKS"

Nella parte su Encryption e Decryption c'è un "messaggio" che dovrebbe essere "messaggi"

Nel teorema di Eulero hai dimenticato delle parentesi graffe è c'è un $a^\varphi(n)$ che dovrebbe essere $a^{\varphi(n)}$
 
  • Mi piace
Reazioni: St3ve
Un'osservazione e un paio di correzioni (di errori di battitura), iniziando con l'osservazione.
Grazie per averlo letto fino in fondo e per avermi segnalato gli errori, li ho sistemati. Non ho avuto tempo di rileggerlo e, piuttosto di ritardare la pubblicazione, l'ho postato direttamente.

Con "facile" e "difficile" sono volutamente rimasto vago, formalizzarlo in modo preciso avrebbe richiesto un paragrafo sostanzioso che sarebbe stato offtopic rispetto a quello di cui volevo parlare. Ci sono inoltre delle sottigliezze, per esempio in crittografia difficilmente utilizziamo degli algoritmi basati su dei problemi NP-complete: sia la fattorizzazione che il logaritmo discreto, su cui si basa El Gamal, sono dei problemi attualmente collocati in NP, ma non in P e nemmeno in NP-complete.
Dicendo facile o difficile possiamo rendere l'idea senza senza mettere troppa carne al fuoco: facile è quello che risolviamo in millisecondi, difficile è quello che risolviamo in secoli.

PS.
Sarebbe bello aver la possibilità di mettere il codice javascript in un link o in un bottone a mo dì spoiler. Capisco che potrebbe comportare qualche problema di sicurezza, ma se fosse fatto apposta solo per il codice che renderizza LaTeX?
Taggo @murdercode
 
Sisi mi riferivo al mio post con "facile" e "difficile", sono d'accordo con il non appesantire troppo il post che già come lo hai scritto ora è bello sostanzioso, anche perché mettendosi a parlare di complessità bene bisognerebbe anche dimostrare che le operazioni svolte da Alice per crittare sono semplici etc. (Che non è particolarmente complicato, ma non necessario in questa sede)
 
Ultima modifica:
Aggiornamento:
  • aggiunta l'implementazione della fattorizzazione di fermat e della trial division.

Challenge:
Ho cifrato un testo utilizzando una chiave pubblica vulnerabile ad uno dei due attacchi che ho implementato. Data la chiave e il ciphertext, provate a scoprire qual'è il messaggio che ho cifrato.

Questa è la mia chiave pubblica:
Codice:
-----BEGIN PUBLIC KEY-----
MGYwDQYJKoZIhvcNAQEBBQADVQAwUgJLME8dYhAPRsdik238gzG/D6nlABNemSXC
Wbzh/eHd30oRX58N5/F6aeUZkO2CogL8VrOninCzgsjhdsN2bYnQe828/FYvJPFD
whU7AgMBAAE=
-----END PUBLIC KEY-----

Il messaggio cifrato ve lo passo su un file, almeno siamo sicuri che non si perde alcun bit. Eccolo: ciphertext.enc.

Il modo di utilizzo dei due eseguibili lo trovate chiaramente scritto in rosso nel source code. Se qualcuno volesse compilarli anche per windows e per mac, li aggiungerei volentieri all'open post.
 
Aspetta, ma questo è solo n perché l'esponente si assume essere 65537? Altrimenti non ho capito dove finisce n ed inizia e...
 
Ultima modifica:
Aspetta, ma questo è solo n perché l'esponente si assume essere 65537? Altrimenti non ho capito dove finisce n ed inizia e...
No, questa è proprio tutta la chiave pubblica espressa nel formato formato PEM. Questo formato viene utilizzato perché rende la coppia di valori (n, e) facilmente trasmissibile in forma testuale.

Per ottenere i due valori si può usare il comando: openssl rsa -pubin -in ./public_key.pem -text
Codice:
Public-Key: (598 bit)
Modulus:
    30:4f:1d:62:10:0f:46:c7:62:93:6d:fc:83:31:bf:
    0f:a9:e5:00:13:5e:99:25:c2:59:bc:e1:fd:e1:dd:
    df:4a:11:5f:9f:0d:e7:f1:7a:69:e5:19:90:ed:82:
    a2:02:fc:56:b3:a7:8a:70:b3:82:c8:e1:76:c3:76:
    6d:89:d0:7b:cd:bc:fc:56:2f:24:f1:43:c2:15:3b
Exponent: 65537 (0x10001)
writing RSA key
-----BEGIN PUBLIC KEY-----
MGYwDQYJKoZIhvcNAQEBBQADVQAwUgJLME8dYhAPRsdik238gzG/D6nlABNemSXC
Wbzh/eHd30oRX58N5/F6aeUZkO2CogL8VrOninCzgsjhdsN2bYnQe828/FYvJPFD
whU7AgMBAAE=
-----END PUBLIC KEY-----

Il modo per trovare queste informazioni a partire dalla chiave in formato PEM lo potevate trovare anche scritto nel capitolo "Dalla teoria alla pratica". Potete risolvere questa challenge senza saper programmare, se avete bisogno di fare calcoli particolari basta che me lo scrivete e vi scrivo io un programma per ricavarli.
 
Ah, ecco, è il formato PEM che mi mancava, comunque credo di aver fatto casino, perché convertito in decimale il modulo mi esce
783043446527998885057777551422574646400265911062734892671066140593392111168030653924167859170782301869187232942068994859941995835955695562880076060116054380000419436077398481835323, che non è un semiprimo
 
ah, sì, certo, non è la mia serata, prima non so leggere, poi non so fare copia/incolla...
884897421472115537917747231336552184760275623836170257877531445387178515989782091158584067
884897421472115537917747231336552184760275623836170257877531445387178515989783091158584169
 
Adesso però bisogna decifrare il messaggio, che era la vera richiesta di questa challenge. Qui è richiesto un po' di google e un po' di calcoli.

Se sapete cosa calcolare e come calcolarlo (la formula), potete chiedermi di svolgere i calcoli. Alla fine rilascerò il codice per fare tutti i calcoli necessari e modificherò l'open post per spiegare come procedere per risolvere questo tipo di esercizi. Magari prima di farlo aspetto qualche giorno, almeno se qualcuno volesse qualche altro messaggio/chiave da decifrare può esercitarsi (anche se, in ogni caso, leggendo i commenti si potrà facilmente capire come procedere).

Ovviamente prima di postare la challenge, ho provato io stesso a risolverla. Quindi vi dico già che, se si sa cosa cercare, con google lo si trova.
 
Lascio che lo decifri qualcun altro, lo ho già dovuto fare all'ultimo esame di algebra e mi provoca brutti ricordi ahahaha (poi sono pigro, come ogni matematico del resto)

Comunque adesso si tratta di calcolare $\varphi(n)$ che poi risulta essere
783043446527998885057777551422574646400265911062734892671066140593392111168030653924167857400987458924956157106574532186837626315404447890539560305053163605643387456512216164667088
che effettivamente è coprimo con 65537 quindi 65537 è invertibile in $\mathbb{Z}/\varphi(n)\mathbb{Z}$ e il suo inverso (indicato con $d$ nel post originale), che a questo punto è l'ultimo pezzo mancante della chiave privata lo calcoliamo con l'algoritmo di Euclide esteso.

Adesso decidiamo quante lettere sono state cifrate alla volta (assumendo un alfabeto di 26 lettere vengono cifrate $k$ lettere alla volta dove $k$ è il massimo intero tale che $26^k<n$) e decrittiamo calcolando $c^d \quad \mathrm{mod} \quad n$, che si può fare velocemente via "exponentation by squaring", prendendo il modulo ad ogni passaggio così da non aver mai a che fare con numeri troppo grossi, dove $c$ sono i gruppi di lettere cifrate
 
Hey
ho trovato la soluzione per fattorizzare l'RSA in tempi brevissimi @St3ve ti ricordi quel numero che mi desti di 39 cifre
140089267639071478002348819284711337427
si fattorizza in 1655 step
 
Visto che ormai sono passati diversi giorni e la partecipazione è stata quella che è stata, vi descrivo come andava risolta la challange.

Soluzione:
  1. Salviamo la chiave pubblica su un file, chiamiamolo public_key.pem
  2. Per ricavare i valori di modulo ed esponente utilizziamo il comando openssl rsa -pubin -in ./public_key.pem -text e abbiamo il seguente output
    Codice:
    Public-Key: (598 bit)
    Modulus:
        30:4f:1d:62:10:0f:46:c7:62:93:6d:fc:83:31:bf:
        0f:a9:e5:00:13:5e:99:25:c2:59:bc:e1:fd:e1:dd:
        df:4a:11:5f:9f:0d:e7:f1:7a:69:e5:19:90:ed:82:
        a2:02:fc:56:b3:a7:8a:70:b3:82:c8:e1:76:c3:76:
        6d:89:d0:7b:cd:bc:fc:56:2f:24:f1:43:c2:15:3b
    Exponent: 65537 (0x10001)
    writing RSA key
    -----BEGIN PUBLIC KEY-----
    MGYwDQYJKoZIhvcNAQEBBQADVQAwUgJLME8dYhAPRsdik238gzG/D6nlABNemSXC
    Wbzh/eHd30oRX58N5/F6aeUZkO2CogL8VrOninCzgsjhdsN2bYnQe828/FYvJPFD
    whU7AgMBAAE=
    -----END PUBLIC KEY-----
    Quindi adesso abbiamo il valore di n in esadecimale, il valore dell'esponente pubblico e sappiamo che è una chiave di 598 bit.
  3. Vogliamo fattorizzare n, vi ho postato l'implementazione di due attacchi e vi avevo già specificato che questa chiave era vulnerabile. L'unica cosa che bisogna fare è convertire il modulo da esadecimale a decimale, esistono diversi tools per farlo. Otteniamo:
    Codice:
    n = 783043446527998885057777551422574646400265911062734892671066140593392111168030653924167859170782301869187232942068994859941995835955695562880076060116054380000419436077398481835323
  4. Utilizziamo la fattorizzazione di Fermat (potete scaricarla dall'open post) per fattorizzarlo e, nel giro di qualche millisecondo, otteniamo questo output:
    Codice:
    p = 884897421472115537917747231336552184760275623836170257877531445387178515989782091158584067
    q = 884897421472115537917747231336552184760275623836170257877531445387178515989783091158584169
    p*q = 783043446527998885057777551422574646400265911062734892671066140593392111168030653924167859170782301869187232942068994859941995835955695562880076060116054380000419436077398481835323
  5. A questo c'è l'unico passo che non potevate svolgere solamente leggendo l'open post: usando google cerchiamo il modo per dire ad openssl "usa questa chiave privata". Arriviamo alla conclusione che bisogna riempire un file di questo tipo:
    Codice:
    asn1=SEQUENCE:private_key
    
    [private_key]
    version=INTEGER:0
    n=INTEGER:
    e=INTEGER:
    d=INTEGER:
    p=INTEGER:
    q=INTEGER:
    exp1=INTEGER:
    exp2=INTEGER:
    coeff=INTEGER:
    Il valore di n ed e li sappiamo dalla chiave pubblica, p e q li abbiamo trovati utilizzando la fattorizzazione di Fermat, l'esponente di decription d abbiamo imparato a calcolarlo. Cosa sono exp1, exp2 e coeff? Guadiamo la sezione Dalla teoria alla pratica e ci ricordiamo: "exponent1, exponent2 e coefficient sono dei dati che vengono utilizzati dal teorema cinese del resto per velocizzare la fase di decryption. Indicano: exponent1 $d \mod{p-1}$, exponent2 $d \mod{q-1}$ e coefficient $q^{-1} \mod p$".
  6. Svolgiamo i calcoli, nel post precedente vi avevo già detto: "potete risolvere questa challenge senza saper programmare, se avete bisogno di fare calcoli particolari basta che me lo scrivete e vi scrivo io un programma per ricavarli" e ora sapete quali sono i calcoli che dovete chiedermi. Ed ecco quale sarebbe stata la mia risposta:
    Codice:
    d = 671830415716661569921018263560125574750759905292377134748331141483831194880253835840853784179930784562786819017434111575654865039426227938983306168924948294577445310164111917284369
    exp1 = 580421524586444765986697594236299628702717064281939223269015124934901549763534533967591639
    exp2 = 291716263956315610978728488228423790404592136548521574399866745160596179836722213169373193
    coeff = 141958374056550744339313075485573767367530234218540867002933041156091024390942588976930653
  7. Compiliamo i campi del file precedente e salviamolo in un file chiamato key.txt
    Codice:
    asn1=SEQUENCE:private_key
    
    [private_key]
    version=INTEGER:0
    n=INTEGER:783043446527998885057777551422574646400265911062734892671066140593392111168030653924167859170782301869187232942068994859941995835955695562880076060116054380000419436077398481835323
    e=INTEGER:65537
    d=INTEGER:671830415716661569921018263560125574750759905292377134748331141483831194880253835840853784179930784562786819017434111575654865039426227938983306168924948294577445310164111917284369
    p=INTEGER:884897421472115537917747231336552184760275623836170257877531445387178515989782091158584067
    q=INTEGER:884897421472115537917747231336552184760275623836170257877531445387178515989783091158584169
    exp1=INTEGER:580421524586444765986697594236299628702717064281939223269015124934901549763534533967591639
    exp2=INTEGER:291716263956315610978728488228423790404592136548521574399866745160596179836722213169373193
    coeff=INTEGER:141958374056550744339313075485573767367530234218540867002933041156091024390942588976930653
  8. Nella ricerca su google dove abbiamo scoperto il formato di questo file, abbiamo trovato anche il comando necessario per generare la chiave: openssl asn1parse -genconf key.txt -out private_key.der per generare la chiave privata in formato der e openssl rsa -in newkey.der -inform der -text -check per verificare che sia tutto andato a buon fine.
  9. A questo punto decriptiamo il file con openssl rsautl -decrypt -inkey newkey.der -keyform der -oaep -in ciphertext.enc e otteniamo come output Prima prova attacco RSA. Challenge risolta, era quello il messaggio segreto.


Come promesso, allego il programma necessario per fare tutti i calcoli necessari per riempire il file necessario per generare la chiave pubblica. Prende in input i due numeri primi e l'esponente di encryption e ricava tutti gli altri dati.
C:
#include <stdio.h>
#include <gmp.h>

int main(int argc, char *argv[])
{
    mpz_t n, p, q, e, p_1, q_1, rho, d, d1, d2, invq;
    mpz_inits(n, p, q, e, p_1, q_1, rho, d, d1, d2, invq, NULL);

    if (argc != 4) {
        fprintf(stderr, "Wrong parameters: ./keydata <decimal p> <decimal q> <decimal e> \n");
        return 1 ;
    }

    mpz_set_str(p, argv[1], 10);
    mpz_set_str(q, argv[2], 10);
    mpz_set_str(e, argv[3], 10);

    mpz_mul(n, p, q);

    gmp_printf("n = %Zd\n\n", n);
    gmp_printf("e = %Zd\n\n", e);

    mpz_sub_ui(p_1, p, 1);
    mpz_sub_ui(q_1, q, 1);
    mpz_mul(rho, p_1, q_1);

    mpz_invert(d, e, rho);
    gmp_printf("d = %Zd\n\n", d);

    gmp_printf("p = %Zd\n\n", p);
    gmp_printf("q = %Zd\n\n", q);

    mpz_mod(d1, d, p_1);
    mpz_mod(d2, d, q_1);
    gmp_printf("exp1 = %Zd\n\n", d1);
    gmp_printf("exp2 = %Zd\n\n", d2);

    mpz_invert(invq, q, p);
    gmp_printf("coeff = %Zd\n\n", invq);

    return 0;
}

Downloads: Linux


Se siete interessati a provare a rieseguire gli stessi passi con un altra chiave ed un altro crittotesto, fatemelo sapere. Ma, adesso che sapete la soluzione, è veramente una banalità.
 
@St3ve aggiungi quello in python senza importare moduli e far uso di strane funzioni per un esempio più conciso.
Python:
def trial(n):
   far = []
   div = 2 # setto il divisore a 2
   while n > 1:
      if n%div == 0:
         n = n/div
         # print(div,end=' ') # stampa in una riga singola
         far.append(div)
      else:
         div += 1
   return far

ar = trial(789)
print(' '.join(map(str, ar)))
(stampa pure i fattori).