Securizarea datelor iOS la odihnă Cheie

Orice aplicație care salvează datele utilizatorului trebuie să se ocupe de securitatea și confidențialitatea datelor respective. Așa cum am văzut cu încălcări recente ale datelor, pot exista consecințe foarte grave pentru a nu proteja datele stocate de utilizatori. În acest tutorial, veți afla câteva bune practici pentru protejarea datelor utilizatorilor.

În postul anterior, ați învățat cum să protejați fișierele utilizând API-ul pentru protecția datelor. Protecția bazată pe fișiere este o funcție puternică pentru stocarea sigură a datelor în bloc. Dar ar putea fi prea mult pentru o cantitate mică de informații de protejat, cum ar fi o cheie sau o parolă. Pentru aceste tipuri de articole, brelocul este soluția recomandată.

Servicii de chei

Brelocul cheie este un loc minunat pentru a stoca cantități mai mici de informații, cum ar fi șiruri de caractere sensibile și ID-uri care persistă chiar și atunci când utilizatorul șterge aplicația. Un exemplu ar putea fi un dispozitiv sau un jet de sesiune pe care serverul dvs. îl returnează aplicației la înregistrare. Indiferent dacă îl numiți un șir secret sau un simbol unic, brelocul de chei se referă la toate aceste elemente ca parole

Există câteva biblioteci populare pentru terțe părți, cum ar fi Strongbox (Swift) și SSKeychain (Obiectiv-C). Sau, dacă doriți un control complet asupra propriului cod, vă recomandăm să utilizați direct API-ul Keychain Services, care este un API C. 

Voi explica pe scurt modul in care functioneaza keychain-ul. Vă puteți gândi la lanțul de chei ca o bază de date tipică în care rulați interogări pe o masă. Funcțiile API-ului keychain necesită toate CFDictionary obiect care conține atribute ale interogării. 

Fiecare intrare din breloc are un nume de serviciu. Numele serviciului este un identificator: a cheie pentru orice valoare pe care doriți să le stocați sau să le recuperați în lanțul de chei. Pentru a permite ca un element cheie de chei să fie stocat numai pentru un anumit utilizator, de asemenea, de multe ori doriți să specificați un nume de cont. 

Deoarece fiecare funcție a unui keychain are un dicționar similar cu mulți dintre aceiași parametri pentru a face o interogare, puteți evita codul duplicat făcând o funcție helper care returnează acest dicționar de interogare.

import Securitate // ... class func passwordQuery (serviciu: String, cont: String) -> Dicționar let dicționar = [kSecClass ca String: kSecClassGenericPassword, kSecAttrAccount ca String: account, kSecAttrService ca String: service, kSecAttrAccessible ca String: kSecAttrAccessibleWhenUnlocked // Dacă aveți nevoie de acces în fundal, ar trebui să luați în considerare kSecAttrAccessibleAfterFirstUnlock] 

Acest cod stabilește interogarea Dicţionar cu numele contului și serviciului dvs. și spuneți brelocul cheie că vom păstra o parolă. 

Similar cu modul în care puteți seta nivelul de protecție pentru fișierele individuale (așa cum am discutat în postul anterior), puteți seta, de asemenea, nivelurile de protecție pentru elementul dvs. de breloc folosind kSecAttrAccessible cheie. 

Adăugarea unei parole

 SecItemAdd () funcția adaugă date în lanțul de chei. Această funcție durează a Date obiect, ceea ce îl face versatil pentru stocarea mai multor tipuri de obiecte. Folosind funcția de interogare a parolei pe care am creat-o mai sus, să stocăm un șir în lanțul de chei. Pentru a face acest lucru, trebuie doar să convertim Şir la Date.

@discardableResult class func setPassword (_ parola: String, service: String, cont: String) -> Bool var status: OSStatus = -1 if! (service.isEmpty) &&! (account.isEmpty) deletePassword , cont: cont) // ștergeți parola dacă treceți șir gol. Ar putea schimba pentru a trece nul pentru a șterge parola, etc if! Password.isEmpty var dicționar = passwordQuery (serviciu: serviciu, cont: cont) permite dataFromString = password.data (folosind: String.Encoding.utf8, allowLossyConversion: false) kSecValueData ca String] = dataFromString status = SecItemAdd (dicționar ca CFDictionary, nil) stare de returnare == errSecSuccess

Ștergerea unei parole

Pentru a preveni inserările duplicate, codul de mai sus va șterge mai întâi intrarea anterioară dacă există una. Să scriem funcția acum. Acest lucru este realizat folosind SecItemDelete () funcţie.

@discardableResult class func deletePassword (serviciu: String, cont: String) -> Bool var status: OSStatus = -1 if! (service.isEmpty) &&! (account.isEmpty) let dictionary = passwordQuery : account) status = SecItemDelete (dicționar ca CFDictionary);  stare de returnare == errSecSuccess

Preluarea unei parole

Apoi, pentru a prelua o intrare din breloc, utilizați SecItemCopyMatching () funcţie. Se va întoarce AnyObject care corespunde interogării dvs..

parola func func (serviciu: String, cont: String) -> String // returneaza sirul gol daca nu a fost gasit, ar putea returna o optiune var status: OSStatus = -1 var rezultatString = "if! (service.isEmpty) (cont.isEmpty) var passwordData: AnyObject? var dicționar = parolăQuery dicționar [kSecReturnData ca String] = dicționar kCFBooleanTrue [kSecMatchLimit ca String] = kSecMatchLimitOne status = SecItemCopyMatching (dicționar ca CFDictionary, & passwordData) dacă status == errSecSuccess if let retrievedData = passwordData la fel de? Date resultString = String (date: retrievedData, codificare: String.Encoding.utf8)!  return resultString

În acest cod, am setat kSecReturnData parametru pentru kCFBooleanTruekSecReturnData înseamnă că datele reale ale elementului vor fi returnate. O altă opțiune ar putea fi returnarea atributelor (kSecReturnAttributes) a elementului. Cheia ia a CFBoolean tip care deține constantele kCFBooleanTrue sau kCFBooleanFalse. Suntem aici kSecMatchLimit la kSecMatchLimitOne astfel încât numai primul element găsit în lanțul de chei va fi returnat, spre deosebire de un număr nelimitat de rezultate.

Cheile publice și private

Lanțul de chei este, de asemenea, locul recomandat pentru stocarea obiectelor cheie publice și private, de exemplu, dacă aplicația dvs. funcționează și trebuie să stocheze EC sau RSA SecKey obiecte. 

Principala diferență constă în aceea că, în loc să spună brelocului să stocheze o parolă, putem să-i spunem să stocheze o cheie. De fapt, putem obține informații specifice prin setarea tipurilor de chei stocate, cum ar fi dacă acestea sunt publice sau private. Tot ce trebuie făcut este să adaptați funcția de ajutor al interogării pentru a funcționa cu tipul de cheie pe care doriți să o utilizați. 

Cheile sunt, în general, identificate utilizând o etichetă de domeniu invers, cum ar fi com.mydomain.mykey în loc de nume de servicii și de cont (deoarece cheile publice sunt distribuite în mod deschis între diferite companii sau entități). Vom prelua șirurile de servicii și cont și le vom transforma într-o etichetă Date obiect. De exemplu, codul de mai sus este adaptat pentru a stoca un RSA privat SecKey ar arata astfel:

class func keyQuery (serviciu: String, cont: String) -> Dicționar lăsați tagString = "com.mydomain". + serviciu + "." + cont permite tag = tagString.data (folosind: .utf8)! // Stocați ca date, nu ca un dicționar String = = [kSecClass ca String: kSecClassKey, kSecAttrKeyType ca String: kSecAttrKeyTypeRSA, kSecAttrKeyClass ca String: kSecAttrKeyClassPrivate, kSecAttrAccessible ca String: kSecAttrAccessibleWhenUnlocked, kSecAttrApplicationTag ca String: tag] ] dicționar de returnare @discardableResult class func setKey (_ cheie: SecKey, serviciu: String, cont: String) -> Bool var status: OSStatus = -1 if! (service.isEmpty) (serviciu: serviciu, cont: cont) var dicționar = cheieQuery (serviciu: serviciu, cont: cont) dicționar [kSecValueRef ca String] = cheie status = SecItemAdd (dicționar ca CFDictionary, nul);  stare de returnare == errSecSuccess @discardableResult class func deleteKey (serviciu: String, cont: String) -> Bool var status: OSStatus = -1 if! (service.isEmpty) keyQuery (serviciu: serviciu, cont: cont) status = SecItemDelete (dicționar ca CFDictionary);  stare de returnare == errSecSuccess cla func func (serviciu: String, cont: String) -> SecKey? var element: CFTypeRef? if (service.isEmpty) &&! (account.isEmpty) dicționar dictionar = cheieQuery (serviciu: serviciu, cont: cont) [kSecReturnRef ca String] = dicționar kCFBooleanTrue [kSecMatchLimit ca String] = kSecMatchLimitOne SecItemCopyMatching (dicționar ca CFDictionary, &articol);  returnați elementul ca! SecKey? 

Parole de aplicație

Elementele securizate cu kSecAttrAccessibleWhenUnlocked steagul este deblocat numai când dispozitivul este deblocat, dar se bazează pe faptul că utilizatorul are un cod de acces sau un ID de identificare setat în primul rând. 

applicationPassword acreditarea permite ca articolele din breloc să fie securizate utilizând o parolă suplimentară. În acest fel, în cazul în care utilizatorul nu are un cod de acces sau un set de identificare tactil, elementele vor fi în continuare sigure și vor adăuga un nivel suplimentar de securitate dacă au un set de parolă.  

Ca scenariu, după ce aplicația dvs. se autentifică cu serverul dvs., serverul dvs. ar putea să returneze parola prin HTTPS, care este necesară pentru a debloca elementul cheie. Acesta este modul preferat de a furniza acea parolă suplimentară. Codul hardcoding în binar nu este recomandat.

Un alt scenariu ar putea fi recuperarea parolei suplimentare de la o parolă furnizată de utilizator în aplicația dvs. totuși, aceasta necesită mai multă muncă pentru a se asigura în mod corespunzător (folosind PBKDF2). Vom examina parolele furnizate de utilizatori în următorul tutorial. 

O altă utilizare a unei parole de aplicație este aceea de a stoca o cheie sensibilă - de exemplu, una pe care nu doriți să o expuneți doar pentru că utilizatorul nu a stabilit încă un cod de trecere. 

applicationPassword este disponibilă numai în iOS 9 și în versiunile superioare, deci veți avea nevoie de o rezervă care nu se utilizează applicationPassword dacă vizați versiuni inferioare pentru iOS. Pentru a utiliza codul, va trebui să adăugați următoarele în antetul dvs. de legătură:

#import  #import 

Următorul cod stabilește o parolă pentru interogare Dicţionar.

dacă #available (iOS 9.0, *) // Folosiți acest lucru în locul kSecAttrAccessible pentru interogarea var error: Unmanaged? permite accessControl = SecAccessControlCreateWithFlags (kCFAllocatorDefault, kSecAttrAccessibleWhenUnlocked, SecAccessControlCreateFlags.applicationPassword, și eroare) dacă accessControl! = nil dicționar [kSecAttrAccessControl ca String] = accessControl enable localAuthenticationContext = LAContext.init .Encoding.utf8)! localAuthenticationContext.setCredential (dicționarApplicationPassword, tip: LACredentialType.applicationPassword) [kSecUseAuthenticationContext ca String] = localAuthenticationContext

Observați că am stabilit kSecAttrAccessControl pe Dicţionar. Acesta este folosit în locul lui kSecAttrAccessible, care a fost stabilit anterior în nostru passwordQuery metodă. Dacă încercați să utilizați ambele, veți obține o OSStatus -50 eroare.

Autentificarea utilizatorului

Începând cu versiunea iOS 8, puteți stoca date în lanțul de chei care poate fi accesat numai după ce utilizatorul se autentifică cu succes pe dispozitiv cu ID-ul de atingere sau cu un cod de acces. Când este timpul ca utilizatorul să se autentifice, ID-ul tactil va avea prioritate dacă este configurat, în caz contrar este prezentat ecranul parolă. Salvarea în breloc nu va necesita autentificarea utilizatorului, dar preluarea datelor se va face. 

Puteți seta un element cheie de chei pentru a solicita autentificarea utilizatorului furnizând un obiect de control al accesului setat la .userPresence. Dacă nu este setat nici un cod de parolă, atunci orice solicitare de chei le cere .userPresence va eșua. 

dacă #available (iOS 8.0, *) let accesControl = SecAccessControlCreateWithFlags (kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, .userPresence, nil) dacă accessControl! = nil dicționar [kSecAttrAccessControl ca String] = accessControl 

Această caracteristică este bună atunci când doriți să vă asigurați că aplicația dvs. este utilizată de persoana potrivită. De exemplu, este important ca utilizatorul să se autentifice înainte de a putea să vă conectați la o aplicație bancară. Acest lucru va proteja utilizatorii care și-au lăsat dispozitivul deblocat, astfel încât banca nu poate fi accesată. 

De asemenea, dacă nu aveți o componentă de pe server pentru aplicația dvs., puteți utiliza această caracteristică pentru a efectua autentificarea pe partea de dispozitiv.

Pentru interogarea de încărcare, puteți furniza o descriere a motivului pentru care utilizatorul trebuie să se autentifice.

dicționarul [kSecUseOperationPrompt as String] = "Autentificare pentru a prelua x" 

La preluarea datelor cu SecItemCopyMatching (), funcția va afișa interfața de autentificare și aștepta ca utilizatorul să utilizeze ID-ul de atingere sau să introducă codul de parolă. De cand SecItemCopyMatching () va bloca până la terminarea autentificării de către utilizator, va trebui să apelați funcția dintr-un fir de fundal pentru a permite firului principal de interfață să rămână receptiv.

DispatchQueue.global () async status = SecItemCopyMatching (dicționar ca CFDictionary, & passwordData) dacă status == errSecSuccess dacă permiteți retrievedData = passwordData as? Data DispatchQueue.main.async // ... face restul lucrărilor înapoi pe firul principal

Din nou, noi suntem kSecAttrAccessControl pe interogare Dicţionar. Va trebui să eliminați kSecAttrAccessible, care a fost stabilit anterior în nostru passwordQuery metodă. Utilizarea ambelor simultan va duce la o OSStatus -50 eroare.

Concluzie

În acest articol, ați făcut un tur al API-ului Keychain Services. Împreună cu API-ul pentru protecția datelor pe care l-am văzut în postul anterior, utilizarea acestei biblioteci face parte din cele mai bune practici pentru securizarea datelor. 

Cu toate acestea, dacă utilizatorul nu are un cod de acces sau un ID de identificare pe dispozitiv, nu există nici o criptare pentru niciun cadru. Deoarece API-urile de servicii Keychain și Protecția datelor sunt utilizate în mod obișnuit de aplicațiile iOS, acestea sunt uneori vizate de atacatori, în special pe dispozitive jailbroken. Dacă aplicația dvs. nu funcționează cu informații extrem de sensibile, acesta poate fi un risc acceptabil. În timp ce iOS actualizează în mod constant securitatea cadrelor, suntem încă la mila utilizatorului actualizând sistemul de operare, folosind un cod de acces puternic și nu jailbreaking dispozitivul. 

Brelocul de chei este destinat unor bucăți mai mici de date și este posibil să aveți o cantitate mai mare de date pentru a vă asigura că este independentă de autentificarea dispozitivului. În timp ce actualizările iOS adaugă câteva noi caracteristici noi, cum ar fi parola aplicației, este posibil să fie necesar să acceptați versiuni inferioare pentru iOS și să aveți în continuare o securitate puternică. Din unele motive, este posibil să doriți să criptați datele în mod propriu. 

Ultimul articol din această serie acoperă criptarea datelor cu ajutorul criptării AES și, deși este o abordare mai avansată, aceasta vă permite să aveți un control deplin asupra modului și momentului în care datele dvs. sunt criptate.

Stați așa. Între timp, verificați câteva dintre celelalte postări ale noastre privind dezvoltarea aplicațiilor iOS!

Cod