Keychain è un archivio sicuro in iOS progettato per conservare in modo sicuro password, chiavi crittografiche, certificati e note riservate. Secondo Apple Security Documentation (2025), Keychain utilizza la crittografia hardware tramite Secure Enclave su tutti i dispositivi con chip A7 e successivi. Comprendere l'architettura di iOS Keychain è essenziale per ogni sviluppatore per archiviare correttamente token e segreti dell'applicazione.
Punti chiave
iOS Keychain è un meccanismo sicuro per archiviare dati riservati, integrato nel sistema operativo Apple. A differenza di UserDefaults o dei file normali, Keychain crittografa tutti gli elementi a livello hardware e fornisce un controllo degli accessi granulare basato su policy di sicurezza.
Keychain è stato introdotto in iOS 2.0 e da allora ha subito cambiamenti significativi: iOS 7 ha aggiunto il supporto per chiavi hardware tramite Secure Enclave, iOS 9 ha introdotto la condivisione Keychain tra app tramite Access Groups, iOS 13 ha aggiunto il supporto per il binding biometrico tramite LAContext. Secondo Apple WWDC Session (2024), oltre il 90% delle app iOS nella top 100 dell'App Store utilizza Keychain per archiviare token di autenticazione.
Architetturalmente, Keychain è un database SQLite crittografato situato al di fuori del sandbox dell'applicazione. Ogni elemento (SecItem) viene crittografato con una chiave separata, che a sua volta è protetta dalla chiave hardware Secure Enclave. Il servizio di sistema Securityd gestisce l'accesso a Keychain in base agli entitlement dell'applicazione e alla classe di protezione richiesta.
Un vantaggio importante di Keychain rispetto ad altri metodi di archiviazione: i dati vengono crittografati e decrittografati automaticamente dal sistema operativo. Lo sviluppatore non deve implementare manualmente la crittografia - basta chiamare SecItemAdd con i parametri corretti. iOS garantisce che i dati di Keychain non possano essere letti da altre applicazioni (quando Access Groups sono configurati correttamente).
L'architettura di Keychain include diversi livelli: fisico (Secure Enclave), sistema (Security.framework), applicazione (API SecItem*) e logico (Access Groups, Protection Classes). Comprendere ogni livello aiuta a progettare correttamente l'archiviazione dei segreti.
L'API principale per lavorare con Keychain sono le funzioni di Security framework: SecItemAdd per aggiungere, SecItemCopyMatching per leggere, SecItemUpdate per aggiornare e SecItemDelete per eliminare. Ogni funzione accetta un dizionario query che descrive gli attributi dell'elemento da cercare o salvare.
Attributi chiave della query: kSecClass - tipo di elemento (kSecClassGenericPassword, kSecClassKey, kSecClassCertificate), kSecAttrAccount - identificatore univoco all'interno della classe, kSecValueData - dati salvati (Data), kSecAttrAccessible - classe di protezione. SecItemCopyMatching con il flag kSecReturnData restituisce i dati dell'elemento, con kSecMatchLimit - il numero di risultati.
Importante: tutte le funzioni restituiscono un OSStatus. Un'operazione riuscita restituisce errSecSuccess (0). Errori: errSecItemNotFound (-25300) - elemento non trovato, errSecDuplicateItem (-25299) - elemento già esistente, errSecAuthFailed (-25293) - autenticazione biometrica fallita. Lo sviluppatore deve gestire correttamente ogni stato.
Protection Class è l'attributo kSecAttrAccessible che determina quando i dati in Keychain sono disponibili per la lettura. iOS supporta sei classi di protezione con diversi livelli di disponibilità e sicurezza.
La classe raccomandata per la maggior parte degli scenari è kSecAttrAccessibleWhenUnlockedThisDeviceOnly: i dati sono disponibili solo quando il dispositivo è sbloccato e non vengono copiati in iCloud Backup. Per i dati che devono essere disponibili dopo il riavvio (ma solo dopo il primo sblocco), utilizzare kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly. Per dati critici che richiedono autenticazione biometrica a ogni accesso, combinare kSecAttrAccessibleWhenUnlockedThisDeviceOnly con una ACL che richiede biometria.
Le classi senza il suffisso ThisDeviceOnly (kSecAttrAccessibleWhenUnlocked, kSecAttrAccessibleAfterFirstUnlock) consentono la copia in iCloud Backup. Questo è comodo per l'utente ma riduce la sicurezza - i dati possono essere ripristinati dal backup. Per i token di autenticazione, utilizzare sempre ThisDeviceOnly.
Access Control List (ACL) è un meccanismo che limita le operazioni su un elemento Keychain in base all'autenticazione dell'utente. L'ACL viene impostata tramite SecAccessControlCreateWithFlags e passata all'attributo kSecAttrAccessControl durante il salvataggio di un elemento.
Flag supportati: kSecAccessControlUserPresence - qualsiasi autenticazione (Face ID, Touch ID o codice di accesso), kSecAccessControlBiometryCurrentSet - solo biometria (impronte digitali o volto attualmente registrati), kSecAccessControlDevicePasscode - solo codice di accesso. L'ACL si applica a ogni operazione: leggere, aggiornare ed eliminare un elemento richiedono anch'essi autenticazione.
Su iOS 15+ è apparso il flag kSecAccessControlWatch - per Apple Watch, che consente l'autenticazione tramite orologio accoppiato. L'ACL può essere combinata: ad esempio, kSecAccessControlUserPresence o kSecAccessControlBiometryAny con un codice di accesso opzionale (orientamento .or).
iOS Keychain supporta quattro classi principali di elementi (kSecClass), ciascuna progettata per il proprio tipo di dati. Scegliere la classe giusta semplifica l'organizzazione e la ricerca degli elementi.
kSecClassGenericPassword - password generica: la classe più utilizzata. Archivia dati binari arbitrari (Data) con una chiave univoca (kSecAttrAccount). Adatto per token, chiavi API, codici PIN. Non richiede entitlement aggiuntivi per l'uso.
kSecClassInternetPassword - password Internet: archivia dati associati a una risorsa di rete. Attributi aggiuntivi: kSecAttrServer (dominio del server), kSecAttrProtocol (https, ftp), kSecAttrPort, kSecAttrAuthenticationType. iOS può compilare automaticamente tali password tramite AutoFill.
kSecClassKey - chiave crittografica: per archiviare chiavi di crittografia (AES, RSA, EC). La chiave viene archiviata come SecKeyRef, non come Data. kSecClassCertificate - certificato X.509 per archiviare e verificare certificati digitali. Entrambe le classi richiedono la comprensione delle operazioni crittografiche e una corretta configurazione degli attributi.
In pratica, il 95% dei casi d'uso di Keychain nelle app mobili è coperto da kSecClassGenericPassword per l'archiviazione di token di autenticazione e kSecClassKey per l'archiviazione di chiavi di crittografia private. kSecClassCertificate viene utilizzato raramente - tipicamente in app aziendali con PKI proprio.
Vediamo esempi pratici di lavoro con Keychain in Swift utilizzando Security framework. Ogni esempio include la gestione degli errori e la corretta configurazione della Protection Class.
Un esempio base salva un token di autenticazione in Keychain con protezione WhenUnlockedThisDeviceOnly. La chiave (kSecAttrAccount) è l'identificatore del servizio, i dati (kSecValueData) sono il token in formato 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)
}
L'esempio dimostra l'uso di SecAccessControlCreateWithFlags per legare una chiave alla biometria. Ogni accesso all'elemento richiederà Face ID o 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)
}
}
L'esempio mostra la configurazione di un Access Group per l'accesso condiviso a Keychain tra app dello stesso sviluppatore. Richiede l'entitlement keychain-access-groups.
// Capabilities: Keychain Sharing abilitato
// 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)
}
L'uso corretto di iOS Keychain richiede il rispetto di diverse regole chiave che prevengono vulnerabilità comuni e perdita di dati.
Utilizzare ThisDeviceOnly per tutti i segreti di autenticazione: kSecAttrAccessibleWhenUnlockedThisDeviceOnly garantisce che i token non finiscano in iCloud Backup. Se un utente malintenzionato ottiene accesso al backup, i dati Keychain con questo flag non saranno disponibili. L'eccezione sono i dati che devono essere disponibili su tutti i dispositivi dell'utente (ad esempio, chiavi di crittografia per servizi proprietari), per i quali utilizzare kSecAttrAccessibleWhenUnlocked con kSecAttrSynchronizable.
Non archiviare password in chiaro - archiviare hash o token di sessione. Apple Security Guide (2025) raccomanda di non salvare mai la password dell'utente in Keychain in testo chiaro. Invece, salvare il token di aggiornamento ricevuto dal server dopo l'autenticazione riuscita tramite OAuth 2.0. La password viene utilizzata solo per ottenere il token e viene immediatamente rimossa dalla memoria.
Gestire correttamente gli errori Keychain: ogni operazione Keychain restituisce un OSStatus che deve essere verificato. Prestare particolare attenzione a errSecItemNotFound (token scaduto o eliminato) e errSecAuthFailed (biometria fallita). Nel primo caso, l'app dovrebbe richiedere una nuova autenticazione; nel secondo, mostrare all'utente un metodo alternativo (codice di accesso). Non ignorare mai lo stato errSecItemNotFound - causerà un arresto anomalo durante il tentativo di leggere nil.
Testare Keychain su un dispositivo reale: il simulatore non ha Secure Enclave e non supporta ACL biometrici. Verificare sempre gli scenari: primo avvio, ripristino da backup, cambio password del dispositivo, eliminazione e reinstallazione dell'app. Su un dispositivo reale, Keychain persiste quando l'app viene eliminata, ma solo se non è stato utilizzato il flag kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, che viene cancellato quando il codice di accesso viene rimosso.
Minimizzare il numero di operazioni Keychain: ogni operazione di lettura o scrittura è una chiamata al servizio di sistema Securityd, che può bloccare il thread. Memorizzare nella cache i token letti in memoria per la durata della sessione e accedere nuovamente a Keychain solo al riavvio dell'app o in caso di errore di autenticazione (401 dal server). iOS blocca automaticamente Keychain quando il dispositivo viene bloccato, quindi pianificare la lettura tramite LAContext con una richiesta biometrica.
Domande frequenti
Sì, utilizzare la classe di protezione kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly o kSecAttrAccessibleAfterFirstUnlock. I dati saranno disponibili dopo il primo sblocco del dispositivo dopo un riavvio. Per l'accesso automatico all'avvio dell'app (senza attendere lo sblocco), utilizzare kSecAttrAccessibleAlways, ma ciò riduce la sicurezza.
Chiamare SecItemDelete con una query contenente kSecClass per ogni tipo di dati. Per la pulizia completa di tutti gli elementi dell'app, eseguire: SecItemDelete([kSecClass as String: kSecClassGenericPassword] as CFDictionary). Ripetere per kSecClassKey, kSecClassCertificate e kSecClassInternetPassword.
kSecAttrAccessible determina quando i dati sono disponibili (allo sblocco, dopo il primo sblocco, ecc.). kSecAttrAccessControl determina chi può accedere (biometria, codice di accesso, qualsiasi autenticazione). Si combinano: prima Protection Class, poi ACL. Ad esempio, i dati sono disponibili solo allo sblocco E solo dopo Face ID.
Motivi: l'elemento non è mai stato salvato, l'elemento è stato eliminato alla rimozione del codice di accesso (se è stato utilizzato kSecAttrAccessibleWhenPasscodeSet), l'app è stata reinstallata (Keychain persiste ma non viene ripristinato dal backup su un nuovo dispositivo), Access Group o identificatore del team di sviluppatori è cambiato. Verificare kSecAttrService e kSecAttrAccount.
Utilizzare LAContext da LocalAuthentication: chiamare context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil). Se restituisce true - il dispositivo supporta Touch ID o Face ID. Per ACL Keychain, utilizzare il flag biometryCurrentSet (solo dati biometrici correnti) o biometryAny (qualsiasi dato registrato in precedenza).
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche