Keychain — to bezpieczne repozytorium danych uwierzytelniających w iOS, watchOS, macOS i tvOS, które zarządza hasłami, kluczami i certyfikatami na poziomie sprzętowym. W przeciwieństwie do UserDefaults, dane w Keychain są szyfrowane i izolowane w sandboxie aplikacji. Według Apple Developer Documentation, Keychain wykorzystuje mechanizmy Secure Enclave i sprzętowe szyfrowanie AES-256 do ochrony poufnych informacji.
Najważniejsze
Keychain — to bezpieczna baza danych systemu operacyjnego Apple, przeznaczona do bezpiecznego przechowywania haseł, kluczy kryptograficznych, certyfikatów i innych poufnych danych. Po raz pierwszy Keychain pojawił się w Mac OS 8.6 w 1999 roku, a na iOS — od pierwszej wersji SDK.
Keychain rozwiązuje fundamentalny problem programowania mobilnego: przechowywanie tajemnic (tokenów, haseł, kluczy szyfrowania) w niezabezpieczonym systemie plików. W przeciwieństwie do UserDefaults czy SQLite, dane w Keychain są przechowywane w zaszyfrowanej postaci i automatycznie szyfrowane podczas zapisu na dysk.
Każda aplikacja iOS działa w izolowanym sandboxie i ma dostęp tylko do swojego keychain. Deweloper może jednak skonfigurować współdzielony dostęp do Keychain między aplikacjami tego samego dewelopera przez keychain access groups. Użytkownik iCloud Keychain dodatkowo synchronizuje dane między urządzeniami przez szyfrowanie end-to-end.
Według Apple Security White Paper (2025), Keychain używa sprzętowego szyfrowania AES-256 z kluczem powiązanym z unikalnym identyfikatorem urządzenia (UID), co sprawia, że dane są niedostępne przy wyodrębnieniu z urządzenia.
Keychain funkcjonuje jako usługa warstwowa: aplikacja odwołuje się do Security framework, który współdziała z demonem securityd, zarządzającym zaszyfrowaną bazą SQLite Keychain. Każdy wpis jest szyfrowany oddzielnym kluczem, a same klucze są chronione sprzętowym modułem Secure Enclave na urządzeniach z chipem A7 i nowszym.
Security framework udostępnia interfejs C do pracy z Keychain Services. Główne funkcje: SecItemAdd (dodawanie), SecItemCopyMatching (wyszukiwanie), SecItemUpdate (aktualizacja) i SecItemDelete (usuwanie). Każda operacja przyjmuje słownik atrybutów CFDictionaryRef opisujący typ zapytania.
Dane są klasyfikowane według klas: kSecClassGenericPassword (dowolne hasło), kSecClassInternetPassword (hasło do serwisu internetowego), kSecClassCertificate (certyfikat), kSecClassKey (klucz kryptograficzny) i kSecClassIdentity (połączenie klucza i certyfikatu).
Podczas zapisywania hasła aplikacja tworzy słownik z atrybutami: serwis, konto, poziom dostępu, etykieta. Security framework przekazuje dane do demona securityd, który szyfruje wpis kluczem powiązanym z urządzeniem i zapisuje w Keychain SQLite. Odczyt odbywa się analogicznie: zapytanie według atrybutów, wyszukiwanie, deszyfrowanie i zwrócenie danych.
Ważna cecha: podczas usuwania aplikacji iOS automatycznie czyści Keychain z wpisów utworzonych przez tę aplikację (od iOS 10.3+). Po ponownej instalacji aplikacji dane Keychain z poprzedniej instalacji są niedostępne, chyba że używano iCloud Keychain.
import Security
func savePassword(service: String, account: String, password: Data) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecValueData as String: password,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
]
SecItemAdd(query as CFDictionary, nil)
}
Keychain obsługuje pięć głównych klas danych, z których każda ma swój zestaw atrybutów i obszar zastosowania. Od wyboru klasy zależy, które klucze wyszukiwania są dostępne i jak dane są szyfrowane.
Klasy kSecClassGenericPassword i kSecClassInternetPassword są przeznaczone do przechowywania haseł tekstowych. Wersja internetowa dodatkowo przechowuje dane serwera: domenę, protokół, port i ścieżkę uwierzytelniania. GenericPassword jest używany do przechowywania dowolnych haseł bez powiązania z serwerem.
Podczas zapisywania hasła internetowego można podać kSecAttrServer (domena), kSecAttrProtocol (HTTPS, FTP itp.), kSecAttrPort i kSecAttrPath. Pozwala to dokładnie zidentyfikować wpis podczas automatycznego wypełniania przez AutoFill.
Klasa kSecClassKey przechowuje klucze symetryczne (AES) i asymetryczne (RSA, EC). Klasa kSecClassCertificate — certyfikaty X.509. Klasa kSecClassIdentity łączy klucz prywatny i odpowiedni certyfikat w jeden wpis.
Dla asymetrycznych kluczy Keychain obsługuje atrybuty: kSecAttrKeyType (RSA, EC), kSecAttrKeySizeInBits (2048, 256), kSecAttrIsPermanent (czy zapisywać w Keychain). Dla certyfikatów: kSecAttrLabel (nazwa czytelna dla człowieka), kSecAttrSubject (DN właściciela), kSecAttrIssuer (DN wystawcy).
| Klasa Keychain | Przeznaczenie | Typ danych |
|---|---|---|
| kSecClassGenericPassword | Hasło aplikacji | Data (NSString) |
| kSecClassInternetPassword | Hasło do serwera | Data + atrybuty serwera |
| kSecClassCertificate | Certyfikat X.509 | SecCertificate |
| kSecClassKey | Klucz kryptograficzny | SecKey |
| kSecClassIdentity | Klucz + certyfikat | SecIdentity |
Główny interfejs interakcji z Keychain w natywnym kodzie iOS — funkcje Security framework. Dla Swift i Objective-C dostępny jest jeden interfejs C, opakowany w wygodne funkcje.
Funkcja SecItemAdd dodaje nowy element do Keychain. Na wejściu przyjmuje słownik z klasą, danymi i atrybutami. Zwraca status errSecSuccess (0) lub kod błędu. Funkcja SecItemCopyMatching wykonuje wyszukiwanie według atrybutów i zwraca znalezione dane.
Do wyszukiwania używana jest flaga kSecReturnData — jeśli true, funkcja zwraca zawartość wpisu. Flaga kSecMatchLimit określa liczbę wyników: kSecMatchLimitOne (jeden) lub kSecMatchLimitAll (wszystkie).
func readPassword(service: String, account: String) -> Data? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
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 else { return nil }
return item as? Data
}
SecItemUpdate pozwala zmienić atrybuty lub dane istniejącego wpisu. SecItemDelete usuwa wszystkie wpisy odpowiadające przekazanemu słownikowi. Podczas usuwania ważne jest podanie wystarczającej liczby atrybutów, aby przypadkowo nie usunąć cudzych wpisów w shared keychain.
func deletePassword(service: String, account: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account
]
SecItemDelete(query as CFDictionary)
}
Każdy wpis Keychain ma atrybut kSecAttrAccessible, który określa, kiedy dane są dostępne do odczytu. Jest to krytyczne dla bezpieczeństwa: nieprawidłowy poziom dostępu może uczynić dane podatnymi lub, przeciwnie, niedostępnymi w odpowiednim momencie.
kSecAttrAccessibleWhenUnlocked — dane dostępne tylko gdy urządzenie jest odblokowane. kSecAttrAccessibleAfterFirstUnlock — dostępne po pierwszym odblokowaniu po restarcie. kSecAttrAccessibleAlways — dostępne zawsze (niezalecane). Postfiks ThisDeviceOnly zabrania przenoszenia danych przez kopię zapasową.
Na urządzeniach z Secure Enclave klucz szyfrowania Keychain może być dodatkowo chroniony biometrią: kSecAccessControlBiometryCurrentSet lub kSecAccessControlUserPresence. Dodaje to wymóg Face ID lub Touch ID dla każdej operacji odczytu.
iCloud Keychain synchronizuje wpisy między urządzeniami Apple przez szyfrowanie end-to-end. Apple nie ma dostępu do odszyfrowanych danych. Aby włączyć synchronizację, dodaj atrybut kSecAttrSynchronizable z wartością kCFBooleanTrue.
Ważne: synchronizowane wpisy nie mogą zawierać atrybutu ThisDeviceOnly, ponieważ muszą być dostępne na innych urządzeniach. W przypadku utraty wszystkich urządzeń iCloud Keychain jest przywracany za pomocą kodu odzyskiwania lub klucza zapasowego iCloud.
Dla tokenów uwierzytelniania używaj kSecAttrAccessibleWhenUnlockedThisDeviceOnly — dane dostępne tylko przy odblokowanym urządzeniu i niekopiowane do kopii zapasowej. Dla certyfikatów, które muszą być dostępne dla zadań w tle, używaj kSecAttrAccessibleAfterFirstUnlock. Nigdy nie używaj kSecAttrAccessibleAlways dla poufnych danych.
Zaleca się zawsze dodawać postfiks ThisDeviceOnly dla danych niewymagających synchronizacji. Zapobiega to przypadkowemu kopiowaniu sekretów do iCloud lub iTunes backup, gdzie mogą być wyodrębnione narzędziami analitycznymi. Dla krytycznych danych łącz dostępność z kSecAccessControlUserPresence — dodaje to wymóg biometrii lub kodu do odszyfrowania wpisu.
Przy korzystaniu z iCloud Keychain pamiętaj, że synchronizowane wpisy są dostępne na wszystkich urządzeniach użytkownika: jeśli atakujący uzyska dostęp do jednego urządzenia, dane mogą być skompromitowane na wszystkich. Dla izolowanych sekretów używaj ThisDeviceOnly.
Często zadawane pytania
UserDefaults przechowuje dane w otwartej postaci w pliku .plist, dostępnym podczas analizy IPA. Keychain szyfruje każdy wpis oddzielnie, używa sprzętowego szyfrowania i obsługuje poziomy dostępu, biometrię i synchronizację przez iCloud.
Podczas każdego uruchomienia na symulatorze iOS Keychain jest czyszczony. Na rzeczywistym urządzeniu usuń aplikację — iOS usunie wszystkie wpisy Keychain tego dewelopera. Do selektywnego czyszczenia wywołaj SecItemDelete z odpowiednimi atrybutami.
Tak, przez keychain access groups. Aplikacje muszą mieć ten sam team ID w podpisie i entitlements z tym samym keychain-access-groups. Deweloper konfiguruje współdzielony dostęp w Xcode Capabilities.
Apple nie dokumentuje ścisłego limitu, ale w praktyce zaleca się nie przekraczać 1–4 KB na wpis. Dla dużych wolumenów używaj szyfrowania plików i przechowywania klucza w Keychain, a same dane — w Documents.
Po jailbreaku ochrona Keychain jest zmniejszona, ponieważ atakujący uzyskuje dostęp root i może czytać Keychain SQLite bezpośrednio. Dla krytycznych danych używaj dodatkowego szyfrowania z kluczem otrzymanym z serwera.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również