Open PGP

Introduzione

Il programma per la protezione crittografia dei dati più famoso è stato sicuramente PGP (Pretty Good Privacy), creato nel 1991 da Philip Zimmermann. Negli anni successivi, Zimmermann fondò un'azienda che successivamente divenne la PGP Inc.[2] Zimmerman propose nel 1997 uno standard aperto, che potesse superare alcuni problemi nei brevetti di alcuni algoritmi crittografici, chiamato OpenPGP. La IETF (Internet Engineering Task Force) pubblicò dei documenti RFC dove veniva descritto lo standard. Il più recente, del 2007, (RFC4880) è stato proposto come standard [1].

Oltre alla versione commerciale di PGP, distribuita dalla PGP Corporation, è presente anche una applicazione open source che è aderente allo standard OpenPGP chiamata GnuPG [3]. La versione GnuPG non implementa algoritmi coperti da brevetti, come ad esempio l'algoritmo simmetrico IDEA, che è invece implementato nelle versioni commerciali.

Le chiavi, pubbliche e private, sono contenute in file chiamati keyring. Infatti generalmente si impiegano due chiavi diverse per la firma e la criptazione dei dati. Normalmente la chiave per la firma è detta primaria e quella per la crittografia è detta sotto-chiave. Di default, il programma open source GnuPG genera una chiave DSA per la firma e una chiave ElGamal per la crittografia.
La chiave privata è a sua volta cifrata da una chiave simmetrica rappresentata da una passphrase. In questo modo la chiave privata, se sottratta, non può essere utilizzata. La perdita della passphrase tuttavia comporta l'inutilizzabilità della chiave, e di conseguenza la necessità di crearne una nuova.
I messaggi cifrati, le firme e le chiavi sono in formato binario. Poiché nella trasmissione dei dati è possibile che il formato binario venga alterato da alcune applicazioni o dalle transizioni attraverso alcune reti, è prassi convertire i dati binari in una stringa di caratteri ASCII attraverso un algoritmo chiamato codifica Radix-64.

La rappresentazione dei messaggi cifrati, delle firme digitali e dei file contenenti chiavi è stata standardizzata attraverso il formato OpenPGP.

Rappresentazione dati: RADIX64

Con l'algoritmo Radix-64 è possibile convertire una sequenza di byte binari in una sequenza di caratteri stampabili, che comprendono i numeri, le lettere maiuscole e minuscole e i caratteri '+', '/' e '='.

Si convertono tre byte binari (per un totale di 24 bit) in quattro caratteri da 6 bit. I 6 bit permettono di codificare 64 caratteri diversi. I tre byte binari sono considerati come una sequenza di 24 bit, che viene suddivisa in 4 blocchi da 6 bit. A seconda del valore dei gruppi da 6 bit viene inserito il carattere corrispondente:

valore codifica valore codifica valore codifica valore codifica
0 A 17 R 34 i 51 z
1 B 18 S 35 j 52 0
2 C 19 T 36 k 53 1
3 D 20 U 37 l 54 2
4 E 21 V 38 m 55 3
5 F 22 W 39 n 56 4
6 G 23 X 40 o 57 5
7 H 24 Y 41 p 58 6
8 I 25 Z 42 q 59 7
9 J 26 a 43 r 60 8
10 K 27 b 44 s 61 9
11 L 28 c 45 t 62 +
12 M 29 d 46 u 63 /
13 N 30 e 47 v (pad) =
14 O 31 f 48 w
15 P 32 g 49 x
16 Q 33 h 50 y

Se i dati terminano con 2 byte, si codificano i primi due gruppi di 6 bit e al terzo gruppo (che contiene solo 4 bit) vengono aggiunti 2 bit con valore '0'. Viene aggiunto un carattere di "pad", ovvero il carattere '='.
Se i dati terminano con 1 byte, si codifica il primo gruppo di 6 bit e al secondo gruppo (che contiene solo 2 bit) vengono aggiunti 4 bit con valore '0'. Vengono aggiunti due caratteri di "pad", ovvero il carattere '='.

Ogni gruppo di dati codificati in Radix-64 contiene i dati codificati e un valore di checksum (somma di controllo) di tipo CRC-24 lunga 24 bit preceduto dal carattere '='.

Ogni messaggio OpenPGP, che può contenere un messaggio cifrato, una firma, un messaggio firmato, delle chiavi, viene rappresentato in questo modo:

-----[testo descrittivo]-----
[header]
(linea vuota)
[DATI in formato Radix-64]
...
=[checksum]
-----[termine]-----

Il "testo descrittivo" può essere:

  • BEGIN PGP MESSAGE: messaggio firmato e/o cifrato
  • BEGIN PGP PUBLIC KEY BLOCK: chiave pubblica
  • BEGIN PGP PRIVATE KEY BLOCK: chiave privata
  • BEGIN PGP MESSAGE, PART X/Y: messaggio suddiviso in varie parti. La parte attuale è indicata con X e le parti totali sono Y
  • BEGIN PGP SIGNATURE: firma distaccata

Gli header sono coppie (chiave, valore) che sono utilizzate per fornire dati aggiuntivi sull'utilizzo o la deodifica del messaggio. La chiave è separata dal valore attraverso il carattere ':' e il carattere spazio, in questo modo
chiave: valore
Le chiavi definite sono: Version, Comment, MessageID, Hash, Charset.

Esempio: una chiave pubblica viene rappresentata in questo formato

-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.7

b2xsZXJpiGAEExECACAFAkl05cwCGyMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAK
...
tstaQtxkjxoM84s3pmZSjHacK7JqfQlKw6n5WD73h1MaJ/CrED2eQ9ZvLuOXeCAZ
e/JLFHI43hwlIviyrwCggByy9FAJyP3uwLa6X6VvM/TVFnQ=
=z3Nr
-----END PGP PUBLIC KEY BLOCK-----

L'ultima riga contiene il checksum, composto da quattro caratteri (che rappresentano 24 bit) e preceduto dal carattere '='.

Rappresentazione dei numeri

I numeri contenuti nelle chiavi sono particolarmente grandi, per consentire un buon livello di sicurezza. Nello standard OpenPGP è previsto un sistema per rappresentare numeri interi senza segno di dimensione arbitraria attraverso la cosiddetta rappresentazione Multi Precision Integer (MPI).

Un numero intero codificato in MPI è costituito da due byte che rappresentano la lunghezza del numero in bit seguiti da un certo numero di byte che rappresentano il numero vero e proprio. Si utilizza il formato big endian. La lunghezza è considerata dal bit più significativo diverso da 0.

Esempio, il numero 8438 in binario è formato da 14 bit. Allora la lunghezza sarà 14.
8438 = 10000011110110
In MPI è rappresentato come: 0x00 0x0E 0x20 0xF6, oppure 00000000 00001110 00100000 11110110

Pacchetti dati

Un pacchetto OpenPGP possiede un ottetto iniziale che specifica la versione del pacchetto, il tipo di pacchetto e la sua lunghezza.

Il primo bit, ovvero quello più significativo (big endian) ha sempre valore 1. Il secondo bit indica la versione: se è 0 la versione del pacchetto è "vecchia", mentre se è 1 il pacchetto è nella "nuova" versione.

I sei bit restanti dipendono dalla versione: nel caso della versione vecchia i quattro bit successivi (bit 5-2) indicano il tag del pacchetto e gli ultimi due la lunghezza dell'header. Nel caso il valore di questi ultimi due bit sia 0 allora l'header è costituito da due ottetti, se è 1 allora è tre ottetti e se è 2 allora l'header è lungo 5 ottetti. Il valore 3 è riservato a pacchetti di lunghezza indeterminata.

Se il pacchetto è nella nuova versione, tutti i sei bit restanti indicano il tag del pacchetto.

Nel nuovo formato la lunghezza è determinata dagli ottetti successivi: se il pacchetto ha lunghezza inferiore a 191 ottetti, allora il valore della lunghezza è inserito in un ottetto, mentre se la lunghezza è compresa tra 192 e 8383 il valore è inserito in due ottetti e se è superiore è inserito in cinque ottetti.

7 6 5 4 3 2 1 0
1 1 Tag
1 0 Tag lunghezza

Il tag è una parte importante del pacchetto, perché è un valore che indica il tipo di dati che sono contenuti.

Valore Significato
0 Riservato
1 Chiave di sessione cifrata con una chiave pubblica
2 Firma digitale
3 Chiave di sessione cifrata con una chiave simmetrica
4 Firma One-pass
5 Chiave privata
6 Chiave pubblica
7 Sottochiave privata
8 Dati compressi
9 Dati cifrati da chiave simmetrica
10 Marcatore
11 Dati literal
12 Fiducia
13 User ID
14 Sottochiave pubblica
17 Attributo Utente
18 Dati cifrati con chiave simmetrica e con protezione dell'integrità
60-63 Valori sperimentali

Pacchetto chiave di sessione cifrata con una chiave pubblica

Un pacchetto contenente una chiave di sessione cifrata può contenere delle chiavi di sessione cifrati da una chiave pubblica e/o da una chiave simmetrica. Se sono presenti più destinatari saranno inseriti più di un pacchetto con la stessa chiave di sessione cifrata da diverse chiavi pubbliche, una per destinatario. Contiene i seguenti dati (tra parentesi il numero di ottetti):

  • versione del pacchetto (1)
  • ID della chiave pubblica utilizzata (8)
  • Algoritmo asimmetrico utilizzato (1)
  • chiave di sessione cifrata
Valore Algoritmo
1 RSA (crittografia o firma)
2 RSA (crittografia)
3 RSA (firma)
16 Elgamal (crittografia)
17 DSA
18 Riservato per le Curve ellittiche
19 Riservato per ECDSA
20 Riservato
21 Riservato per Diffie-Hellman (X9.42)
100-110 Privato/Algoritmo sperimentale

Il valore della chiave di sessione viene preceduto da un identificatore che indica l'algoritmo simmetrico usato per cifrare i dati del pacchetto. Vengono aggiunti due ottetti di checksum. Questi dati sono indicati con m: nel caso si utilizzi l'algoritmo RSA si ha un numero in formato MPI del valore (me mod n), nel caso si utilizzi l'algoritmo Elgamal saranno inseriti i numeri MPI dei valori (gk mod p), (m yk mod p).

Pacchetto dati cifrati con chiave simmetrica

Questo tipo di pacchetto contiene i dati cifrati con la chiave di sessione. I dati vengono elaborati con la modalità Cipher Feedback CFB, avente un vettore di inizializzazione con tutti i bit a 0. Nei dati elaborati, il primo byte indica l'algoritmo simmetrico utilizzato.

Valore Algoritmo
0 Testo o dati non cifrati
1 IDEA
2 TripleDES
3 CAST5
4 Blowfish
5 Riservato
6 Riservato
7 AES-128
8 AES-192
9 AES-256
10 Twofish-256
100-110 Privato/Algoritmo sperimentale

Pacchetto chiave pubblica

Un pacchetto contenente una chiave pubblica (tag 6) ha il seguente formato (tra parentesi il numero di ottetti):

  • versione del pacchetto (1)
  • data di creazione della chiave in secondi successivi alla mezzanotte del 01-01-1970 (4)
  • algoritmo utilizzato (1)
  • elenco di numeri in formato MPI dei valori della chiave

La versione del pacchetto considerata è la 4, ma può essere utilizzato anche il vecchio formato versione 3. La data di creazione della chiave è rappresentata dal numero di secondi trascorsi dalla mezzanotte del 01-01-1970. Essendo 4 ottetti, questo sistema può rappresentare le date fino a 232 secondi successivi al primo gennaio 1970, ovvero circa 136 anni. L'algoritmo utilizzato fa riferimento alla stessa tabella precedente.

Devono essere implementati gli algoritmi DSA per la firma digitale ed Elgamal per la crittografia.

Di seguito a questi valori sono presenti quelli appropriati per l'algoritmo impiegato. In particolare, nel caso di una chiave pubblica RSA il valore di n e di e in formato MPI, nel caso di una chiave DSA il valore di p, q, g e y in formato MPI, nel caso di una chiave Elgamal il valore di p, g e y in formato MPI. Per il significato di questi valori vedere le pagine dedicate alla descrizione del relativo algoritmo.

Esempio:

Si considera la prima riga di un pacchetto OpenPGP contenente una chiave pubblica:

-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.7 (MingW32)

mQGiBEl05cwRBADRa3WtszKnpQLBV2jLRg7uYBf70GMLR3Wyj3DNd3H6YNDicpzL

Essendo in formato Radix-64 è necessario convertirla in formato binario. Dopo la conversione, i primi byte sono i seguenti (formato esadecimale):
0x99 0x01 0xa2 0x04 0x49 0x74 0xe5 0xcc 0x11 0x04 0x00 …
I primi tre byte sono relativi all'intestazione del pacchetto. Del primo byte (0x99 = 10011001), il bit più significativo è sempre 1, mentre il secondo indica se il formato dell'intestazione è di vecchio tipo o di nuovo tipo. In questo caso è vecchio tipo. Il tag del pacchetto, che indica il tipo di contenuto è rappresentato dai valori dei successivi 4 bit. Gli ultimi bit due sono relativi alla lunghezza dell'header.
I quattro bit che indicato il tipo di pacchetto sono 0110, ovvero il valore 6, che corrisponde ad un pacchetto contenente una chiave pubblica. Gli altri due byte (0x01 0xa2) indicano la lunghezza in ottetti del pacchetto. Il quarto byte (0x04) è il primo byte del corpo del pacchetto e contiene il numero di versione, che ha valore 4. I quattro byte seguenti (dal quinto all'ottavo: 0x49 0x74 0xe5 0xcc) contengono la data di creazione della chiave. Combinati assieme forniscono il valore di 1 232 397 772 (0x4974e5cc) secondi, che aggiunto alla data 01-01-1970 00:00:00 fornisce la data "19 gennaio 2009, 20:42:52".
Il byte successivo (0x11) contiene l'algoritmo utilizzato. Il valore decimale è 17, che corrisponde all'algoritmo DSA. Infine sono presenti i valori della chiave vera e propria, in formato MPI. Quindi i due byte successivi (0x04 0x00) rappresentano la lunghezza in bit del valore del numero primo p. Il loro valore è 0x400 = 1024 bit, in effetti la dimensione di p è proprio la lunghezza della chiave DSA.

Pacchetto Firma

Un pacchetto firma contiene una firma digitale di un messaggio. Sono previste due versioni di questo pacchetto: la versione 3 e la versione 4.

Nella versione 3 il pacchetto contiene (tra parentesi il numero di ottetti):

  • numero di versione (1)
  • lunghezza, che deve avere valore pari a 5
  • tipo firma (1)
  • data creazione firma (4)
  • ID della chiave del firmatario (8)
  • Algoritmo asimmetrico utilizzato (1)
  • Algoritmo di hash utilizzato (1)
  • 16 bit di dati (vedere sotto) (2)
  • firma digitale

Il numero di versione deve essere pari a 3, il tipo della firma è uno dei valori della tabella seguente:

Valore Tipo di firma
0x00 Firma di un documento binario. Il firmatario afferma di possedere o aver creato o certifica che non è stato modificato
0x01 Firma di un documento di testo. Il firmatario afferma di possedere o aver creato o certifica che non è stato modificato
0x02 La firma riguardo solo il contenuto di un sottopacchetto (usata nella versione 4)
0x10 Firma di una chiave pubblica. Il firmatario non fa alcuna affermazione particolare su quanto accuratamente ha controllato che il possessore della chiave sia il legittimo proprietario
0x11 Firma di una chiave pubblica. Il firmatario non ha controllato che il possessore della chiave sia il legittimo proprietario
0x12 Firma di una chiave pubblica. Il firmatario ha effettuato alcuni controlli sul possessore della chiave
0x13 Firma di una chiave pubblica. Il firmatario ha effettuato controlli significativi sul possessore della chiave
0x18 Firma di una sottochiave. La firma è effettuata dalla chiave primaria per confermare il possesso di una sottochiave
0x19 Firma di una chiave primaria. La firma è effettuata da una sottochiave per confermare il possesso da parte della chiave primaria
0x1F Firma di un sottopacchetto
0x20 Firma per revocare una chiave. La chiave revocata è quella usata per firmare, ed è l'unica firma che deve essere considerata valida
0x28 Firma per revocare una sottochiave
0x30 Firma per la revoca di un certificato. Un certificato è una firma di tipo 0x10, 0x11, 0x12, 0x13 o 0x1F.
0x40 Firma timestamp. La firma è valida solo in base al timestamp contenuto in esso
0x50 Firma di terze parti. È una firma di un pacchetto di tipo firma OpenPGP.

Per indicare l'algoritmo di hash utilizzato viene impiegata la seguente tabella

Valore Algoritmo
1 MD5
2 SHA-1
3 RIPE-MD/160
4 Riservato
5 Riservato
6 Riservato
7 Riservato
8 SHA-256
9 SHA-384
10 SHA-512
11 SHA-224
100-110 Privato/Algoritmo sperimentale

Viene effettuato un hash dei dati da firmare, assieme a cinque ottetti aggiuntivi contenenti il tipo della firma e la data di creazione. Questo valore viene utilizzato per la firma. I 16 bit (2 ottetti) più significativi sono inseriti nel pacchetto.

Nella versione 4, la più recente, i dati contenuti sono:

  • Versione (1)
  • Tipo firma (1)
  • Algoritmo asimmetrico (1)
  • Algoritmo di hash (1)
  • lunghezza dati dei sottopacchetti con hash (2)
  • sottopacchetti con hash
  • lunghezza dati dei sottopacchetti senza hash (2)
  • sottopacchetti senza hash
  • 16 bit di dati (2)
  • firma

La differenza tra i sottopacchetti con hash e quelli senza hash consiste nel fatto che viene calcolato l'hash dei primi, mentre i secondi contengono dati non protetti dalla firma, come informazioni varie. I sottopacchetti di cui viene calcolato l'hash contengono vari dati relativi alla firma.

Esempio

Si consideri il seguente pacchetto contenente la firma digitale di un file. La prima riga è la seguente:

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.7 (MingW32)

iD8DBQBJeSbM8evrY1qCR5MRAjHdAJ0VLjNrvsey+w43ANAfGRzsbR2GaQCeKclc

I dati, convertiti in formato binario sono i seguenti (in formato esadecimale):
0x3f 0x3f 0x03 0x05 0x00 0x49 0x79 0x0c 0xf1 0xeb 0xeb 0x63 0x5f 0x42 0x47 0x93 0x11 0x02 …
Il primo byte (0x3f = 10001000) indica che il formato del pacchetto è vecchio e che è una firma, poiché il tag è 0010 = 2. Il secondo byte indica la lunghezza e il terzo byte è il primo del pacchetto della firma. Il terzo byte 0x03 indica la versione 3, e quello successivo la lunghezza, che è pari a 5. Proseguendo si incontra il byte 0x00 che indica il tipo di firma (in questo caso firma di un file binario), e i quattro byte successivi ({{0x49790cf1) indicano la data sotto forma di numero di secondi trascorsi dal 01-01-1970. Successivamente si hanno gli 8 byte che rappresentano l'ID della chiave ed infine il byte che indica l'algoritmo utilizzato per la firma (0x11 = 17), che in questo caso è il DSA, e il byte che indica l'algoritmo di hash utilizzato (0x02), che è SHA-1. I byte successivi rappresentano la firma vera e propria.
Bibliography
1. J. Callas, L. Donnerhacke, H. Finney, D. Shaw, R. Thayer, "OpenPGP Message Format", IETF Network Working Group - RFC4880, 2007, Sito web: http://www.ietf.org/rfc/rfc4880.txt
2. PGP Corporation Sito web: http://www.pgp.com/index.html
3. GnuPG Sito web: http://www.gnupg.org
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License