
In questo nuovo tutorial mostreremo come è possibile usare facilmente le librerie di crittografia CommonCrypto di Apple, mediante delle API sviluppate da Rob Napier e messe a disposizione su un repository github. Il tutorial è una demo app che fa vedere in modo semplice come è possibile crittografare e decifrare un messaggio con una password. Il progetto è aggiornato alla versione Xcode 4.6. Ringraziamo lo sviluppatore Gabriele Merlonghi, creatore delle applicazioni Fast & Tutor e GPS Alarm Pro (che potete trovare su App Store), il quale ci ha concesso la pubblicazione di questa utilissima guida.
La prima cosa da dire è che RNCryptor non contiene gli algoritmi di crittografia, ma si interpone tra essi (contenuti nel Security.framework) e voi che volete usarli.
La mia curiosità è stata quella di voler capire come fare a scambiare dei dati cifrati tra 2 device, di cui almeno uno è un iOS. Conoscevo già il potente e decantato algoritmo AES, ma quando mi sono imbattuto googlando su RNCryptor ho scoperto che l’AES è solo una parte del lavoro da fare. In verità per fare un sistema di questo genere c’è da considerare oltre che la cifratura dei dati in se, anche aspetti legati al trattamento delle password, il vettore di inizializzazione, il padding e la signature.
Se si parte dall’ipotesi di avere un device A e un device B tra cui scambiare messaggi cifrati e si ha in mano l’implementazione su entrambi i device, tutti questi aspetti sopra esposti possono sembrare opzionali, nel senso che si può pensare di scegliere una soluzione anche molto basilare per proteggere i messaggi. Tuttavia, dopo l’approfondimento che ho eseguito, ho dedotto che non è così.
Vi riporto letteralmente una conversazione che ho avuto direttamente con Rob Napier:
“What is your goal with this encryption? If you are trying to hide the traffic from someone who has both devices, they can just jailbreak the phone and read it after decryption. If you have a hard-coded password anywhere in the iPhone software, then they can pull that out and decryption is then trivial. So what problem are you trying to solve? Who is your attacker and what is their goal? You can’t secure a system without knowing these things.
Rob”
Tradotto:
- Qual è l’obiettivo di questa encryption?
- Se tu stai tentando di nascondere il traffico tra 2 punti da qualcuno che ha entrambi i devices, questi possono jailbrekkare l’iPhone e leggere il messaggio dopo che è stato decifrato.
- Se tu hai una password embeddata da qualche parte nella tua applicazione, (sempre tramite il jailbreak) questa può essere letta e quindi poi decifrare i messaggi sarà un gioco da ragazzi
- Quindi, qual è il problema che stai tentando di risolvere ?
- Chi sono i tuoi avversari negli attacchi e quali sono i loro obiettivi ?
- Non puoi proteggere un sistema senza sapere queste cose.
Mi ritrovo completamente con queste affermazioni, anche se sono molto “pesanti” e non hanno una soluzione univoca per qualsiasi sistema. Tutto ciò per dare una idea di quanto realmente potrebbe essere complessa e piena di “sfumature” una soluzione di protezione informatica.
Le principali linee guida che hanno portato allo sviluppo di RNCryptor sono riassunte in questo articolo di Rob Napier sul suo blog, è molto interessante e vi consiglio di leggervelo.
Adesso andiamo sul concreto e cerchiamo di capire cosa fa questo cuscinetto tra noi e le CommonCrypto che si chiama RNCryptor.
Questo da una interfaccia in Objective-C facile da usare verso le funzioni di crittografia AES, occupandosi di vari aspetti quali quelli di:
- password stretching: stiramento della password tramite l’algoritmo PBKDF2
- salting: letteralmente “mettere il sale” ovvero dati random
- IV: vettore di inizializzazione
- HMAC: controllo di integrità del messaggio e firma
Ci sono semplicemente 2 metodi RNEncryptor, RNDecryptor che provvedono a scrivere e leggere il messaggio in un formato definito da Rob Napier ovvero in RNCryptor data format.
RNCryptor contiene anche una interfaccia verso il formato OpenSSL (metodi RNOpenSSLEncryptor, RNOpenSSLDecryptor) ma questo formato non è raccomandabile perché secondo Napier manca di alcuni aspetti di sicurezza. E’ presente in RNCryptor solo a scopo di compatibilità. Questo aspetto non è trattato nel presente tutorial perché non è stato da me approfondito al momento.
Nella documentazione di RNCryptor sono riportati 2 esempi d’uso: uno sincrono e l’altro asincrono. Nel presente tutorial è stato effettuato solo l’esempio sincrono.
L’algoritmo AES Rijndael
La storia recita che l’algoritmo Rijndael divenne l’Advanced Encryption Standard (AES) a seguito di una selezione tra vari algoritmi per ben 5 anni di studi. Nel novembre del 2001 il NIST lo scelse come standard di riferimento per cifratura.
Non mi dilungo sugli aspetti tecnico-matematici dell’algoritmo, perché qui (su wikipedia) trovate un adeguato approfondimento.
Vorrei sottolineare solo gli aspetti che secondo me sono più rappresentativi:
- l’AES è molto sicuro perché non è stato ancora mai bucato, ci sono solo studi teorici di possibili attacchi che risultano al momento non realizzabili in pratica
- l’AES è molto veloce perché ha una matematica relativamente semplice, e per questo risulta facilmente implementabile anche su microcontrollori 8 bit, ottenendo dei buoni risultati prestazionali
- l’AES è molto compatto, solo qualche Kbytes di codice per la sua implementazione
L’AES ha come principale caratteristica quella di essere un algoritmo di cifratura a blocchi con lunghezza fissa di 128bit (16 bytes). La lunghezza della chiave può essere di 3 tipi: 128, 192 o 256 bit. In questo tutorial è stato scelto di usare la lunghezza di 128 bit della chiave.
Cifrare a blocchi di 16bytes va bene, ma come si gestisce il flusso di cifratura di tutto un messaggio che ovviamente può essere maggiore di questa dimensione ?
Ebbene ci sono 3 aspetti molto importanti da considerare:
- la modalità di funzionamento di un cifrario a blocchi
- il vettore di inizializzazione
- il padding dell’ultimo blocco
Questi aspetti vengono spiegati nei successivi paragrafi.
La modalità di funzionamento di un cifrario a blocchi
Ci sono varie tecniche più o meno sicure, ma quella certamente più sconsigliata (perché altamente insicura) è in verità quella che ci verrebbe più naturale di pensare: scompongo il messaggio a blocchi di 16bytes e cifro ad uno ad uno tutti i blocchi con la stessa chiave.
Questa modalità è chiamata ECB (Electronic Codebook) e questa immagine rende bene l’idea del perché è insicura:

La modalità più sicura è solo leggermente più complicata, si chiama CBC (Cipher Block Chaining) e consiste semplicemente nell’introdurre un passaggio in più di XOR tra il blocco in chiaro e il risultato del blocco cifrato al passaggio precedente.
Questa immagine descrive perfettamente quanto esposto:

Il vettore di inizializzazione
La modalità CBC è quella implementata in RNCryptor, ma questa fa nascere un altro problema: quando si cifra il primo blocco, cosa occorre scegliere in ingresso all’operazione di XOR dato che non è stata fatta alcuna iterazione precedente ?
La risposta è il vettore di inizializzazione, ma come lo scelgo ?
La cosa più sicura da fare è generare una sequenza casuale di 16 byte ed utilizzarla come IV. Ovviamente per poter essere decifrato correttamente il messaggio, occorre trasportare nel payload stesso (in chiaro) anche l’IV usato in questa fase di cifratura oltre che il risultato della cifratura, ma questo non rappresenta un problema di sicurezza.
L’uso di un IV predeterminato e quindi costante è insicuro ed altamente sconsigliato. Qui l’articolo di wikipedia che spiega in dettaglio le principali modalità di funzionamento a blocchi.
Il padding dell’ultimo blocco
Non ci sono ragioni legate alla sicurezza, ma solo una ottima tecnica facilmente implementabile e autoconsistente anche in fase di decrypt, alla base della scelta dell’algoritmo PKCS#7.
In pratica quando l’ultimo blocco dati da cifrare risulta inferiore di 16bytes si aggiungono un numero di byte in modo da riempirlo e tutti con un valore uguale al numero di byte aggiunti.
ESEMPIO: se l’ultimo blocco dati risulta da 10 byte, si aggiungono 6 byte tutti con il valore 0x06.
Nel caso particolare che l’ultimo blocco dati fitta perfettamente in 16bytes, allora si aggiunge alla catena di cifratura un intero altro blocco da 16 bytes aventi tutti valore 0x10.
Così facendo è facile intuire come in fase di decrypt occorre scartare gli ultimi bytes che non fanno parte quindi del messaggio originale.
Qui è l’articolo di wikipedia che spiega il padding nella crittografia.
Password Stretching & Salting
L’algoritmo AES è alla base del motore crittografico, ma abbiamo visto come questo ha il vincolo che la chiave è di una dimensione prefissata ben precisa (nel tutorial è stato scelto 128 bit = 16 bytes).
Inoltre è irragionevole pensare che una persona umana, inserisca mnemonicamente una numerazione esadecimale di 16 valori per cifrare e decifrare un messaggio.
Queste semplici considerazioni sono alla base delle definizioni di “key” e di “password”.
La key è la chiave a 128 bit usata fisicamente per l’algoritmo di cifratura, mentre la password è la frase mnemonica che un utente può digitare ed è umanamente facile da ricordare.
Quindi si pone il problema di come trasformare una password generica e originalmente in formato NSString in una key dalla lungezza fissa. Per fortuna ci viene in aiuto l’algorimo di password stretching (stiramento) denominato PBKDF2 che letteralmente significa Password-Based Key Derivation Function 2.
Anche in questo caso non mi dilungo sui dettagli tecnico-matematici che si possono approfondire leggendo questo articolo su wikipedia. Mi limito però a indicare gli aspetti essenziali:
- un algoritmo di password stretching è veramente molto affascinante, perché puoi definire come password una stringa di qualsiasi lunghezza e qualsiasi carattere e questo la stira allungandola o riducendola per farla fittare sempre nella lunghezza prefissata (16 bytes nel nostro caso)
- una password “umana” può essere facilmente attaccabile per tentativi mediante l’uso di un database di password più famose. Per proteggersi da questo tipo di attacco è fondamentale utilizzare il salt (letteralmente “salatura”) che consiste nell’indicare al PBKDF2 un certo numero di bytes random (in RNCryptor sono previsto 8 bytes) che l’algoritmo provvederà a mescolare insieme alla password originale per generare quindi la key. Ovvio che per ricostruirsi la stessa key in fase di decrypt occorre conoscere il “salt” utilizzato in origine. Anche questo (come per l’IV) transiterà in chiaro insieme al messaggio cifrato.
- un parametro da non sottovalutare è il numero di iterazioni che l’algoritmo può fare per derivare la key. Per avere una elevata sicurezza, Rob Napier consiglia di usare 10000 iterazioni ed infatti questo valore è preimpostato nella struttura di configurazione di RNCrypto che vedremo più avanti.
Qui trovate l’articolo di wikipedia che spiega in dettaglio l’algoritmo PBKDF2
E’ importante osservare che essendo il salt e l’IV dati random, una molteplice cifratura dello stesso messaggio di origine porta ad avere rispettivi messaggio cifrati mai uguali tra loro. Questo rafforza notevolmente la sicurezza.
Questo è un interessante portale in cui è implementato in javascript l’algorito di derivazione delle chiavi PBKDF2. Molto utile per verificare se la vostra implementazione è corretta. Fate attenzione, perchè non è scritto esplicitamente, ma i campi (password e salt) sono trattati come ASCII e non come binari.
HMAC
L’HMAC è un codice di autenticazione del messaggio (Message Autentication Code) e può essere utilizzato per verificare sia l’integrità che l’autenticazione di un messaggio. Si tratta di un algoritmo di hashing con una chiave segreta. Come con qualsiasi MAC, esso può essere utilizzato con normale funzione hash, come MD5 o SHA-1, che si traduce in metodi come HMAC-MD5 o HMAC-SHA1. Come con qualsiasi funzione di hashing, la forza dipende dalla qualità della funzione di hashing, e il risultante numero di bit di codice.
Nel presente tutorial si è scelto di usare l’algoritmo HMAC-SHA1 che ha la caratteristica di avere 20 bytes di uscita. La lunghezza della chiave utilizzata è pari a 16 bytes come anche per la cifratura, ma è diversa, nel senso che viene derivata dalla stessa password iniziale, ma con un salt randomico, quindi differente. Per questo motivo quindi le key derivate sono in totale 2, una usata per l’encryption e l’altra per l’HMAC.
L’hashing viene fatto dell’intero payload della struttura dati d’uscita dell’RNCrypt e i 20 bytes vengono accodati andando a costituire così il pacchetto finale.
L’HMAC non modificare il messaggio cifrato, ma ha lo scopo di essere utilizzato prima della fase di decrypt per validare il messaggio entrante.
Questo è un interessante portale in cui sono implementati vari algoritmi di HMAC. Molto utile per verificare se la vostra implementazione è corretta.
Conclusioni
Con la teoria, e per oggi, è tutto. Nella prossima parte di questa guida vedremo un esempio pratico di implementazione di crittografia nelle nostre app iOS con RBCryptor.
Un ringraziamento alla redazione di devAPP per avermi dato la possibilità di pubblicare questo tutorial sul questo blog. Non dimenticatevi di accedere anche al mio blog personale http://www.megabri.com e al portale delle mie App http://www.mgproductions.it.
Gabriele Merlonghi
2 Responses to “T#113 – Uso della crittografia nelle applicazioni iPhone e iPad con RNCryptor – Parte 1”
18 Marzo 2013
TechnoHouse » Tutorial sull’uso della Crittografia in applicazioni iOS[…] T#113 – Uso della crittografia nelle applicazioni iPhone e iPad con RNCryptor – Parte 1 […]
23 Marzo 2013
TechnoHouse » Tutorial sull’uso della Crittografia in applicazioni iOS (parte 2)[…] chi è rimasto appassionato di questo argomento seguendo qui il primo articolo, devAPP mi ha pubblicato la seconda parte in cui viene descritto passo passo il tutorial pratico […]