Protezione dei dati iOS a riposo crittografia

In questo post, esamineremo gli usi avanzati della crittografia per i dati utente nelle app iOS. Inizieremo con uno sguardo di alto livello sulla crittografia AES, quindi esamineremo alcuni esempi su come implementare la crittografia AES in Swift.

Nell'ultimo post, hai imparato come memorizzare i dati utilizzando il portachiavi, che è utile per piccole informazioni come chiavi, password e certificati. 

Se si memorizza una grande quantità di dati personalizzati che si desidera rendere disponibili solo dopo l'autenticazione dell'utente o del dispositivo, è consigliabile crittografare i dati utilizzando un framework di crittografia. Ad esempio, potresti avere un'app che può archiviare i messaggi di chat privati ​​salvati dall'utente o le foto private scattate dall'utente o che possono archiviare i dettagli finanziari dell'utente. In questi casi, probabilmente vorrai usare la crittografia.

Esistono due flussi comuni nelle applicazioni per la crittografia e la decrittografia dei dati dalle app iOS. O l'utente viene presentato con una schermata di password, o l'applicazione viene autenticata con un server che restituisce una chiave per decrittografare i dati. 

Non è mai una buona idea reinventare la ruota quando si parla di crittografia. Pertanto, useremo lo standard AES fornito dalla libreria iOS Common Crypto.

AES

AES è uno standard che crittografa i dati in base a una chiave. La stessa chiave utilizzata per crittografare i dati viene utilizzata per decrittografare i dati. Esistono diverse dimensioni di chiave e AES256 (256 bit) è la lunghezza preferita da utilizzare con i dati sensibili.

RNCryptor è un popolare wrapper di crittografia per iOS che supporta AES. RNCryptor è un'ottima scelta perché ti consente di essere subito operativo senza doversi preoccupare dei dettagli sottostanti. È anche open source in modo che i ricercatori della sicurezza possano analizzare e controllare il codice.  

D'altra parte, se la tua app si occupa di informazioni molto sensibili e ritieni che la tua applicazione sia mirata e incrinata, potresti voler scrivere la tua soluzione. La ragione di ciò è che quando molte app usano lo stesso codice, può rendere più facile il lavoro dell'hacker, consentendo loro di scrivere un'app cracking che trova schemi comuni nel codice e applica patch a loro. 

Tieni presente, tuttavia, che scrivere la tua soluzione rallenta solo un utente malintenzionato e impedisce gli attacchi automatici. La protezione che stai ottenendo dalla tua implementazione è che un hacker avrà bisogno di dedicare tempo e dedizione a far crollare la tua app da solo. 

Sia che scegliate una soluzione di terze parti o decidiate di farvi da soli, è importante essere informati su come funzionano i sistemi di crittografia. In questo modo, puoi decidere se un particolare framework che desideri utilizzare è davvero sicuro. Pertanto, il resto di questo tutorial si concentrerà sulla scrittura della tua soluzione personalizzata. Con la conoscenza che imparerai da questo tutorial, sarai in grado di dire se stai usando un particolare framework in modo sicuro. 

Inizieremo con la creazione di una chiave segreta che verrà utilizzata per crittografare i tuoi dati.

Crea una chiave

Un errore molto comune nella crittografia AES consiste nell'utilizzare la password di un utente direttamente come chiave di crittografia. Cosa succede se l'utente decide di utilizzare una password comune o debole? In che modo costringiamo gli utenti a utilizzare una chiave abbastanza casuale e sufficientemente forte (ha abbastanza entropia) per la crittografia e quindi invitali a ricordarla? 

La soluzione è allungamento chiave. Lo stretching chiave deriva una chiave da una password tagliandola più volte con un pizzico di sale. Il sale è solo una sequenza di dati casuali, ed è un errore comune omettere questo sale - il sale dà alla chiave la sua entropia di vitale importanza, e senza il sale, la stessa chiave sarebbe derivata se la stessa password fosse usata da qualcuno altro.

Senza il sale, un dizionario di parole potrebbe essere usato per dedurre le chiavi comuni, che potrebbero quindi essere utilizzate per attaccare i dati dell'utente. Questo è chiamato "attacco dizionario". Per questo scopo vengono utilizzate tabelle con chiavi comuni che corrispondono a password non salate. Si chiamano "tavoli arcobaleno".

Un altro trabocchetto quando si crea un sale è usare una funzione di generazione di numeri casuali che non è stata progettata per la sicurezza. Un esempio è il rand () funzione in C, a cui è possibile accedere da Swift. Questa uscita può essere molto prevedibile! 

Per creare un sale sicuro, useremo la funzione SecRandomCopyBytes per creare byte casuali crittograficamente sicuri, vale a dire numeri difficili da prevedere. 

Per utilizzare il codice, devi aggiungere quanto segue nell'intestazione del bridging:
#importare

Ecco l'inizio del codice che crea un sale. Aggiungeremo a questo codice mentre procediamo:

var salt = Dati (numero: 8) salt.withUnsafeMutableBytes (saltBytes: UnsafeMutablePointer) -> Void in let saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes) // ... 

Ora siamo pronti per fare stretching chiave. Fortunatamente, abbiamo già una funzione a nostra disposizione per eseguire lo stretching effettivo: la funzione di derivazione chiave basata su password (PBKDF2). PBKDF2 esegue una funzione molte volte per derivare la chiave; aumentando il numero di iterazioni si espande il tempo necessario per operare su un set di chiavi durante un attacco di forza bruta. Si consiglia di utilizzare PBKDF2 per generare la chiave.

var setupSuccess = true var key = Dati (ripetizione: 0, conteggio: kCCKeySizeAES256) var salt = Dati (numero: 8) salt.withUnsafeMutableBytes (saltBytes: UnsafeMutablePointer) -> Void in let saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes) se saltStatus == errSecSuccess let passwordData = password.data (utilizzando: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer) in let derivationStatus = CCKeyDerivationPBKDF (CCPBKDFAlgorithm (kCCPBKDF2), password, passwordData.count, saltBytes, salt.count, CCPseudoRandomAlgorithm (kCCPRFHmacAlgSHA512), 14271, keyBytes, key.count) se derivationStatus! = Int32 (kCCSuccess) setupSuccess = false  else setupSuccess = false

Tasto sul lato server

Potresti chiederti ora dei casi in cui non desideri richiedere agli utenti di fornire una password all'interno della tua app. Forse si stanno già autenticando con un unico schema di accesso. In questo caso, fare in modo che il server generi una chiave AES a 256 bit (32 byte) utilizzando un generatore sicuro. La chiave dovrebbe essere diversa per diversi utenti o dispositivi. Durante l'autenticazione con il server, è possibile passare al server un dispositivo o un ID utente su una connessione protetta e può inviare la corrispondente chiave indietro. 

Questo schema ha una grande differenza. Se la chiave proviene dal server, l'entità che controlla quel server ha la capacità di essere in grado di leggere i dati crittografati se il dispositivo oi dati sono stati mai ottenuti. C'è anche il potenziale per la chiave di essere trapelato o esposto in un secondo momento. 

D'altra parte, se la chiave è derivata da qualcosa che solo l'utente conosce, la password dell'utente, solo l'utente può decodificare tali dati. Se si stanno proteggendo informazioni come dati finanziari privati, solo l'utente dovrebbe essere in grado di sbloccare i dati. Se tali informazioni sono comunque note all'entità, potrebbe essere accettabile che il server sblocchi il contenuto tramite una chiave sul lato server.

Modalità e IV

Ora che abbiamo una chiave, cifriamo alcuni dati. Esistono diverse modalità di crittografia, ma utilizzeremo la modalità consigliata: cipher block concatenamento (CBC). Questo funziona sui nostri dati un blocco alla volta. 

Un errore comune con CBC è il fatto che ogni successivo blocco di dati non crittografato è XOR'd con il blocco crittografato precedente per rendere più forte la crittografia. Il problema qui è che il primo blocco non è mai unico come tutti gli altri. Se un messaggio da crittografare dovesse iniziare come un altro messaggio da crittografare, l'output crittografato iniziale sarebbe lo stesso, e ciò darebbe a un utente malintenzionato un indizio per capire quale potrebbe essere il messaggio. 

Per aggirare questa potenziale debolezza, inizieremo a salvare i dati con un cosiddetto vettore di inizializzazione (IV): un blocco di byte casuali. L'IV sarà XOR con il primo blocco di dati utente, e poiché ogni blocco dipende da tutti i blocchi elaborati fino a quel punto, assicurerà che l'intero messaggio sarà crittografato in modo univoco, anche se ha gli stessi dati di un altro Messaggio. In altre parole, i messaggi identici crittografati con la stessa chiave non produrranno risultati identici. Quindi, mentre sali e IV sono considerati pubblici, non dovrebbero essere sequenziali o riutilizzati. 

Useremo la stessa sicurezza SecRandomCopyBytes funzione per creare il IV.

var iv = Data.init (conteggio: kCCBlockSizeAES128) iv.withUnsafeMutableBytes (ivBytes: UnsafeMutablePointer) in let ivStatus = SecRandomCopyBytes (kSecRandomDefault, kCCBlockSizeAES128, ivBytes) if ivStatus! = errSecSuccess setupSuccess = false

Mettere tutto insieme

Per completare il nostro esempio, useremo il CCCrypt funzione con entrambi kCCEncrypt o kCCDecrypt. Poiché stiamo usando un codice a blocchi, se il messaggio non si adatta bene a un multiplo della dimensione del blocco, dovremo dire alla funzione di aggiungere automaticamente il padding alla fine. 

Come al solito nella crittografia, è meglio seguire gli standard stabiliti. In questo caso, lo standard PKCS7 definisce come eseguire il rilievo dei dati. Diciamo alla nostra funzione di crittografia di utilizzare questo standard fornendo il KCCOptionPKCS7Padding opzione. Mettendo tutto insieme, ecco il codice completo per crittografare e decifrare una stringa.

class func encryptData (_ clearTextData: Data, withPassword password: String) -> Dizionario var setupSuccess = true var outDictionary = Dizionario.init () var key = Data (ripetizione: 0, conteggio: kCCKeySizeAES256) var salt = Dati (numero: 8) salt.withUnsafeMutableBytes (saltBytes: UnsafeMutablePointer) -> Void in let saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes) se saltStatus == errSecSuccess let passwordData = password.data (utilizzando: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer) in let derivationStatus = CCKeyDerivationPBKDF (CCPBKDFAlgorithm (kCCPBKDF2), password, passwordData.count, saltBytes, salt.count, CCPseudoRandomAlgorithm (kCCPRFHmacAlgSHA512), 14271, keyBytes, key.count) se derivationStatus! = Int32 (kCCSuccess) setupSuccess = false  else setupSuccess = false var iv = Data.init (conteggio: kCCBlockSizeAES128) iv.withUnsafeMutableBytes (ivBytes: UnsafeMutablePointer) in let ivStatus = SecRandomCopyBytes (kSecRandomDefault, kCCBlockSizeAES128, ivBytes) if ivStatus! = errSecSuccess setupSuccess = false if (setupSuccess) var numberOfBytesEncrypted: size_t = 0 let size = clearTextData.count + kCCBlockSizeAES128 var encrypted = Data.init ( count: size) let cryptStatus = iv.withUnsafeBytes ivBytes in encrypted.withUnsafeMutableBytes encryptedBytes in clearTextData.withUnsafeBytes clearTextBytes in key.withUnsafeBytes keyBytes in CCCrypt (CCOperation (kCCEncrypt), CCAlgorithm (kCCAlgorithmAES), CCOptions (kCCOptionPKCS7Padding), keyBytes, key.count, ivBytes, clearTextBytes, clearTextData.count, encryptedBytes, size, & numberOfBytesEncrypted) if cryptStatus == Int32 (kCCSuccess) encrypted.count = numberOfBytesEncrypted outDictionary ["EncryptionData"] = crittografato outDictionary ["EncryptionIV"] = iv outDictionary ["EncryptionSalt"] = salt return outDictionary;  

Ed ecco il codice di decrittazione:

class func decryp (dal dizionarioDictionary: Dictionary, withPassword password: String) -> Data var setupSuccess = true let encrypted = dictionary ["EncryptionData"] let iv = dictionary ["EncryptionIV"] let salt = dictionary ["EncryptionSalt"] var key = Dati (ripetizione: 0, conteggio : kCCKeySizeAES256) salt? .withUnsafeBytes (saltBytes: UnsafePointer) -> Void in let passwordData = password.data (utilizzando: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer) in let derivationStatus = CCKeyDerivationPBKDF (CCPBKDFAlgorithm (kCCPBKDF2), password, passwordData.count, saltBytes, salt! .count, CCPseudoRandomAlgorithm (kCCPRFHmacAlgSHA512), 14271, keyBytes, key.count) se derivationStatus! = Int32 (kCCSuccess) setupSuccess = false  var decryptSuccess = false let size = (crittografato? .count)! + kCCBlockSizeAES128 var clearTextData = Data.init (count: size) if (setupSuccess) var numberOfBytesDecrypted: size_t = 0 let cryptStatus = iv? .withUnsafeBytes ivBytes in clearTextData.withUnsafeMutableBytes clearTextBytes in encrypted? .withUnsafeBytes encryptedBytes in key.withUnsafeBytes keyBytes in CCCrypt (CCOperation (kCCDecrypt), CCAlgorithm (kCCAlgorithmAES128), CCOptions (kCCOptionPKCS7Padding), keyBytes, key.count, ivBytes, encryptedBytes, (crittografato? .count)!, clearTextBytes, size, e numberOfBytesDecrypted) if cryptStatus ! == Int32 (kCCSuccess) clearTextData.count = numberOfBytesDecrypted decryptSuccess = true restituisce decryptSuccess? clearTextData: Data.init (count: 0) 

Infine, ecco un test per garantire che i dati vengano decodificati correttamente dopo la crittografia:

class func encryptionTest () lascia clearTextData = "un po 'di testo chiaro da crittografare" .data (usando: String.Encoding.utf8)! let dictionary = encryptData (clearTextData, withPassword: "123456") let decrypted = decryp (fromDictionary: dictionary, withPassword: "123456") let decryptedString = String (data: decrypted, encoding: String.Encoding.utf8) print ("decifrato cleartext risultato - ", decryptedString ??" Errore: impossibile convertire i dati in stringa ")

Nel nostro esempio, impacchettiamo tutte le informazioni necessarie e le restituiamo come a Dizionario in modo che tutti i pezzi possano essere successivamente utilizzati per decifrare i dati con successo. Hai solo bisogno di memorizzare il IV e il sale, sia nel portachiavi o sul tuo server.

Conclusione

Questo completa la serie in tre parti sulla protezione dei dati a riposo. Abbiamo visto come memorizzare correttamente password, informazioni sensibili e grandi quantità di dati utente. Queste tecniche sono la base per la protezione delle informazioni utente archiviate nella tua app.

È un enorme rischio quando un dispositivo dell'utente viene perso o rubato, specialmente con i recenti exploit per ottenere l'accesso a un dispositivo bloccato. Mentre molte vulnerabilità del sistema sono corredate di un aggiornamento software, il dispositivo stesso è sicuro quanto il passcode e la versione di iOS dell'utente. Pertanto spetta allo sviluppatore di ogni app fornire una protezione forte dei dati sensibili memorizzati. 

Tutti gli argomenti trattati finora fanno uso dei framework Apple. Lascerò un'idea con te a cui pensare. Cosa succede quando viene attaccata la libreria di crittografia di Apple? 

Quando viene compromessa un'architettura di sicurezza comunemente utilizzata, anche tutte le app che si basano su di essa vengono compromesse. Qualsiasi delle librerie collegate dinamicamente di iOS, specialmente su dispositivi jailbroken, può essere riparata e scambiata con quelle malevole. 

Tuttavia, una libreria statica che è in bundle con il binario della tua app è protetta da questo tipo di attacco perché se provi a correggerla, finisci per cambiare il binario dell'app. Ciò interromperà la firma del codice dell'app, impedendone il lancio. Se hai importato e utilizzato, ad esempio, OpenSSL per la crittografia, la tua app non sarebbe vulnerabile a un attacco diffuso dell'API Apple. Puoi compilare OpenSSL da solo e collegarlo staticamente alla tua app.

Quindi c'è sempre altro da imparare e il futuro della sicurezza delle app su iOS è in continua evoluzione. L'architettura di sicurezza iOS supporta ora anche dispositivi crittografici e smart card! In chiusura, ora conosci le migliori pratiche per proteggere i dati a riposo, quindi spetta a te seguirli!

Nel frattempo, controlla alcuni dei nostri altri contenuti sullo sviluppo di app per iOS e sulla sicurezza delle app.