Keychain (Łańcuch kluczy) — to chronione przechowywanie w iOS, przeznaczone do bezpiecznego przechowywania haseł, kluczy kryptograficznych, certyfikatów i poufnych notatek. Według Apple Security Documentation (2025), Keychain używa sprzętowego szyfrowania przez Secure Enclave na wszystkich urządzeniach z układem A7 i nowszym. Zrozumienie architektury iOS Keychain jest niezbędne każdemu programiście do prawidłowego przechowywania tokenów i sekretów aplikacji.
Najważniejsze
iOS Keychain — to chroniony mechanizm przechowywania poufnych danych wbudowany w system operacyjny Apple. W przeciwieństwie do UserDefaults lub zwykłych plików, Keychain szyfruje wszystkie elementy na poziomie sprzętowym i zapewnia precyzyjną kontrolę dostępu opartą na politykach bezpieczeństwa.
Keychain został wprowadzony w iOS 2.0 i od tego czasu przeszedł znaczące zmiany: w iOS 7 dodano obsługę kluczy sprzętowych przez Secure Enclave, w iOS 9 — podział Keychain między aplikacjami przez Access Groups, w iOS 13 — obsługę biometrycznego wiązania przez LAContext. Według Apple WWDC Session (2024), ponad 90% aplikacji iOS w top 100 App Store używa Keychain do przechowywania tokenów uwierzytelniania.
Architektonicznie Keychain to zaszyfrowana baza danych SQLite, znajdująca się poza piaskownicą aplikacji. Każdy element (SecItem) jest szyfrowany osobnym kluczem, który z kolei jest chroniony sprzętowym kluczem Secure Enclave. Systemowa usługa Securityd zarządza dostępem do Keychain na podstawie uprawnień aplikacji (entitlements) i żądanej klasy ochrony.
Ważną zaletą Keychain w porównaniu do innych sposobów przechowywania jest to, że dane są automatycznie szyfrowane i deszyfrowane przez system. Programista nie musi implementować kryptografii ręcznie — wystarczy wywołać SecItemAdd z odpowiednimi parametrami. iOS gwarantuje, że dane z Keychain nie mogą zostać odczytane przez inne aplikacje (przy prawidłowej konfiguracji Access Groups).
Architektura Keychain obejmuje kilka poziomów: fizyczny (Secure Enclave), systemowy (Security.framework), aplikacyjny (API SecItem*) i logiczny (Access Groups, Protection Classes). Zrozumienie każdego poziomu pomaga prawidłowo projektować przechowywanie sekretów.
Podstawowe API do pracy z Keychain to funkcje Security framework: SecItemAdd do dodawania, SecItemCopyMatching do odczytu, SecItemUpdate do aktualizacji i SecItemDelete do usuwania. Każda funkcja przyjmuje słownik query, który opisuje atrybuty szukanego lub zapisywanego elementu.
Kluczowe atrybuty query: kSecClass — typ elementu (kSecClassGenericPassword, kSecClassKey, kSecClassCertificate), kSecAttrAccount — unikalny identyfikator w ramach klasy, kSecValueData — przechowywane dane (Data), kSecAttrAccessible — klasa ochrony. SecItemCopyMatching z flagą kSecReturnData zwraca dane elementu, z kSecMatchLimit — liczbę wyników.
Ważne: wszystkie funkcje zwracają status OSStatus. Udana operacja zwraca errSecSuccess (0). Błędy: errSecItemNotFound (-25300) — element nie znaleziony, errSecDuplicateItem (-25299) — element już istnieje, errSecAuthFailed (-25293) — uwierzytelnianie biometryczne nie powiodło się. Programista powinien prawidłowo obsługiwać każdy status.
Protection Class — atrybut kSecAttrAccessible, który określa, kiedy dane w Keychain są dostępne do odczytu. iOS obsługuje sześć klas ochrony z różnym poziomem dostępności i bezpieczeństwa.
Zalecaną klasą dla większości scenariuszy jest kSecAttrAccessibleWhenUnlockedThisDeviceOnly: dane są dostępne tylko przy odblokowanym urządzeniu i nie są kopiowane do iCloud Backup. Dla danych, które mają być dostępne po restarcie (ale dopiero po pierwszym odblokowaniu), użyj kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly. Dla krytycznych danych wymagających uwierzytelniania biometrycznego przy każdym dostępie, połącz kSecAttrAccessibleWhenUnlockedThisDeviceOnly z ACL wymagającym biometrii.
Klasy bez sufiksu ThisDeviceOnly (kSecAttrAccessibleWhenUnlocked, kSecAttrAccessibleAfterFirstUnlock) pozwalają na kopiowanie do iCloud Backup. Jest to wygodne dla użytkownika, ale obniża bezpieczeństwo — dane mogą zostać odzyskane z kopii zapasowej. Dla tokenów uwierzytelniania zawsze używaj ThisDeviceOnly.
Access Control List (ACL) — mechanizm ograniczający operacje na elemencie Keychain na podstawie uwierzytelniania użytkownika. ACL jest ustawiany przez SecAccessControlCreateWithFlags i przekazywany w atrybucie kSecAttrAccessControl podczas zapisywania elementu.
Obsługiwane flagi: kSecAccessControlUserPresence — dowolne uwierzytelnianie (Face ID, Touch ID lub kod dostępu), kSecAccessControlBiometryCurrentSet — tylko biometria (aktualnie zarejestrowane odciski palców lub twarz), kSecAccessControlDevicePasscode — tylko kod dostępu. ACL jest stosowany do każdej operacji: odczyt, aktualizacja i usunięcie elementu również wymagają uwierzytelniania.
W iOS 15+ pojawiła się flaga kSecAccessControlWatch — dla Apple Watch, która pozwala na uwierzytelnianie przez sparowany zegarek. ACL można łączyć: na przykład kSecAccessControlUserPresence lub kSecAccessControlBiometryAny z opcjonalnym kodem dostępu (orientacja .or).
iOS Keychain obsługuje cztery główne klasy elementów (kSecClass), z których każda jest przeznaczona dla swojego typu danych. Właściwy wybór klasy upraszcza organizację i wyszukiwanie elementów.
kSecClassGenericPassword — ogólne hasło: najczęściej używana klasa. Przechowuje dowolne dane binarne (Data) z unikalnym kluczem (kSecAttrAccount). Nadaje się do tokenów, kluczy API, kodów PIN. Nie wymaga dodatkowych uprawnień (entitlements) do użycia.
kSecClassInternetPassword — hasło internetowe: przechowuje dane związane z zasobem sieciowym. Dodatkowe atrybuty: kSecAttrServer (domena serwera), kSecAttrProtocol (https, ftp), kSecAttrPort, kSecAttrAuthenticationType. iOS może automatycznie wypełniać takie hasła przez AutoFill.
kSecClassKey — klucz kryptograficzny: do przechowywania kluczy szyfrowania (AES, RSA, EC). Klucz jest przechowywany jako SecKeyRef, a nie jako Data. kSecClassCertificate — certyfikat X.509 do przechowywania i weryfikacji certyfikatów cyfrowych. Obie klasy wymagają zrozumienia operacji kryptograficznych i prawidłowej konfiguracji atrybutów.
W praktyce 95% przypadków użycia Keychain w aplikacjach mobilnych pokrywają kSecClassGenericPassword do przechowywania tokenów uwierzytelniania i kSecClassKey do przechowywania prywatnych kluczy szyfrowania. kSecClassCertificate jest używany rzadko — zazwyczaj w aplikacjach korporacyjnych z własnym PKI.
Rozważmy praktyczne przykłady pracy z Keychain w Swift z użyciem Security framework. Każdy przykład zawiera obsługę błędów i prawidłową konfigurację Protection Class.
Podstawowy przykład zapisuje token uwierzytelniania w Keychain z ochroną WhenUnlockedThisDeviceOnly. Klucz (kSecAttrAccount) to identyfikator usługi, dane (kSecValueData) to token w formacie 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)
}
Przykład demonstruje użycie SecAccessControlCreateWithFlags do wiązania klucza z biometrią. Każdy dostęp do elementu będzie wymagał Face ID lub 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)
}
}
Przykład pokazuje konfigurację Access Group dla wspólnego dostępu do Keychain między aplikacjami tego samego programisty. Wymaga entitlement keychain-access-groups.
// Capabilities: Keychain Sharing е включено
// 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)
}
Prawidłowe użycie iOS Keychain wymaga przestrzegania kilku kluczowych zasad, które zapobiegają typowym podatnościom i utracie danych.
Używaj ThisDeviceOnly dla wszystkich sekretów uwierzytelniania: kSecAttrAccessibleWhenUnlockedThisDeviceOnly gwarantuje, że tokeny nie trafią do iCloud Backup. Jeśli osoba atakująca uzyska dostęp do kopii zapasowej, dane Keychain z tą flagą nie będą dostępne. Wyjątkiem są dane, które muszą być dostępne na wszystkich urządzeniach użytkownika (na przykład klucze szyfrowania dla własnych usług), dla których użyj kSecAttrAccessibleWhenUnlocked z kSecAttrSynchronizable.
Nie przechowuj surowych haseł — przechowuj hasze lub tokeny sesji. Apple Security Guide (2025) zaleca, aby nigdy nie zapisywać hasła użytkownika w Keychain w postaci otwartego tekstu. Zamiast tego przechowuj refresh token uzyskany od serwera po pomyślnym uwierzytelnieniu przez OAuth 2.0. Hasło jest używane tylko do uzyskania tokena i natychmiast usuwane z pamięci.
Prawidłowo obsługuj błędy Keychain: każda operacja z Keychain zwraca OSStatus, który należy sprawdzić. Szczególną uwagę zwróć na errSecItemNotFound (token wygasł lub został usunięty) i errSecAuthFailed (biometria nie powiodła się). W pierwszym przypadku aplikacja powinna poprosić o nowe uwierzytelnienie, w drugim — pokazać użytkownikowi alternatywny sposób (kod dostępu). Nigdy nie ignoruj statusu errSecItemNotFound — doprowadzi to do awarii aplikacji próbującej odczytać nil.
Testuj Keychain na rzeczywistym urządzeniu: symulator nie ma Secure Enclave i nie obsługuje biometrycznych ACL. Zawsze sprawdzaj scenariusze: pierwsze uruchomienie, przywrócenie z kopii zapasowej, zmiana hasła urządzenia, usunięcie i ponowna instalacja aplikacji. Na rzeczywistym urządzeniu Keychain jest zachowywany po usunięciu aplikacji, ale tylko jeśli nie użyto flagi kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, która jest czyszczona po usunięciu kodu dostępu.
Minimalizuj liczbę operacji na Keychain: każda operacja odczytu lub zapisu to wywołanie systemowej usługi Securityd, która może blokować wątek. Buforuj odczytane tokeny w pamięci na czas sesji i odwołuj się do Keychain ponownie tylko przy ponownym uruchomieniu aplikacji lub błędzie uwierzytelnienia (401 od serwera). iOS automatycznie blokuje Keychain przy zablokowaniu urządzenia, dlatego planuj odczyt przez LAContext z żądaniem biometrii.
Często zadawane pytania
Tak, użyj klasy ochrony kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly lub kSecAttrAccessibleAfterFirstUnlock. Dane będą dostępne po pierwszym odblokowaniu urządzenia po restarcie. Aby uzyskać automatyczny dostęp przy uruchomieniu aplikacji (bez oczekiwania na odblokowanie), użyj kSecAttrAccessibleAlways, ale zmniejsza to bezpieczeństwo.
Wywołaj SecItemDelete z query zawierającym kSecClass dla każdego typu danych. Aby całkowicie wyczyścić wszystkie elementy aplikacji, wykonaj: SecItemDelete([kSecClass as String: kSecClassGenericPassword] as CFDictionary). Powtórz dla kSecClassKey, kSecClassCertificate i kSecClassInternetPassword.
kSecAttrAccessible określa, kiedy dane są dostępne (przy odblokowaniu, po pierwszym odblokowaniu itd.). kSecAttrAccessControl określa, kto może uzyskać dostęp (biometria, kod dostępu, dowolne uwierzytelnienie). Są one łączone: najpierw Protection Class, potem ACL. Na przykład dane są dostępne tylko przy odblokowaniu I tylko po Face ID.
Przyczyny: element nigdy nie został zapisany, element został usunięty po usunięciu kodu dostępu (jeśli użyto kSecAttrAccessibleWhenPasscodeSet), aplikacja została ponownie zainstalowana (Keychain jest zachowywany, ale nie jest przywracany z kopii zapasowej na nowym urządzeniu), zmieniła się Access Group lub identyfikator zespołu programisty. Sprawdź kSecAttrService i kSecAttrAccount.
Użyj LAContext z LocalAuthentication: wywołaj context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil). Jeśli zwraca true — urządzenie obsługuje Touch ID lub Face ID. Dla ACL Keychain użyj flagi biometryCurrentSet (tylko aktualne dane biometryczne) lub biometryAny (dowolne wcześniej zarejestrowane).
Podsumowanie
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също