Keychain (Înel de chei) — un depozit protejat în iOS, destinat stocării sigure a parolelor, cheilor criptografice, certificatelor și notițelor confidențiale. Conform Apple Security Documentation (2025), Keychain utilizează criptarea hardware prin Secure Enclave pe toate dispozitivele cu cip A7 și mai noi. Înțelegerea arhitecturii iOS Keychain este necesară fiecărui dezvoltator pentru stocarea corectă a tokenurilor și secretelor aplicației.
Principalele
iOS Keychain — un mecanism protejat de stocare a datelor confidențiale, integrat în sistemul de operare Apple. Spre deosebire de UserDefaults sau fișierele obișnuite, Keychain criptează toate elementele la nivel hardware și oferă un control fin al accesului bazat pe politici de securitate.
Keychain a fost introdus în iOS 2.0 și de atunci a suferit modificări semnificative: în iOS 7 s-a adăugat suportul pentru chei hardware prin Secure Enclave, în iOS 9 — separarea Keychain între aplicații prin Access Groups, în iOS 13 — suportul pentru legătura biometrică prin LAContext. Conform Apple WWDC Session (2024), peste 90% din aplicațiile iOS din top 100 App Store folosesc Keychain pentru stocarea tokenurilor de autentificare.
Arhitectural, Keychain este o bază de date SQLite criptată, situată în afara sandbox-ului aplicației. Fiecare element (SecItem) este criptat cu o cheie separată, care la rândul său este protejată de cheia hardware Secure Enclave. Serviciul de sistem Securityd gestionează accesul la Keychain pe baza drepturilor aplicației (entitlements) și a clasei de protecție solicitate.
Un avantaj important al Keychain față de alte metode de stocare: datele sunt automat criptate și decriptate de sistemul de operare. Dezvoltatorul nu trebuie să implementeze criptografia manual — este suficient să apeleze SecItemAdd cu parametrii corecți. iOS garantează că datele din Keychain nu pot fi citite de alte aplicații (la configurarea corectă a Access Groups).
Arhitectura Keychain include mai multe niveluri: fizic (Secure Enclave), de sistem (Security.framework), aplicativ (API SecItem*) și logic (Access Groups, Protection Classes). Înțelegerea fiecărui nivel ajută la proiectarea corectă a stocării secretelor.
API-ul principal pentru lucrul cu Keychain — funcțiile Security framework: SecItemAdd pentru adăugare, SecItemCopyMatching pentru citire, SecItemUpdate pentru actualizare și SecItemDelete pentru ștergere. Fiecare funcție primește un dicționar query care descrie atributele elementului căutat sau salvat.
Atributele cheie ale query: kSecClass — tipul elementului (kSecClassGenericPassword, kSecClassKey, kSecClassCertificate), kSecAttrAccount — identificator unic în cadrul clasei, kSecValueData — datele stocate (Data), kSecAttrAccessible — clasa de protecție. SecItemCopyMatching cu flagul kSecReturnData returnează datele elementului, cu kSecMatchLimit — numărul de rezultate.
Important: toate funcțiile returnează statusul OSStatus. O operațiune reușită returnează errSecSuccess (0). Erori: errSecItemNotFound (-25300) — elementul nu a fost găsit, errSecDuplicateItem (-25299) — elementul există deja, errSecAuthFailed (-25293) — autentificarea biometrică a eșuat. Dezvoltatorul trebuie să gestioneze corect fiecare status.
Protection Class — atributul kSecAttrAccessible, care determină când datele din Keychain sunt disponibile pentru citire. iOS suportă șase clase de protecție cu diferite niveluri de disponibilitate și securitate.
Clasa recomandată pentru majoritatea scenariilor este kSecAttrAccessibleWhenUnlockedThisDeviceOnly: datele sunt disponibile doar la dispozitiv deblocat și nu sunt copiate în iCloud Backup. Pentru datele care trebuie să fie disponibile după repornire (dar numai după prima deblocare), utilizați kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly. Pentru datele critice care necesită autentificare biometrică la fiecare acces, combinați kSecAttrAccessibleWhenUnlockedThisDeviceOnly cu ACL care necesită biometrie.
Clasele fără sufixul ThisDeviceOnly (kSecAttrAccessibleWhenUnlocked, kSecAttrAccessibleAfterFirstUnlock) permit copierea în iCloud Backup. Acest lucru este convenabil pentru utilizator, dar reduce securitatea — datele pot fi recuperate din backup. Pentru tokenurile de autentificare, utilizați întotdeauna ThisDeviceOnly.
Access Control List (ACL) — mecanism care restricționează operațiile cu un element Keychain pe baza autentificării utilizatorului. ACL se setează prin SecAccessControlCreateWithFlags și se transmite în atributul kSecAttrAccessControl la salvarea elementului.
Flaguri suportate: kSecAccessControlUserPresence — orice autentificare (Face ID, Touch ID sau cod de acces), kSecAccessControlBiometryCurrentSet — doar biometrie (amprentele sau fața înregistrate curent), kSecAccessControlDevicePasscode — doar codul de acces. ACL se aplică fiecărei operații: citirea, actualizarea și ștergerea elementului necesită de asemenea autentificare.
În iOS 15+ a apărut flagul kSecAccessControlWatch — pentru Apple Watch, care permite autentificarea prin ceasul asociat. ACL poate fi combinat: de exemplu, kSecAccessControlUserPresence sau kSecAccessControlBiometryAny cu cod de acces opțional (orientarea .or).
iOS Keychain suportĄ patru clase principale de elemente (kSecClass), fiecare destinată propriului tip de date. Alegerea corectă a clasei simplifică organizarea și căutarea elementelor.
kSecClassGenericPassword — parola generală: cea mai frecvent utilizată clasă. Stochează date binare arbitrare (Data) cu o cheie unică (kSecAttrAccount). Potrivită pentru tokenuri, chei API, coduri PIN. Nu necesită entitlements suplimentare pentru utilizare.
kSecClassInternetPassword — parola de internet: stochează date asociate cu o resursă de rețea. Atribute suplimentare: kSecAttrServer (domeniul serverului), kSecAttrProtocol (https, ftp), kSecAttrPort, kSecAttrAuthenticationType. iOS poate completa automat astfel de parole prin AutoFill.
kSecClassKey — cheie criptografică: pentru stocarea cheilor de criptare (AES, RSA, EC). Cheia este stocată ca SecKeyRef, nu ca Data. kSecClassCertificate — certificat X.509 pentru stocarea și verificarea certificatelor digitale. Ambele clase necesită înțelegerea operațiunilor criptografice și configurarea corectă a atributelor.
În practică, 95% din cazurile de utilizare a Keychain în aplicații mobile sunt acoperite de kSecClassGenericPassword pentru stocarea tokenurilor de autentificare și kSecClassKey pentru stocarea cheilor private de criptare. kSecClassCertificate este utilizat rar — de obicei în aplicații corporative cu propriul PKI.
Să examinăm exemple practice de lucru cu Keychain în Swift utilizând Security framework. Fiecare exemplu include gestionarea erorilor și configurarea corectă a Protection Class.
Exemplul de bază salvează un token de autentificare în Keychain cu protecție WhenUnlockedThisDeviceOnly. Cheia (kSecAttrAccount) — identificatorul serviciului, datele (kSecValueData) — tokenul în format Data.
import Security
enum KeychainError: Error {
case unexpectedStatus(OSStatus)
}
func saveToken(token: String, service: String) throws {
let data = Data(token.utf8)
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: "auth_token",
kSecValueData as String: data,
kSecAttrAccessible as String:
kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
SecItemDelete(query as CFDictionary)
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else {
throw KeychainError.unexpectedStatus(status)
}
}
func readToken(service: String) throws -> String {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: "auth_token",
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
let status = SecItemCopyMatching(
query as CFDictionary, &result
)
guard status == errSecSuccess,
let data = result as? Data else {
throw KeychainError.unexpectedStatus(status)
}
return String(decoding: data, as: UTF8.self)
}
Exemplul demonstrează utilizarea SecAccessControlCreateWithFlags pentru legarea cheii de biometrie. Fiecare acces la element va necesita Face ID sau Touch ID.
import LocalAuthentication
func saveWithBiometry(data: Data, key: String) throws {
let accessControl = SecAccessControlCreateWithFlags(
nil,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
.biometryCurrentSet,
nil
)
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: data,
kSecAttrAccessControl as String: accessControl as Any
]
SecItemDelete(query as CFDictionary)
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else {
throw KeychainError.unexpectedStatus(status)
}
}
Exemplul arată configurarea Access Group pentru accesul partajat la Keychain între aplicațiile aceluiași dezvoltator. Necesită entitlement keychain-access-groups.
// Capabilities: Keychain Sharing activat
// App IDs: group.com.example.shared
func saveSharedToken(token: Data) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "shared_token",
kSecValueData as String: token,
kSecAttrAccessGroup as String:
"group.com.example.shared",
kSecAttrAccessible as String:
kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
SecItemAdd(query as CFDictionary, nil)
}
Utilizarea corectă a iOS Keychain necesită respectarea câtorva reguli cheie care previn vulnerabilitățile tipice și pierderea datelor.
Utilizați ThisDeviceOnly pentru toate secretele de autentificare: kSecAttrAccessibleWhenUnlockedThisDeviceOnly garantează că tokenurile nu vor ajunge în iCloud Backup. Dacă un atacator obține acces la backup, datele Keychain cu acest flag nu vor fi disponibile. Excepție o fac datele care trebuie să fie disponibile pe toate dispozitivele utilizatorului (de exemplu, chei de criptare pentru servicii proprii), pentru care utilizați kSecAttrAccessibleWhenUnlocked cu kSecAttrSynchronizable.
Nu stocați parole în formă brută — stocați hashuri sau tokenuri de sesiune. Apple Security Guide (2025) recomandă să nu salvați niciodată parola utilizatorului în Keychain în text clar. În schimb, salvați refresh token-ul obținut de la server după autentificarea reușită prin OAuth 2.0. Parola este utilizată doar pentru obținerea tokenului și este imediat ștearsă din memorie.
Gestionați corect erorile Keychain: fiecare operație cu Keychain returnează OSStatus care trebuie verificat. O atenție deosebită — errSecItemNotFound (tokenul a expirat sau a fost șters) și errSecAuthFailed (biometria a eșuat). În primul caz, aplicația trebuie să solicite o nouă autentificare, în al doilea — să arate utilizatorului o metodă alternativă (cod de acces). Niciodată nu ignorați statusul errSecItemNotFound — aceasta va duce la prăbușirea aplicației la încercarea de a citi nil.
Testați Keychain pe un dispozitiv real: simulatorul nu are Secure Enclave și nu suportă ACL-uri biometrice. Verificați întotdeauna scenariile: prima lansare, restaurarea din backup, schimbarea parolei dispozitivului, ștergerea și reinstalarea aplicației. Pe un dispozitiv real, Keychain se păstrează după ștergerea aplicației, dar numai dacă nu s-a utilizat flagul kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, care se curăță la eliminarea codului de acces.
Minimizați numărul de operații cu Keychain: fiecare operație de citire sau scriere este o apelare a serviciului de sistem Securityd, care poate bloca firul de execuție. Cache-uiți tokenurile citite în memorie pe durata sesiunii și accesați Keychain din nou doar la repornirea aplicației sau la eroare de autentificare (401 de la server). iOS blochează automat Keychain la blocarea dispozitivului, de aceea planificați citirea prin LAContext cu solicitare de biometrie.
Întrebări frecvente
Da, utilizați clasa de protecție kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly sau kSecAttrAccessibleAfterFirstUnlock. Datele vor fi disponibile după prima deblocare a dispozitivului după repornire. Pentru accesul automat la pornirea aplicației (fără a aștepta deblocarea), utilizați kSecAttrAccessibleAlways, dar aceasta reduce securitatea.
Apelați SecItemDelete cu query care conține kSecClass pentru fiecare tip de date. Pentru curățarea completă a tuturor elementelor aplicației, executați: SecItemDelete([kSecClass as String: kSecClassGenericPassword] as CFDictionary). Repetați pentru kSecClassKey, kSecClassCertificate și kSecClassInternetPassword.
kSecAttrAccessible determină când datele sunt disponibile (la deblocare, după prima deblocare etc.). kSecAttrAccessControl determină cine poate obține acces (biometrie, cod de acces, orice autentificare). Ele se combină: mai întâi Protection Class, apoi ACL. De exemplu, datele sunt disponibile doar la deblocare ȘI numai după Face ID.
Cauze: elementul nu a fost niciodată salvat, elementul a fost șters la eliminarea codului de acces (dacă s-a utilizat kSecAttrAccessibleWhenPasscodeSet), aplicația a fost reinstalată (Keychain se păstrează, dar nu se restaurează din backup pe un dispozitiv nou), s-a schimbat Access Group sau identificatorul echipei de dezvoltare. Verificați kSecAttrService și kSecAttrAccount.
Utilizați LAContext din LocalAuthentication: apelați context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil). Dacă returnează true — dispozitivul suportă Touch ID sau Face ID. Pentru ACL Keychain, utilizați flagul biometryCurrentSet (doar datele biometrice curente) sau biometryAny (orice date înregistrate anterior).
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și