() translation by (you can also view the original English article)
Jede app speichert die Daten des Benutzers zu kümmern hat, die Sicherheit und Vertraulichkeit der Daten. Wie wir gesehen haben-mit den jüngsten Daten zu Verletzungen, kann es sehr ernsthafte Konsequenzen für das scheitern, schützen Sie Ihre Benutzer gespeicherten Daten. In diesem tutorial lernen Sie einige bewährte Methoden für den Schutz Ihrer Benutzer-Daten.
In der früheren post, haben Sie gelernt, wie zu schützen von Dateien mithilfe von Data Protection API. Datei-basierten Schutz ist eine leistungsfähige Funktion, für die sichere Speicherung großer Datenmengen. Aber vielleicht overkill für eine kleine Menge von Informationen zu schützen, wie ein Schlüssel oder Passwort. Für diese Arten von Elementen, die keychain ist die empfohlene Lösung.
Keychain Services
Der Schlüsselanhänger ist ein großartiger Ort zum speichern kleiner Mengen von Informationen, wie empfindlich Saiten und IDs beibehalten, selbst wenn der Benutzer löscht die app. Ein Beispiel wäre ein Gerät oder Sitzungs-token, die Ihre server gibt der app bei der Registrierung. Ob Sie nennen es eine geheime Zeichenfolge oder das eindeutige "token", der Schlüsselanhänger bezieht sich auf alle diese Elemente Passwörter.
Es gibt ein paar beliebte Drittanbieter-Bibliotheken für Schlüsselanhänger Dienstleistungen, wie die Geldkassette (Swift) und SSKeychain (Objective-C). Oder, wenn Sie wollen die komplette Kontrolle über Ihre eigenen code Sie möchten, können Sie direkt verwenden, um die Keychain Services API, welches ein C-API.
Ich werde kurz erklären, wie der Schlüsselbund funktioniert. Sie können sich den Schlüsselbund wie eine typische Datenbank, in dem Sie Abfragen auf eine Tabelle. Die Funktionen der keychain-API, die alle benötigen eine CFDictionary
Objekt, das Attribute enthält, von der Abfrage.
Jeder Eintrag in der keychain hat einen service-Namen. Der service-name ist ein Bezeichner,: ein Schlüssel für was auch immer Wert, den Sie möchten, zu speichern oder abrufen von im Schlüsselbund. Zu ermöglichen, ein Schlüsselbund-Element gespeichert werden, die für einen bestimmten Benutzer nur, Sie werden auch oft wünschen, geben Sie einen account-Namen.
Weil jedes Schlüsselbund-Funktion nimmt einen ähnlichen Wörterbuch mit vielen der gleichen Parameter an eine Abfrage machen, können Sie vermeiden doppelten code, indem Sie eine Hilfsfunktion, gibt diese Abfrage Wörterbuch.
1 |
import Security |
2 |
|
3 |
//...
|
4 |
|
5 |
class func passwordQuery(service: String, account: String) -> Dictionary<String, Any> |
6 |
{
|
7 |
let dictionary = [ |
8 |
kSecClass as String : kSecClassGenericPassword, |
9 |
kSecAttrAccount as String : account, |
10 |
kSecAttrService as String : service, |
11 |
kSecAttrAccessible as String : kSecAttrAccessibleWhenUnlocked //If need access in background, might want to consider kSecAttrAccessibleAfterFirstUnlock |
12 |
] as [String : Any] |
13 |
|
14 |
return dictionary |
15 |
}
|
Dieser code setzt den Abfrage-Wörterbuch mit deinen account-und service-Namen und erzählt die Schlüsselanhänger, die wir speichern werden ein Passwort.
Ähnlich wie können Sie den Schutz für einzelne Dateien (wie wir diskutiert in der früheren post), Sie können auch die Sicherheitsstufen für Ihr Schlüsselbund-Element mit dem kSecAttrAccessible
-Taste.
Hinzufügen ein Passwort
Die SecItemAdd ()
- Funktion fügt die Daten der keychain. Diese Funktion nimmt ein Daten-Objekt, macht es vielseitig für die Speicherung von vielen Arten von Objekten. Verwenden der Passwort-Abfrage-Funktion, die wir oben erstellt, wir speichern eine Zeichenfolge in der keychain. Um dies zu tun, wir müssen nur konvertieren Sie die String-Daten.
1 |
@discardableResult class func setPassword(_ password: String, service: String, account: String) -> Bool |
2 |
{
|
3 |
var status : OSStatus = -1 |
4 |
if !(service.isEmpty) && !(account.isEmpty) |
5 |
{
|
6 |
deletePassword(service: service, account: account) //delete password if pass empty string. Could change to pass nil to delete password, etc |
7 |
|
8 |
if !password.isEmpty |
9 |
{
|
10 |
var dictionary = passwordQuery(service: service, account: account) |
11 |
let dataFromString = password.data(using: String.Encoding.utf8, allowLossyConversion: false) |
12 |
dictionary[kSecValueData as String] = dataFromString |
13 |
status = SecItemAdd(dictionary as CFDictionary, nil) |
14 |
}
|
15 |
}
|
16 |
return status == errSecSuccess |
17 |
}
|
Löschen Sie ein Passwort
Um zu verhindern, dass doppelte Einfügungen, der obige code löscht zuerst den vorherigen Eintrag, wenn es einen gibt. Schreiben Sie die Funktion jetzt. Dies geschieht mithilfe der SecItemDelete ()
- Funktion.
1 |
@discardableResult class func deletePassword(service: String, account: String) -> Bool |
2 |
{
|
3 |
var status : OSStatus = -1 |
4 |
if !(service.isEmpty) && !(account.isEmpty) |
5 |
{
|
6 |
let dictionary = passwordQuery(service: service, account: account) |
7 |
status = SecItemDelete(dictionary as CFDictionary); |
8 |
}
|
9 |
return status == errSecSuccess |
10 |
}
|
Abrufen ein Passwort
Neben dem abrufen eines Eintrags aus der Schlüsselbundverwaltung benutzen, verwenden Sie die SecItemCopyMatching () - Funktion. Wird es wieder ein AnyObject entspricht Ihrer Anfrage.
1 |
class func password(service: String, account: String) -> String //return empty string if not found, could return an optional |
2 |
{
|
3 |
var status : OSStatus = -1 |
4 |
var resultString = "" |
5 |
if !(service.isEmpty) && !(account.isEmpty) |
6 |
{
|
7 |
var passwordData : AnyObject? |
8 |
var dictionary = passwordQuery(service: service, account: account) |
9 |
dictionary[kSecReturnData as String] = kCFBooleanTrue |
10 |
dictionary[kSecMatchLimit as String] = kSecMatchLimitOne |
11 |
status = SecItemCopyMatching(dictionary as CFDictionary, &passwordData) |
12 |
|
13 |
if status == errSecSuccess |
14 |
{
|
15 |
if let retrievedData = passwordData as? Data |
16 |
{
|
17 |
resultString = String(data: retrievedData, encoding: String.Encoding.utf8)! |
18 |
}
|
19 |
}
|
20 |
}
|
21 |
return resultString |
22 |
}
|
In diesem code, wir setzen die kSecReturnData parameter kCFBooleanTrue. kSecReturnData bedeutet, dass die eigentlichen Daten des Elements werden zurückgegeben. Eine andere option könnte sein, die Attribute zurückgeben (kSecReturnAttributes) des Artikels. Der Schlüssel nimmt eine CFBoolean geben, die hält den Konstanten kCFBooleanTrue oder kCFBooleanFalse. Wir setzen kSecMatchLimit zu kSecMatchLimitOne, so dass nur die erste Gefundene Element in der Schlüsselbund-Datenbank zurückgegeben werden, im Gegensatz zu einer unbegrenzten Anzahl von Ergebnissen.
Öffentliche und Private Schlüssel
Der Schlüsselanhänger ist auch der empfohlene Ort zum speichern von public-und private-key-Objekte, zum Beispiel, wenn Sie Ihre app funktioniert mit und speichern muss, EG-oder RSA-SecKey Objekte.
Der wesentliche Unterschied ist, dass anstatt zu sagen, ist der Schlüsselbund ein Passwort zu speichern, können wir sagen, es zu bewahren den Schlüssel. In der Tat können wir die spezifische, durch die Einstellung der Typen von Schlüsseln gespeichert werden, beispielsweise, ob es privat oder öffentlich ist. Alle, die getan werden muss, ist die Anpassung des query helper-Funktion zum arbeiten mit der Art von Schlüssel Sie möchten.
Schlüssel in der Regel identifiziert werden, die Verwendung eines reverse-domain-tag wie com.mydomain.mykey-anstelle des service-und account-Namen (da die öffentlichen Schlüssel werden offen geteilt zwischen verschiedenen Unternehmen oder Organisationen). Wir nehmen die service-und account-strings und konvertieren Sie Sie in einer tag-Daten-Objekt. Z. B. der obige code, der angepasst ist zum speichern eines Privaten RSA-SecKey würde wie folgt Aussehen:
1 |
class func keyQuery(service: String, account: String) -> Dictionary<String, Any> |
2 |
{
|
3 |
let tagString = "com.mydomain." + service + "." + account |
4 |
let tag = tagString.data(using: .utf8)! //Store it as Data, not as a String |
5 |
let dictionary = [ |
6 |
kSecClass as String : kSecClassKey, |
7 |
kSecAttrKeyType as String : kSecAttrKeyTypeRSA, |
8 |
kSecAttrKeyClass as String : kSecAttrKeyClassPrivate, |
9 |
kSecAttrAccessible as String : kSecAttrAccessibleWhenUnlocked, |
10 |
kSecAttrApplicationTag as String : tag |
11 |
] as [String : Any] |
12 |
|
13 |
return dictionary |
14 |
}
|
15 |
|
16 |
@discardableResult class func setKey(_ key: SecKey, service: String, account: String) -> Bool |
17 |
{
|
18 |
var status : OSStatus = -1 |
19 |
if !(service.isEmpty) && !(account.isEmpty) |
20 |
{
|
21 |
deleteKey(service: service, account:account) |
22 |
var dictionary = keyQuery(service: service, account: account) |
23 |
dictionary[kSecValueRef as String] = key |
24 |
status = SecItemAdd(dictionary as CFDictionary, nil); |
25 |
}
|
26 |
return status == errSecSuccess |
27 |
}
|
28 |
|
29 |
@discardableResult class func deleteKey(service: String, account: String) -> Bool |
30 |
{
|
31 |
var status : OSStatus = -1 |
32 |
if !(service.isEmpty) && !(account.isEmpty) |
33 |
{
|
34 |
let dictionary = keyQuery(service: service, account: account) |
35 |
status = SecItemDelete(dictionary as CFDictionary); |
36 |
}
|
37 |
return status == errSecSuccess |
38 |
}
|
39 |
|
40 |
class func key(service: String, account: String) -> SecKey? |
41 |
{
|
42 |
var item: CFTypeRef? |
43 |
if !(service.isEmpty) && !(account.isEmpty) |
44 |
{
|
45 |
var dictionary = keyQuery(service: service, account: account) |
46 |
dictionary[kSecReturnRef as String] = kCFBooleanTrue |
47 |
dictionary[kSecMatchLimit as String] = kSecMatchLimitOne |
48 |
SecItemCopyMatching(dictionary as CFDictionary, &item); |
49 |
}
|
50 |
return item as! SecKey? |
51 |
}
|
Passwörter
Elemente, die gesichert mit der kSecAttrAccessibleWhenUnlocked flag werden nur freigeschaltet, wenn das Gerät entsperrt ist, aber es stützt sich auf die, dass der Benutzer einen passcode oder Touch ID einrichten, in den ersten Platz.
Die applicationPassword Berechtigung können Elemente, die im Schlüsselbund gesichert werden mit einem zusätzlichen Passwort. Auf diese Weise, wenn der Benutzer nicht über einen passcode oder Touch ID eingerichtet ist, werden die Elemente immer noch werden sicher, und es fügt eine zusätzliche Schicht der Sicherheit, wenn Sie nicht über einen passcode gesetzt.
Als Beispiel-Szenario, nachdem die app authentifiziert sich mit Ihren server, Ihr server zurückgeben könnte, das Passwort über HTTPS, die erforderlich ist, um zu entsperren das keychain-Element. Dies ist die bevorzugte Art und Weise zu liefern, dass zusätzliche Passwort. Hardcoding Sie ein Kennwort in das binary wird nicht empfohlen.
Ein anderes Szenario könnte zum abrufen der zusätzlichen Passwort von einem Benutzer bereitgestellten Kennwort in der app; dies erfordert jedoch mehr Arbeit zu sichern, ordnungsgemäß (mit PBKDF2). Schauen wir uns die Sicherung von Nutzer-vorausgesetzt, die Passwörter im nächsten tutorial.
Eine weitere Anwendung Passwort ist für die Speicherung empfindlicher Schlüssel—zum Beispiel eine, die Sie würde nicht wollen, ausgesetzt werden, nur weil der user noch nicht eingerichtet, einen passcode.
applicationPassword ist nur verfügbar auf iOS 9 und höher, so müssen Sie einen fallback, der nicht applicationPassword, wenn Sie gezielt niedrigere iOS-Versionen. Um den code zu verwenden, müssen Sie das folgende in den bridging-header:
1 |
#import <LocalAuthentication/LocalAuthentication.h>
|
2 |
#import <Security/SecAccessControl.h>
|
Der folgende code legt ein Passwort für den query-Wörterbuch.
1 |
if #available(iOS 9.0, *) |
2 |
{
|
3 |
//Use this in place of kSecAttrAccessible for the query
|
4 |
var error: Unmanaged<CFError>? |
5 |
let accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlocked, SecAccessControlCreateFlags.applicationPassword, &error) |
6 |
if accessControl != nil |
7 |
{
|
8 |
dictionary[kSecAttrAccessControl as String] = accessControl |
9 |
}
|
10 |
|
11 |
let localAuthenticationContext = LAContext.init() |
12 |
let theApplicationPassword = "passwordFromServer".data(using:String.Encoding.utf8)! |
13 |
localAuthenticationContext.setCredential(theApplicationPassword, type: LACredentialType.applicationPassword) |
14 |
dictionary[kSecUseAuthenticationContext as String] = localAuthenticationContext |
15 |
}
|
Beachten Sie, dass wir kSecAttrAccessControl auf das Wörterbuch. Dies verwendet wird, in kSecAttrAccessible, die zuvor in unserem passwordQuery Methode. Wenn Sie versuchen, beide zu verwenden, erhalten Sie eine OSStatus -50 Fehler.
Benutzerauthentifizierung
Ab iOS 8 können Sie speichern Daten in der keychain, kann nur zugegriffen werden, nachdem der Benutzer erfolgreich authentifiziert sich am Gerät mit Touch-ID oder passcode. Wenn es Zeit ist für den Benutzer zu authentifizieren, Touch-ID Priorität, wenn es eingerichtet ist, ansonsten den passcode-Bildschirm. Speichern im keychain nicht erforderlich, den Benutzer zu authentifizieren, aber das abrufen der Daten.
Sie können einen Schlüsselanhänger Element, um eine Benutzerauthentifizierung erfordern, indem Sie eine access control-Objekt festgelegt .userPresence. Falls kein passcode eingestellt ist dann jedem Schlüsselbund Anfragen mit .userPresence fehl.
1 |
if #available(iOS 8.0, *) |
2 |
{
|
3 |
let accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, .userPresence, nil) |
4 |
if accessControl != nil |
5 |
{
|
6 |
dictionary[kSecAttrAccessControl as String] = accessControl |
7 |
}
|
8 |
} |
Diese Funktion ist gut, wenn Sie wollen, stellen Sie sicher, dass Ihre app verwendet wird, indem Sie die richtige person. Zum Beispiel wäre es wichtig für den Benutzer zu authentifizieren, bevor Sie in der Lage, log-in, um eine banking-app. Dadurch wird verhindert, dass Benutzer, die Links Ihr Gerät entsperrt, so dass die Banken nicht zugegriffen werden kann.
Auch, wenn Sie nicht über eine server-seitige Komponente zu Ihrer app, können Sie diese Funktion für Geräte-Authentifizierung statt.
Für die Last-Abfrage, können Sie eine Beschreibung der warum muss sich der Benutzer authentifizieren.
1 |
dictionary[kSecUseOperationPrompt as String] = "Authenticate to retrieve x" |
Beim abrufen der Daten mit SecItemCopyMatching(), die Funktion zeigt die Authentifizierung UI, und warten Sie für den Benutzer verwenden Sie Touch-ID oder geben Sie den passcode ein. Da SecItemCopyMatching()
blockiert, bis der Benutzer fertig zu authentifizieren, müssen Sie zum Aufruf der Funktion aus einem hintergrund-thread, damit der Haupt-UI-thread zu bleiben, reagieren.
1 |
DispatchQueue.global().async |
2 |
{
|
3 |
status = SecItemCopyMatching(dictionary as CFDictionary, &passwordData) |
4 |
if status == errSecSuccess |
5 |
{
|
6 |
if let retrievedData = passwordData as? Data |
7 |
{
|
8 |
DispatchQueue.main.async |
9 |
{
|
10 |
//... do the rest of the work back on the main thread
|
11 |
}
|
12 |
}
|
13 |
}
|
14 |
}
|
Wieder setzen wir kSecAttrAccessControl auf die Abfrage Wörterbuch. Sie müssen entfernen kSecAttrAccessible, die zuvor in unserem passwordQuery Methode. Die Verwendung beider gleichzeitig führt zu einer OSStatus -50 Fehler.
Fazit
In diesem Artikel, Sie hatten eine tour, die die Keychain Services API. Zusammen mit der Data Protection API, die wir sahen, in der früheren post, die Verwendung dieser Bibliothek ist Teil der best practices für das sichern von Daten.
Allerdings, wenn der Benutzer nicht über ein Passwort oder Touch-ID auf dem Gerät, es gibt keine Verschlüsselung, die entweder für Rahmen. Da die Keychain Services und Data Protection-APIs sind Häufig von iOS-apps, sind Sie mitunter gezielt von Angreifern, vor allem auf jailbroken Geräte. Wenn Ihre app nicht mit hoch sensiblen Informationen, kann dies ein akzeptables Risiko. Während iOS wird ständig aktualisiert, die Sicherheit des frameworks, sind wir immer noch auf die Gnade der Anwender das Betriebssystem aktualisieren, mit einem starken passcode, und nicht Jailbreak Ihr Gerät.
Der Schlüsselanhänger ist gedacht für kleinere Stücke von Daten, und Sie können eine größere Menge an Daten zu sichern, die unabhängig von der Geräte-Authentifizierung. Während iOS-updates fügen Sie einige großartige neue Funktionen wie die Anwendung das Kennwort, können Sie noch brauchen, zu unterstützen, niedrigere iOS-Versionen und haben immer noch eine starke Sicherheit. Für einige von diesen Gründen, können Sie stattdessen wollen, verschlüsseln Sie die Daten selbst.
Der Letzte Artikel in dieser Serie deckt die Verschlüsselung der Daten mit AES-Verschlüsselung, und während es ist ein Weiterführender Ansatz, der dies ermöglicht Ihnen vollständige Kontrolle über, wie und Wann Ihre Daten verschlüsselt sind.
So stay tuned. Und in der Zwischenzeit, überprüfen Sie heraus einige unserer anderen Beiträge auf iOS-app-Entwicklung!