Jede App, die die Daten des Benutzers speichert, muss für die Sicherheit und den Datenschutz dieser Daten sorgen. Wie wir kürzlich bei Datenschutzverletzungen gesehen haben, kann es sehr schwerwiegende Folgen haben, wenn die gespeicherten Daten Ihrer Benutzer nicht geschützt werden. In diesem Lernprogramm lernen Sie einige bewährte Methoden zum Schutz Ihrer Benutzerdaten kennen.
Im vorigen Beitrag haben Sie erfahren, wie Sie Dateien mit der Data Protection API schützen. Dateibasierter Schutz ist eine leistungsstarke Funktion für die sichere Massenspeicherung von Daten. Es kann jedoch ein Overkill für eine kleine Menge von Informationen sein, die geschützt werden müssen, wie z. B. ein Schlüssel oder ein Kennwort. Für diese Art von Artikeln ist der Schlüsselbund die empfohlene Lösung.
Der Schlüsselbund ist ein großartiger Ort, um kleinere Informationsmengen wie sensible Zeichenfolgen und IDs zu speichern, die auch dann bestehen bleiben, wenn der Benutzer die App löscht. Ein Beispiel könnte ein Geräte- oder Sitzungstoken sein, das Ihr Server bei der Registrierung an die App zurückgibt. Unabhängig davon, ob Sie es eine geheime Zeichenfolge oder ein eindeutiges Token nennen, bezieht sich der Schlüsselbund auf alle diese Elemente als Passwörter.
Es gibt einige beliebte Drittanbieter-Bibliotheken für Schlüsselbunddienste, wie z. B. Strongbox (Swift) und SSKeychain (Objective-C). Wenn Sie die vollständige Kontrolle über Ihren eigenen Code wünschen, können Sie die Keychain Services API, eine C-API, direkt verwenden.
Ich werde kurz erklären, wie der Schlüsselbund funktioniert. Sie können sich den Schlüsselbund als eine typische Datenbank vorstellen, in der Sie Abfragen für eine Tabelle ausführen. Die Funktionen der Schlüsselbund-API erfordern alle eine CFDictionary
Objekt, das Attribute der Abfrage enthält.
Jeder Eintrag im Schlüsselbund hat einen Servicenamen. Der Dienstname ist eine Kennung: a Schlüssel für was auch immer Wert Sie möchten im Schlüsselbund speichern oder abrufen. Damit ein Schlüsselbundelement nur für einen bestimmten Benutzer gespeichert werden kann, möchten Sie häufig auch einen Kontonamen angeben.
Da für jede Schlüsselbundfunktion ein ähnliches Wörterbuch mit vielen gleichen Parametern benötigt wird, um eine Abfrage auszuführen, können Sie doppelten Code vermeiden, indem Sie eine Hilfsfunktion erstellen, die dieses Abfragewörterbuch zurückgibt.
Sicherheit importieren //… class func passwordQuery (Dienst: Zeichenfolge, Konto: Zeichenfolge) -> WörterbuchLassen Wörterbuch = wie [kSecClass als String: kSecClassGenericPassword, kSecAttrAccount als String: Konto, kSecAttrService als String:: Service, kSecAttrAccessible als String kSecAttrAccessibleWhenUnlocked // Wenn Notwendigkeit, den Zugang im Hintergrund, könnte kSecAttrAccessibleAfterFirstUnlock betrachten wollen] [String: Alles] Rückkehr Wörterbuch
Dieser Code richtet die Abfrage ein Wörterbuch
mit Ihrem Konto und Ihren Dienstnamen und teilt dem Schlüsselbund mit, dass wir ein Kennwort speichern werden.
Ähnlich wie Sie die Schutzstufe für einzelne Dateien einstellen können (wie im vorigen Beitrag beschrieben), können Sie auch die Schutzstufe für Ihr Schlüsselbundelement mithilfe von festlegen kSecAttrAccessible
Schlüssel.
Das SecItemAdd ()
Funktion fügt dem Schlüsselbund Daten hinzu. Diese Funktion benötigt a Daten
Objekt, wodurch es für das Speichern vieler Arten von Objekten vielseitig ist. Lassen Sie uns mit der oben erstellten Passwortabfrage eine Zeichenfolge im Schlüsselbund speichern. Dazu müssen wir nur das konvertieren String
zu Daten
.
@discardableResult-Klasse func setPassword (_Passwort: String, Dienst: String, Konto: String) -> Bool var status: OSStatus = -1 if! (service.isEmpty) &&! (account.isEmpty) deletePassword (service: service) , Konto: Konto) // Passwort löschen, wenn leere Zeichenfolge übergeben wird. Könnte geändert werden, um nil zu übergeben, um das Kennwort usw. zu löschen. If! Password.isEmpty var dictionary = passwordQuery (Dienst: Dienst, Konto: Konto). Lassen Sie dataFromString = password.data (unter Verwendung von: String.Encoding.utf8, allowLossyConversion: false) dictionary [ kSecValueData as String] = dataFromString status = SecItemAdd (Dictionary as CFDictionary, nil) Rückgabestatus == errSecSuccess
Um doppelte Einfügungen zu verhindern, löscht der obige Code zuerst den vorherigen Eintrag, falls vorhanden. Schreiben wir diese Funktion jetzt. Dies wird mit der SecItemDelete ()
Funktion.
@discardableResult-Klasse func deletePassword (Dienst: String, Konto: String) -> Bool var status: OSStatus = -1 if! (service.isEmpty) &&! (account.isEmpty) let dictionary = passwordQuery (Dienst: service, Konto) : account) status = SecItemDelete (Wörterbuch als CFDictionary); return status == errSecSuccess
Um einen Eintrag aus dem Schlüsselbund abzurufen, verwenden Sie als nächstes SecItemCopyMatching ()
Funktion. Es wird ein zurückgeben AnyObject
das entspricht Ihrer Anfrage.
class func password (service: String, Konto: String) -> String // Leere Zeichenfolge zurückgeben, falls nicht gefunden. Möglicherweise wird ein optionaler var-Status zurückgegeben: OSStatus = -1 var resultString = "" if! (service.isEmpty) &&! (account.isEmpty) var passwordData: AnyObject? var dictionary = .QueryQuery (Dienst: Dienst, Konto: Konto) Dictionary [kSecReturnData as String] = kCFBooleanTrue-Wörterbuch [kSecMatchLimit as String] = kSecMatchLimitOne status = SecItemCopyMatching (Wörterbuch als CFDictionary, & passwordData) wie? Data resultString = String (Daten: retrievedData, Kodierung: String.Encoding.utf8)! return resultString
In diesem Code setzen wir die kSecReturnData
Parameter zu kCFBooleanTrue
. kSecReturnData
bedeutet, dass die tatsächlichen Daten des Artikels zurückgegeben werden. Eine andere Option könnte die Rückgabe der Attribute sein (kSecReturnAttributes
) des Artikels. Der Schlüssel dauert a CFBoolean
Typ, der die Konstanten enthält kCFBooleanTrue
oder kCFBooleanFalse
. Wir setzen ein kSecMatchLimit
zu kSecMatchLimitOne
so dass nur das erste Element, das im Schlüsselbund gefunden wurde, zurückgegeben wird, im Gegensatz zu einer unbegrenzten Anzahl von Ergebnissen.
Der Schlüsselbund ist auch der empfohlene Ort zum Speichern von öffentlichen und privaten Schlüsselobjekten, z. B. wenn Ihre App mit EC oder RSA arbeitet und diese speichern muss SecKey
Objekte.
Der Hauptunterschied besteht darin, dass der Schlüsselbund nicht aufgefordert wird, ein Kennwort zu speichern, sondern dass er einen Schlüssel speichert. Tatsächlich können wir durch Festlegen der gespeicherten Schlüsselarten bestimmte Angaben machen, z. B. ob es sich um öffentliche oder private Schlüssel handelt. Sie müssen lediglich die Abfragehilfsfunktion so anpassen, dass sie mit dem gewünschten Schlüsseltyp arbeitet.
Schlüssel werden im Allgemeinen mithilfe eines umgekehrten Domain-Tags wie z com.mydomain.mykey anstelle von Dienst- und Kontonamen (da öffentliche Schlüssel offen von verschiedenen Unternehmen oder Entitäten gemeinsam genutzt werden). Wir nehmen die Dienst- und Konto-Zeichenfolgen und konvertieren sie in ein Tag Daten
Objekt. Beispielsweise kann der obige Code zum Speichern eines privaten RSA-Protokolls angepasst werden SecKey
würde so aussehen:
Klasse func keyQuery (Dienst: Zeichenfolge, Konto: Zeichenfolge) -> Wörterbuchlet tagString = "com.mydomain". + service + "." + account let tag = tagString.data (mit: .utf8)! // Speichern Sie das Dokument als kt............................... Als............ Als....... Als...... Als...... Als...... In.......... Als..... In..... Als.... Ab in...... Ab..... Als.......... Abhängt ab ] Rückgabewörterbuch @discardableResult-Klasse func setKey (_Schlüssel: SecKey, Dienst: String, Konto: String) -> Bool var status: OSStatus = -1 if! (service.isEmpty) &&! (account.isEmpty) deleteKey (Dienst: Dienst, Konto: Konto) var Dictionary = Schlüsselabfrage (Dienst: Dienst, Konto: Konto) Dictionary [kSecValueRef as String] = Schlüsselstatus = SecItemAdd (Dictionary als CFDictionary, nil); return status == errSecSuccess @discardableResult-Klasse func deleteKey (Dienst: Zeichenfolge, Konto: Zeichenfolge) -> Bool Var-Status: OSStatus = -1 Wenn! (Dienst.isEmpty) &&! (Konto.isEmpty) Lassen Sie das Wörterbuch = keyQuery (Dienst: Dienst, Konto: Konto) status = SecItemDelete (Wörterbuch als CFDictionary); return status == errSecSuccess Klassenfunktionsschlüssel (Dienst: Zeichenfolge, Konto: Zeichenfolge) -> SecKey? var item: CFTypeRef? if! (service.isEmpty) &&! (account.isEmpty) var dictionary = keyQuery (service: Dienst, Konto: Konto) Dictionary [kSecReturnRef als String] = kCFBooleanTrue-Dictionary [kSecMatchLimit als String] = kSecMatchLimitOne SecItemCopyMatching () &Artikel); Artikel zurücksenden als! SecKey?
Artikel gesichert mit kSecAttrAccessibleWhenUnlocked
Das Flag wird nur dann freigegeben, wenn das Gerät entsperrt ist. Der Benutzer muss jedoch einen Passcode oder eine Touch-ID eingerichtet haben.
Das applicationPassword
Mit dem Berechtigungsnachweis können Elemente im Schlüsselbund mit einem zusätzlichen Kennwort gesichert werden. Auf diese Weise sind die Elemente weiterhin sicher, wenn für den Benutzer kein Kennwort oder keine Touch-ID eingerichtet ist, und es wird eine zusätzliche Sicherheitsebene hinzugefügt, wenn ein Kennwort festgelegt ist.
Als Beispielszenario könnte Ihr Server nach der Authentifizierung Ihrer App bei Ihrem Server das Kennwort über HTTPS zurückgeben, das zum Entsperren des Schlüsselbundelements erforderlich ist. Dies ist die bevorzugte Art, dieses zusätzliche Passwort anzugeben. Es wird nicht empfohlen, ein Kennwort in der Binärdatei zu kodieren.
Ein anderes Szenario besteht darin, das zusätzliche Kennwort von einem vom Benutzer angegebenen Kennwort in Ihrer App abzurufen. Dies erfordert jedoch mehr Arbeit, um ordnungsgemäß gesichert zu werden (mit PBKDF2). Wir werden uns im nächsten Tutorial mit dem Schutz der vom Benutzer angegebenen Passwörter befassen.
Ein anderes Anwendungskennwort ist das Speichern eines vertraulichen Schlüssels, z. B. eines, auf den Sie nicht zugreifen möchten, nur weil der Benutzer noch keinen Zugangscode eingerichtet hat.
applicationPassword
ist nur für iOS 9 und höher verfügbar. Sie benötigen daher einen Fallback, der nicht verwendet wird applicationPassword
wenn Sie auf niedrigere iOS-Versionen abzielen. Um den Code zu verwenden, müssen Sie Folgendes in Ihren Bridging-Header einfügen:
#einführen#einführen
Mit dem folgenden Code wird ein Kennwort für die Abfrage festgelegt Wörterbuch
.
if #available (iOS 9.0, *) // Verwenden Sie diese Option anstelle von kSecAttrAccessible für den Abfragevariablenfehler: Nicht verwaltet? lassen Sie zu, dass der Zugriff auf das Dokument nicht erfüllt ist, wenn dies nicht der Fall ist, wenn Sie nicht gesperrt sind .Encoding.utf8)! localAuthenticationContext.setCredential (theApplicationPassword, Typ: LACredentialType.applicationPassword) Dictionary [kSecUseAuthenticationContext as String] = localAuthenticationContext
Beachten Sie, dass wir eingestellt haben kSecAttrAccessControl
auf der Wörterbuch
. Dies wird anstelle von verwendet kSecAttrAccessible
, das war vorher in unserem Passwortabfrage
Methode. Wenn Sie versuchen, beide zu verwenden, erhalten Sie eine OSStatus
-50
Error.
Ab iOS 8 können Sie Daten im Schlüsselbund speichern, auf die nur dann zugegriffen werden kann, wenn der Benutzer sich mit Touch-ID oder einem Passcode erfolgreich auf dem Gerät authentifiziert hat. Wenn der Benutzer sich authentifizieren muss, hat die Touch-ID Priorität, wenn sie eingerichtet ist. Andernfalls wird der Passcode-Bildschirm angezeigt. Beim Speichern im Schlüsselbund muss der Benutzer sich nicht authentifizieren. Der Abruf der Daten erfolgt jedoch.
Sie können ein Schlüsselbundelement so einstellen, dass eine Benutzerauthentifizierung erforderlich ist, indem Sie ein Zugriffssteuerungsobjekt angeben, das auf festgelegt ist .userPresence
. Wenn kein Passcode eingerichtet ist, werden alle Schlüsselbundanfragen mit angefordert .userPresence
wird versagen.
if #available (iOS 8.0, *) let accessControl = SecAccessControlCreateWithFlags (kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, .userPresence, nil), wenn accessControl!
Diese Funktion ist nützlich, wenn Sie sicherstellen möchten, dass Ihre App von der richtigen Person verwendet wird. Zum Beispiel wäre es wichtig, dass sich der Benutzer authentifiziert, bevor er sich bei einer Banking-App anmelden kann. Dies schützt Benutzer, die ihr Gerät nicht gesperrt haben, so dass kein Zugriff auf das Banking möglich ist.
Wenn Ihre App keine serverseitige Komponente enthält, können Sie diese Funktion auch verwenden, um stattdessen die geräteseitige Authentifizierung durchzuführen.
Für die Ladeabfrage können Sie beschreiben, warum der Benutzer sich authentifizieren muss.
dictionary [kSecUseOperationPrompt as String] = "Authentifizierung zum Abrufen von x"
Beim Abrufen der Daten mit SecItemCopyMatching ()
, Die Funktion zeigt die Authentifizierungs-Benutzeroberfläche an und wartet, bis der Benutzer die Touch-ID verwendet oder den Passcode eingibt. Schon seit SecItemCopyMatching ()
blockiert, bis der Benutzer die Authentifizierung abgeschlossen hat. Sie müssen die Funktion von einem Hintergrund-Thread aus aufrufen, damit der Haupt-UI-Thread reaktionsfähig bleibt.
DispatchQueue.global (). Async status = SecItemCopyMatching (Wörterbuch als CFDictionary & passwordData), wenn status == errSecSuccess , wenn retrievedData = passwordData as ist? Data DispatchQueue.main.async //… erledigt den Rest der Arbeit am Haupt-Thread
Wieder setzen wir kSecAttrAccessControl
auf der Abfrage Wörterbuch
. Sie müssen entfernen kSecAttrAccessible
, das war vorher in unserem Passwortabfrage
Methode. Die gleichzeitige Verwendung beider führt zu einem OSStatus
-50 fehler.
In diesem Artikel haben Sie einen Überblick über die Keychain Services-API erhalten. Neben der Datenschutz-API, die wir im vorherigen Beitrag gesehen haben, gehört die Verwendung dieser Bibliothek zu den bewährten Methoden zum Schutz von Daten.
Wenn der Benutzer jedoch keinen Passcode oder keine Touch-ID auf dem Gerät hat, gibt es für beide Frameworks keine Verschlüsselung. Da die Schlüsselbunddienste- und Datenschutz-APIs häufig von iOS-Apps verwendet werden, werden sie von Angreifern angegriffen, insbesondere auf Geräten mit Jailbreak. Wenn Ihre App nicht mit hochsensiblen Informationen arbeitet, kann dies ein akzeptables Risiko darstellen. Während iOS die Sicherheit der Frameworks ständig aktualisiert, sind wir immer noch dem Benutzer ausgeliefert, der das Betriebssystem mit einem starken Passcode aktualisiert und sein Gerät nicht jailbreakt.
Der Schlüsselbund ist für kleinere Datenmengen gedacht, und Sie haben möglicherweise eine größere zu sichernde Datenmenge, die unabhängig von der Geräteauthentifizierung ist. Während iOS-Updates einige großartige neue Funktionen hinzufügen, z. B. das Anwendungskennwort, müssen Sie möglicherweise immer noch niedrigere iOS-Versionen unterstützen und verfügen dennoch über eine starke Sicherheit. Aus einigen Gründen möchten Sie die Daten stattdessen selbst verschlüsseln.
Der letzte Artikel dieser Serie befasst sich mit der Verschlüsselung der Daten mithilfe der AES-Verschlüsselung. Obwohl dies ein fortgeschrittener Ansatz ist, haben Sie die vollständige Kontrolle darüber, wie und wann Ihre Daten verschlüsselt werden.
Also bleibt gespannt. In der Zwischenzeit können Sie einige unserer anderen Beiträge zur Entwicklung von iOS-Apps lesen!