În această postare, vom examina utilizările avansate de criptare pentru datele utilizatorilor din aplicațiile iOS. Vom începe cu un aspect la nivel înalt la criptarea AES și apoi vom examina câteva exemple de implementare a criptării AES în Swift.
În ultimul post, ați învățat cum să stocați date utilizând cheia de breloc, ceea ce este bun pentru mici fragmente de informații, cum ar fi cheile, parolele și certificatele.
Dacă stocați o cantitate mare de date personalizate pe care doriți să fie disponibile numai după ce utilizatorul sau dispozitivul se autentifică, atunci este mai bine să criptați datele utilizând un cadru de criptare. De exemplu, este posibil să aveți o aplicație care să arhiveze mesaje private de chat salvate de utilizatori sau fotografii private realizate de utilizator sau care să stocheze detaliile financiare ale utilizatorului. În aceste cazuri, probabil că doriți să utilizați criptarea.
Există două fluxuri comune în aplicațiile pentru criptarea și decriptarea datelor din aplicațiile iOS. Fie utilizatorul este prezentat cu un ecran de parolă, fie aplicația este autentificată cu un server care returnează o cheie pentru a decripta datele.
Nu este o idee bună să reinventezi roata atunci când vine vorba de criptare. Prin urmare, vom folosi standardul AES furnizat de biblioteca iOS Common Crypto.
AES este un standard care criptează datele date unei chei. Aceeași cheie folosită pentru a cripta datele este utilizată pentru a decripta datele. Există diferite dimensiuni de taste, iar AES256 (256 biți) este lungimea preferată pentru a fi utilizată cu date sensibile.
RNCryptor este un popular wrapper de criptare pentru iOS care suportă AES. RNCryptor este o alegere excelentă, deoarece vă ajută să lucrați foarte repede, fără să vă faceți griji cu privire la detaliile care stau la baza. Este, de asemenea, open source, astfel încât cercetătorii în domeniul securității să poată analiza și să auditeze codul.
Pe de altă parte, dacă aplicația dvs. se ocupă de informații foarte sensibile și credeți că aplicația dvs. va fi direcționată și crăpată, vă recomandăm să vă scrieți propria soluție. Motivul pentru aceasta este că atunci când multe aplicații utilizează același cod, pot simplifica activitatea hacker-ului, permițându-le să scrie o aplicație de cracare care găsește modele comune în cod și le aplică patch-uri.
Rețineți, însă, că scrierea propriei soluții încetinește doar un atacator și previne atacurile automate. Protecția pe care o obțineți de la propria implementare este că un hacker va trebui să-și petreacă timpul și dedicarea pentru a vă sparge aplicația în monoterapie.
Indiferent dacă alegeți o soluție terță parte sau alegeți să vă prezentați, este important să aveți cunoștință despre modul în care funcționează sistemele de criptare. În acest fel, puteți decide dacă un anumit cadru pe care doriți să îl utilizați este într-adevăr sigur. Prin urmare, restul acestui tutorial se va concentra pe scrierea propriei soluții personalizate. Odată cu cunoștințele pe care le veți învăța din acest tutorial, veți putea afla dacă folosiți un anumit cadru în siguranță.
Vom începe cu crearea unei chei secrete care va fi utilizată pentru criptarea datelor dvs..
O eroare foarte frecventă în criptarea AES este să folosești parola unui utilizator direct ca cheia de criptare. Ce se întâmplă dacă utilizatorul decide să utilizeze o parolă comună sau slabă? Cum îi forțăm pe utilizatori să folosească o cheie destul de aleatoare și destul de puternică (are entropie suficientă) pentru criptare și apoi să-i aducă aminte de ea?
Soluția este cheie stretching. Extensia cheie derivă o cheie de la o parolă prin ștergerea ei de mai multe ori cu o sare. Sarea este doar o secvență de date aleatorii și este o greșeală obișnuită să omiteți această sare - sarea conferă cheii entropia vitală vitală și fără sare, aceeași cheie ar fi derivată dacă aceeași parolă ar fi fost utilizată de cineva altfel.
Fără sare, un dicționar de cuvinte ar putea fi folosit pentru a deduce cheile comune, care ar putea fi apoi folosite pentru a ataca datele utilizatorului. Aceasta se numește "atac de dicționar". În acest scop sunt utilizate tabele cu chei comune care corespund parolelor nesalocate. Se numesc "mese curcubeu".
O altă capcană atunci când creați o sare este să utilizați o funcție generatoare de numere aleatoare care nu a fost proiectată pentru securitate. Un exemplu este rand ()
funcția în C, care poate fi accesată de la Swift. Această ieșire poate deveni foarte previzibilă!
Pentru a crea o sare sigură, vom folosi funcția SecRandomCopyBytes
pentru a crea octeți oculți securizați criptografic - adică numere care sunt greu de prezis.
Pentru a utiliza codul, va trebui să adăugați următoarele în antetul dvs. de legătură:#import
Iată începutul codului care creează o sare. Vom adăuga la acest cod pe măsură ce mergem:
var sare = date (count: 8) salt.withUnsafeMutableBytes (saltBytes: UnsafeMutablePointer) -> Void in let saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes) // ...
Acum suntem gata să facem o întindere cheie. Din fericire, avem deja o funcție la dispoziția noastră pentru a face întinderea reală: Funcția de derivare a cheilor bazate pe parolă (PBKDF2). PBKDF2 execută o funcție de mai multe ori pentru a extrage cheia; creșterea numărului de iterații extinde timpul necesar pentru a funcționa pe un set de chei în timpul unui atac de forță brute. Se recomandă utilizarea PBKDF2 pentru a genera cheia.
var setSuccess = adevărată vară cheie = date (repetare: 0, număr: kCCKeySizeAES256) var sare = date (count: 8) salt.withUnsafeMutableBytes (saltBytes: UnsafeMutablePointer) -> Void in let saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes) dacă saltStatus == errSecSuccess lăsați passwordData = password.data (folosind: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer ) în derivareStatus = CCKeyDerivationPBKDF (CCPBKDFAlgorithm (kCCPBKDF2), parola, parolaData.count, sareBute, sare.count, CCPseudoRandomAlgorithm (kCCPRFHmacAlgSHA512), 14271, keyBytes, key.count) altceva setupSuccess = false
Este posibil să vă întrebați acum despre cazurile în care nu doriți să solicitați utilizatorilor să furnizeze o parolă în aplicația dvs. Poate că deja autentifică printr-o singură schemă de conectare. În acest caz, serverul dvs. trebuie să genereze o cheie AES de 256 de biți (32 de octeți) utilizând un generator securizat. Cheia trebuie să fie diferită pentru diferiți utilizatori sau dispozitive. La autentificarea cu serverul dvs., puteți transmite serverului un dispozitiv sau un ID de utilizator printr-o conexiune securizată și poate trimite cheia corespunzătoare.
Această schemă are o diferență majoră. Dacă cheia vine de la server, entitatea care controlează acest server are capacitatea de a citi datele criptate dacă dispozitivul sau datele au fost obținute vreodată. Există, de asemenea, posibilitatea ca cheia să fie scursă sau expusă mai târziu.
Pe de altă parte, dacă cheia este derivată din ceva ce numai utilizatorul cunoaște - parola utilizatorului - atunci numai utilizatorul poate decripta datele respective. Dacă protejați informațiile cum ar fi datele financiare private, numai utilizatorul ar trebui să poată debloca datele. Dacă oricum această informație este cunoscută entității, poate fi acceptabil ca serverul să deblocheze conținutul printr-o cheie de server.
Acum, că avem o cheie, să criptez câteva date. Există diferite moduri de criptare, dar vom folosi modul recomandat: blocarea blocului de cifru (CBC). Aceasta funcționează pe datele noastre câte un bloc la un moment dat.
O capcană obișnuită cu CBC este faptul că fiecare următor bloc blocat de date este XOR'd cu blocul criptat anterior pentru a face criptarea mai puternică. Problema este că primul bloc nu este niciodată la fel de unic ca toate celelalte. Dacă un mesaj care urmează să fie criptat ar trebui să înceapă la fel ca un alt mesaj care urmează să fie criptat, ieșirea criptată inițială ar fi aceeași și ar da un atacator un indiciu pentru a afla ce mesaj ar putea fi.
Pentru a elimina această slăbiciune potențială, vom începe să salvăm datele cu ceea ce se numește un vector de inițializare (IV): un bloc de octeți aleatorii. IV va fi XOR'd cu primul bloc de date de utilizator și din moment ce fiecare bloc depinde de toate blocurile prelucrate până în acel moment, se va asigura că întregul mesaj va fi criptat în mod unic, chiar dacă are aceleași date ca și altul mesaj. Cu alte cuvinte, mesaje identice criptate cu aceeași cheie nu vor produce rezultate identice. Deci, în timp ce sărurile și IV-urile sunt considerate publice, acestea nu trebuie să fie secvențiale sau reutilizate.
Vom folosi aceeași siguranță SecRandomCopyBytes
funcția de a crea IV.
var iv = Data.init (număr: kCCBlockSizeAES128) iv.withUnsafeMutableBytes (ivBytes: UnsafeMutablePointer) în cazul în care ivStatus = SecRandomCopyBytes (kSecRandomDefault, kCCBlockSizeAES128, ivBytes) dacă ivStatus! = errSecSuccess setupSuccess = false
Pentru a completa exemplul nostru, vom folosi CCCrypt
cu oricare dintre ele kCCEncrypt
sau kCCDecrypt
. Deoarece folosim un cifru bloc, dacă mesajul nu se potrivește frumos într-un număr mai mare de dimensiune a blocului, va trebui să spunem funcției să adauge automat umplutura până la capăt.
Ca de obicei în criptare, cel mai bine este să urmați standardele stabilite. În acest caz, standardul PKCS7 definește modul de împachetare a datelor. Spunem funcției noastre de criptare să utilizeze acest standard furnizând KCCOptionPKCS7Padding
opțiune. Punând totul împreună, iată codul complet pentru criptarea și decriptarea unui șir.
class func encryptData (_ clarTextData: Date, cu parola de parolă: String) -> Dicționarvar setSuccess = adevărat var outDictionary = dicționar .init () vară cheie = date (repetare: 0, număr: kCCKeySizeAES256) var sare = date (count: 8) salt.withUnsafeMutableBytes (saltBytes: UnsafeMutablePointer ) -> Void in let saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes) dacă saltStatus == errSecSuccess lăsați passwordData = password.data (folosind: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer ) în derivareStatus = CCKeyDerivationPBKDF (CCPBKDFAlgorithm (kCCPBKDF2), parola, parolaData.count, sareBute, sare.count, CCPseudoRandomAlgorithm (kCCPRFHmacAlgSHA512), 14271, keyBytes, key.count) altceva setupSuccess = false var iv = Data.init (count: kCCBlockSizeAES128) iv.withUnsafeMutableBytes (ivBytes: UnsafeMutablePointer ) în varianta ivStatus = SecRandomCopyBytes (kSecRandomDefault, kCCBlockSizeAES128, ivBytes) dacă ivStatus! = errSecSuccess setupSuccess = false dacă (setupSuccess) var numberOfBytesEncrypted: size_t = 0 let size = clearTextData.count + kCCBlockSizeAES128 var encrypted = Data.init conta: dimensiunea) lasa cryptStatus = iv.withUnsafeBytes ivBytes în encrypted.withUnsafeMutableBytes encryptedBytes în clearTextData.withUnsafeBytes clearTextBytes în key.withUnsafeBytes keyBytes în CCCrypt (CCOperation (kCCEncrypt), CCAlgorithm (kCCAlgorithmAES), CCOptions (kCCOptionPKCS7Padding), keyBytes, ()) dacă cryptStatus == Int32 (kCCSuccess) encrypted.count = numberOfBytesEncrypted outDictionary ["EncryptionData"] = criptat outDictionary ["EncryptionIV"] = iv outDictionary ["EncryptionSalt"] = sare return outDictionary;
Și aici este codul de decriptare:
class func decryp (din dicționarul dicționar: dicționar, cu parola de parolă: String) -> Date var setupSuccess = true permite criptat = dicționar ["EncryptionData"] permite iv = dicționar ["EncryptionIV"] salt = dictionary ["EncryptionSalt"] varkey = Data : kCCKeySizeAES256) sare? .withUnsafeBytes (saltBytes: UnsafePointer ) -> Void în parola passwordData = password.data (folosind: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer ), în cazul în care derivationStatus = CCKeyDerivationPBKDF (CCPBKDFAlgorithm (kCCPBKDF2), password, passwordData.count, saltBytes, sare! .count, CCPseudoRandomAlgorithm (kCCPRFHmacAlgSHA512), 14271, keyBytes, key.count) var decryptSuccess = false lasă dimensiunea = (criptată? .count)! + kCCBlockSizeAES128 var clearTextData = Date.init (număr: dimensiune) dacă (setupSuccess) var numberOfBytesDecrypted: size_t = 0 let cryptStatus = iv? .withUnsafeBytes ivBytes în clearTextData.withUnsafeMutableBytes clearTextBytes în encrypted? .withUnsafeBytes encryptedBytes in key.withUnsafeBytes keyBytes în CCCrypt (CCOperation (kCCDecrypt), CCAlgoritm (kCCAlgorithmAES128), CCOptions (kCCOptionPKCS7Padding), keyBytes, key.count, ivBytes, encryptedBytes (encrypted? .count)! clearTextBytes, size & numberOfBytesDecrypted if cryptStatus ! == Int32 (kCCSuccess) clearTextData.count = numereOfBytesDecrypted decryptSuccess = true retur decryptSuccess? clearTextData: Data.init (count: 0)
În cele din urmă, aici este un test pentru a vă asigura că datele sunt decriptate corect după criptare:
class func encryptionTest () let clearTextData = "un text clar pentru a cripta" .data (folosind: String.Encoding.utf8)! dați dicționar = encryptData (clearTextData, cuPassword: "123456") decrypted = decryp (din dicter: dicționar, cuPassword: "123456") decryptedString = String (decrypted, encoding: String.Encoding.utf8) rezultat - ", decryptedString" Eroare: Nu s-a putut converti datele în șir "
În exemplul nostru, punem la dispoziție toate informațiile necesare și îl returnează ca a Dicţionar
astfel încât toate piesele pot fi utilizate ulterior pentru a decripta datele. Trebuie doar să stocați IV și sare, fie în lanțul de chei, fie pe serverul dvs..
Aceasta completează seria cu trei părți privind securizarea datelor în repaus. Am văzut cum să stocăm în mod corespunzător parole, fragmente de informații sensibile și cantități mari de date de utilizator. Aceste tehnici reprezintă linia de bază pentru protejarea informațiilor stocate de utilizatori în aplicația dvs..
Este un risc imens când dispozitivul unui utilizator este pierdut sau furat, mai ales cu exploatații recente pentru a avea acces la un dispozitiv blocat. Deși multe vulnerabilități de sistem sunt patch-uri cu o actualizare de software, dispozitivul în sine este doar la fel de sigur ca și codul de acces și versiunea iOS a utilizatorului. Prin urmare, dezvoltatorii fiecărei aplicații au la dispoziție o protecție puternică pentru stocarea datelor sensibile.
Toate subiectele acoperite până acum fac uz de cadrele Apple. Voi lăsa o idee cu dvs. să vă gândiți. Ce se întâmplă când biblioteca de criptare Apple este atacată?
Atunci când o arhitectură de securitate frecvent utilizată este compromisă, toate aplicațiile care se bazează pe aceasta sunt, de asemenea, compromise. Oricare dintre bibliotecile legate dinamic din iOS, în special pe dispozitive jailbroken, pot fi patch-uri și schimbate cu cele rău-intenționate.
Cu toate acestea, o bibliotecă statică care este asociată cu aplicația binară a aplicației dvs. este protejată de acest tip de atac, deoarece dacă încercați să o modificați, veți termina modificarea aplicației binare. Aceasta va sparge semnătura codului aplicației, împiedicând lansarea acesteia. Dacă ați importat și ați folosit, de exemplu, OpenSSL pentru criptarea dvs., aplicația dvs. nu ar fi vulnerabilă la un atac API pe scară largă pe Apple. Puteți compila OpenSSL și conectați-l în mod static la aplicația dvs..
Deci, există întotdeauna mai mult de învățat, iar viitorul securității aplicațiilor pe iOS evoluează întotdeauna. Arhitectura de securitate iOS suportă chiar acum dispozitive criptografice și carduri inteligente! În cele din urmă, știți acum cele mai bune practici pentru securizarea datelor în repaus, deci depinde de dvs. să le urmați!
Între timp, verificați unele dintre celelalte informații despre dezvoltarea aplicațiilor iOS și securitatea aplicațiilor.