Introduzione alla conservazione sicura dei dati su iOS

La protezione dei dati degli utenti sensibili è una responsabilità fondamentale di qualsiasi applicazione iOS. Se si memorizzano i token di autenticazione, le chiavi di crittografia, o le credenziali private, la piattaforma fornisce una soluzione hardware-backed dedicata: il Keychain]. A differenza o i file di lista di proprietà, il Keychain crittografa i dati a riposo e applica controlli di archiviazione rigorosi.

Capire il portachiavi iOS

Il Keychain è un contenitore di archiviazione sicuro gestito dal sistema operativo. Conserva piccoli oggetti sensibili, come password, chiavi crittografiche o certificati, in un database crittografato. I dati scritti sulla Keychain sono protetti anche quando il dispositivo è bloccato.

  • Crittografia a riposo[]] utilizzando AES-256 supportato dall'hardware.
  • Controllo accesso[]] tramite codice di accesso del dispositivo, ID touch o Face ID.
  • Persistenza attraverso le reinstallazioni delle app[[] (se configurate) e la sincronizzazione iCloud opzionale.
  • Isolazione[]]] tra le app: per impostazione predefinita, un'app non può leggere gli elementi Keychain di un'altra app a meno che non condividano un gruppo di accesso Keychain.

Il Keychain non è progettato per grandi blobs; tenere ogni elemento sotto pochi kilobyte. Per i dati più grandi, considerare l'utilizzo dell'API o del framework ] insieme alla crittografia basata su file.

Keychain Services API vs. Biblioteche di terze parti

Apple fornisce la comprensione madre Keychain Services API (C-based, ), che è potente ma verbose. È possibile utilizzarlo direttamente, o adottare un wrapper Swift-friendly.

Impostazione di archiviazione portachiavi

Prima di memorizzare qualcosa, è necessario decidere sul Keychain classe voce[. Il più comune per password generiche è []. Per le password Internet o i certificati, ci sono altre classi. Ogni articolo è riferito da un insieme di attributi — un dizionario (CFDictionary) che descrive l'articolo.

Il flusso di base segue sempre questo modello:

  1. Costruire un dizionario di query con la classe di oggetti e gli attributi.
  2. Chiamare la funzione appropriata ([, , , []).
  3. Controllare il reso ( o un codice di errore).

Prima di scrivere il codice, importare il modulo di sicurezza:

import Security
import Foundation // for Data and String utilities

Memorizzazione dei dati nel Portachiavi

Scrivere una password generica

Per salvare un token (ad esempio, un JWT) per l'utente corrente:

func saveToken(_ token: String, forAccount account: String) -> Bool {
 guard let tokenData = token.data(using: .utf8) else { return false }

 let query: [String: Any] = [
 kSecClass as String: kSecClassGenericPassword,
 kSecAttrAccount as String: account,
 kSecValueData as String: tokenData,
 // Optional: restrict access to when device is unlocked
 kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
 ]

 // Delete any existing item first to avoid duplicates
 SecItemDelete(query as CFDictionary)

 let status = SecItemAdd(query as CFDictionary, nil)
 return status == errSecSuccess
}

Punti chiave:

  • agisce come una chiave primaria; scegliere una stringa unica (ad esempio, l'ID utente o una costante come ).
  • ] controlla quando l'elemento può essere letto. Usa [[] per la migliore sicurezza; impedisce il backup di iCloud e limita l'accesso al dispositivo corrente.
  • Noi chiamiamo prima di aggiungere per evitare di accumulare oggetti duplicati. In alternativa, è possibile utilizzare .

Aggiunta di controllo di accesso (Biometria o codice di passaggio)

Per i dati altamente sensibili, richiedere Touch ID o Face ID prima di leggere:

let accessControl = SecAccessControlCreateWithFlags(
 nil,
 kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
 .userPresence, // requires passcode, Face ID, or Touch ID
 nil
)

let query: [String: Any] = [
 kSecClass as String: kSecClassGenericPassword,
 kSecAttrAccount as String: account,
 kSecValueData as String: tokenData,
 kSecAttrAccessControl as String: accessControl as Any
]
SecItemAdd(query as CFDictionary, nil)

Ora qualsiasi chiamata per questo articolo innescherà un prompt dei codici biometrici o di codice di accesso.

Recuperare i dati dal Portachiavi

Per leggere il token memorizzato:

func retrieveToken(forAccount account: String) -> String? {
 let query: [String: Any] = [
 kSecClass as String: kSecClassGenericPassword,
 kSecAttrAccount as String: account,
 kSecReturnData as String: true,
 kSecMatchLimit as String: kSecMatchLimitOne
 ]

 var item: CFTypeRef?
 let status = SecItemCopyMatching(query as CFDictionary, &item)

 guard status == errSecSuccess,
 let data = item as? Data,
 let token = String(data: data, encoding: .utf8) else {
 return nil
 }
 return token
}

Imposta a ] per ottenere i dati indietro. Utilizzare [ per recuperare un singolo risultato. Se ometti il limite, l'API può restituire un array.

Important:[] Quando si utilizza il controllo di accesso (biometria), la chiamata [] potrebbe tornare [] se l'utente annulla.

Aggiornamento e cancellazione di elementi portachiavi

Aggiornare un articolo esistente

Invece di eliminare e rifornire, usare :

func updateToken(_ newToken: String, forAccount account: String) -> Bool {
 guard let newData = newToken.data(using: .utf8) else { return false }

 let query: [String: Any] = [
 kSecClass as String: kSecClassGenericPassword,
 kSecAttrAccount as String: account
 ]

 let attributesToUpdate: [String: Any] = [
 kSecValueData as String: newData
 ]

 let status = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary)
 return status == errSecSuccess
}

Questo è più efficiente di un delete+add, ed evita potenziali condizioni di gara.

Cancellare un oggetto

func deleteItem(forAccount account: String) -> Bool {
 let query: [String: Any] = [
 kSecClass as String: kSecClassGenericPassword,
 kSecAttrAccount as String: account
 ]
 let status = SecItemDelete(query as CFDictionary)
 return status == errSecSuccess
}

Fare attenzione a non eliminare elementi che appartengono ad altre applicazioni che condividono lo stesso gruppo di accesso, sempre discutiamo la tua richiesta con se si utilizza Keychains condiviso.

Controllo di accesso e Accessibilità Attributi

] definisce costantemente [ quando[] l'elemento portachiavi può essere letto. Scegliere l'opzione più restrittiva che soddisfa ancora le esigenze della tua app:

AttributeMeaning
kSecAttrAccessibleWhenUnlockedAvailable only while device is unlocked (default).
kSecAttrAccessibleAfterFirstUnlockAvailable after device boots and is unlocked once. Allows background access.
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnlyRequires a passcode to be set. Strictest option—prevents access even after unlock if passcode is removed.
kSecAttrAccessibleWhenUnlockedThisDeviceOnlySame as WhenUnlocked but does not back up to iCloud, and cannot be restored to another device.

Per la maggior parte delle applicazioni, ] colpisce il giusto equilibrio tra sicurezza e usabilità. Se avete bisogno di leggere gli elementi in background (ad esempio, un token di aggiornamento di sfondo), è necessario utilizzare (e accettare che i dati sono leggermente meno protetti).

Gestione degli errori e Pitfalls comuni

Le funzioni ritornano a []. Controllare sempre e gestire i guasti in modo appropriato.

  • (–25300) – Nessun elemento corrisponde alla query.
  • (–25299) – Un elemento con la stessa chiave primaria già esistente (se non hai eliminato prima).
  • (–128) – L'utente ha cancellato il prompt biometrico.
  • (–25293) – L'autenticazione non è stata valida o non è disponibile.

Non ignorare mai lo stato di non successo. Degrado con grazia: mostrare un messaggio di errore o riprovare, ma non memorizzare mai i dati sensibili al di fuori della Keychain come un fallback. È possibile utilizzare ] per controllare la disponibilità biometrica prima di tentare l'accesso.

Migliori Pratiche e Considerazioni di Produzione

  • Utilizzare nomi di account descrittivi unici[[] per utente o per tipo di articolo per evitare collisioni.
  • Sempre specificare un attributo di accessibilità[; altrimenti, si applica il default del sistema (]), che potrebbe non essere ideale.
  • Clear dati della catena di tasti quando l'utente effettua l'accesso[[]—indicare su tutti gli account noti ed eliminare gli elementi.
  • Usa Keychain Access Groups[[]] solo quando si condivide tra le proprie applicazioni.
  • Non memorizzare dati non sensibili[[] (come le preferenze dell'utente) nel Portachiavi—utilizzare o un database invece.
  • Consider usando [ con []] per scenari avanzati (macOS Catalyst).
  • Test su un dispositivo reale[[[]; il Simulatore utilizza un software Keychain che si comporta in modo diverso da quello di archiviazione supportato dall'hardware.

Utilizzo di Portachiavi con SwiftUI e Asincrona/Await

Per le applicazioni moderne, avvolgere le operazioni di Keychain in un attore o una classe asincastro per evitare di bloccare il thread principale. Esempio utilizzando :

actor KeychainManager {
 func saveToken(_ token: String, for account: String) async -> Bool {
 // same implementation as above, but now it's safe to call from any context
 return saveToken(token, forAccount: account)
 }
}

Se si utilizza biometrica, la chiamata [] può bloccare il thread in attesa dell'interazione dell'utente. Avvolgerlo in una coda di sfondo, o meglio, utilizzare [] ] metodo prima della chiamata Keychain.

Conclusioni

The iOS Keychain is the correct place to store small, sensitive pieces of data. By using the native Keychain Services API, you gain direct control over encryption, accessibility, and authentication policies. Always pair your Keychain usage with solid error handling and remember to clear data when appropriate. For further reading, refer to the Apple Keychain Service Documentation and the Keychain Concepts overview. Adopting these practices will help you ship iOS apps that respect user privacy and withstand security scrutiny.