Guten Tag, Habr! Ich präsentiere Ihnen die Übersetzung eines Artikels über die Grundprinzipien der Sicherheit vertraulicher Daten in iOS-Anwendungen
„Anwendungssicherheitsanforderungen für jede iOS-App“ von Arlind Aliu.
Anwendungssicherheit ist einer der wichtigsten Aspekte der Softwareentwicklung. Anwendungsbenutzer hoffen, dass die von ihnen bereitgestellten Informationen sicher geschützt sind. Daher können Sie nicht nur vertrauliche Informationen an jemanden weitergeben.
Glücklicherweise werden wir in diesem Artikel die Fehler diskutieren, die Entwickler in ihren Anwendungen machen, sowie Möglichkeiten, sie zu beheben.
Fortsetzung unter dem Schnitt.
Datenspeicherung am falschen Ort
Ich habe mehrere Anwendungen aus dem AppStore untersucht und viele machen den gleichen Fehler: Vertrauliche Informationen werden dort gespeichert, wo sie nicht sein sollten.
Wenn Sie personenbezogene Daten in
UserDefaults speichern , gefährden Sie diese.
UserDefaults werden in einer Datei mit einer Liste von Eigenschaften gespeichert, die sich im Ordner "Einstellungen" in Ihrer Anwendung befindet. Die Daten werden in der Anwendung ohne den geringsten Hinweis auf Verschlüsselung gespeichert.
Wenn Sie ein Programm eines Drittanbieters auf einem Mac wie iMazing installieren, müssen Sie nicht einmal das Telefon hacken, sondern sehen sofort alle
UserDefaults- Daten der im AppStore installierten Anwendung. Mit solchen Programmen können Sie Daten von auf dem iPhone installierten Anwendungen anzeigen und verwalten. Sie können problemlos
UserDefaults für jede Anwendung
abrufen .
Dies ist der Hauptgrund, warum ich mich dazu entschlossen habe, einen Artikel zu schreiben. Im AppStore habe ich eine Reihe von Anwendungen gefunden, in denen Daten in
UserDefaults gespeichert sind , z. B. Token, aktive und erneuerbare Abonnements, verfügbarer Geldbetrag usw. All diese Daten können leicht mit böswilliger Absicht abgerufen und verwendet werden, von der Verwaltung kostenpflichtiger Abonnements in der Anwendung bis hin zum Hacken auf Netzwerkebene und noch schlimmer.
Und nun zum Speichern von Daten.
Denken Sie daran, dass in
UserDefaults nur eine kleine Menge an Informationen gespeichert werden
sollte , z. B. Einstellungen in der Anwendung,
dh Daten, die für den Benutzer nicht vertraulich sind.
Verwenden Sie die speziellen Sicherheitsdienste von Apple, um persönliche Informationen zu speichern. Mit dem Schlüsselbund-API-Dienst können Sie eine bestimmte Menge von Benutzerdaten in einer verschlüsselten Datenbank speichern. Dort können Sie Passwörter und andere wichtige Daten für den Benutzer speichern, z. B. Kreditkarteninformationen oder sogar kleine wichtige Notizen.
Möglicherweise gibt es auch verschlüsselte Schlüssel und Zertifikate, mit denen Sie arbeiten.
Schlüsselbund-API-Dienst
Das folgende Beispiel zeigt, wie Sie das Kennwort eines Benutzers im Schlüsselbund speichern.
class KeychainService { func save(_ password: String, for account: String) { let password = password.data(using: String.Encoding.utf8)! let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account, kSecValueData as String: password] let status = SecItemAdd(query as CFDictionary, nil) guard status == errSecSuccess else { return print("save error") } }
Teil des
kSecClass- Wörterbuchs
: kSecClassGenericPassword bedeutet, dass die Informationen, die verschlüsselt werden müssen, das Kennwort sind. Anschließend fügen wir dem Schlüsselbund das neue Kennwort hinzu, indem wir die
SecItemAdd- Methode aufrufen. Das Abrufen von Daten aus einem Bundle ähnelt dem Speichern.
func retrivePassword(for account: String) -> String? { let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account, kSecMatchLimit as String: kSecMatchLimitOne, kSecReturnData as String: kCFBooleanTrue] var retrivedData: AnyObject? = nil let _ = SecItemCopyMatching(query as CFDictionary, &retrivedData) guard let data = retrivedData as? Data else {return nil} return String(data: data, encoding: String.Encoding.utf8) }
Lassen Sie uns einen kleinen Check auf die Richtigkeit des Speicherns und Empfangens von Daten schreiben.
func testPaswordRetrive() { let password = "123456" let account = "User" keyChainService.save(password, for: account) XCTAssertEqual(keyChainService.retrivePassword(for: account), password) }
Auf den ersten Blick scheint die Verwendung der Schlüsselbund-API recht schwierig zu sein, insbesondere wenn Sie mehr als ein Kennwort speichern müssen. Ich empfehle Ihnen daher dringend, das Fassadenmuster für diese Zwecke zu verwenden. Sie können Daten je nach den Anforderungen der Anwendung speichern und ändern.
Wenn Sie mehr über dieses Muster erfahren möchten und wissen möchten, wie Sie einen einfachen Wrapper für komplexe Subsysteme erstellen, hilft Ihnen dieser
Artikel . Auch im Internet gibt es viele offene Bibliotheken, die bei der Verwendung der Schlüsselbund-API helfen, z. B.
SAMKeychain und
SwiftKeychainWrapper .
Speichern und Autorisieren von Passwörtern
In meiner Entwicklungskarriere stehe ich ständig vor dem gleichen Problem. Entwickler speichern entweder Kennwörter in der Anwendung oder erstellen eine Anforderung an den Server, der den Benutzernamen und das Kennwort direkt sendet.
Wenn Sie Daten in
UserDefault speichern,
wissen Sie nach dem Lesen der Informationen aus dem ersten Teil des Artikels bereits, wie viel Sie riskieren. Durch das Speichern von Kennwörtern im Schlüsselbund erhöhen Sie die Sicherheitsstufe Ihrer Anwendung erheblich. Bevor Sie jedoch vertrauliche Informationen irgendwo speichern, müssen Sie sie zunächst verschlüsseln.
Angenommen, ein Hacker kann uns über unser Netzwerk angreifen. So erhält er Passwörter in Form von Rohtext. Es ist natürlich besser, alle Passwörter zu hashen.
Verschlüsselung personenbezogener Daten
Hashing kann
wie ein Overkill erscheinen, wenn Sie es selbst tun. In diesem Artikel verwenden wir daher die
CryptoSwift- Bibliothek. Es wurden viele zuverlässige Standardverschlüsselungsalgorithmen gesammelt, die in Swift verwendet werden.
Versuchen wir, das Kennwort mithilfe der
CryptoSwift- Algorithmen zu speichern und aus dem Schlüsselbund
abzurufen .
func saveEncryptedPassword(_ password: String, for account: String) { let salt = Array("salty".utf8) let key = try! HKDF(password: Array(password.utf8), salt: salt, variant: .sha256).calculate().toHexString() keychainService.save(key, for: account) }
Die obige Funktion zeichnet den Benutzernamen und das Passwort auf und speichert sie im Schlüsselbund als verschlüsselte Zeichenfolge.
Mal sehen, was drinnen passiert:
- Der Login und das Passwort werden als String in die Salt-Variable geschrieben
- sha256 füllt den SHA-2-Hash
- HKDF ist eine Schlüsselgenerierungsfunktion (
KDF ), die auf dem Nachrichtenauthentifizierungscode (
HMAC ) basiert.
Wir haben die Salt-Variable erstellt, um die Aufgabe für Hacker zu erschweren. Wir konnten das Passwort nur verschlüsseln, aber in diesem Fall kann der Angreifer eine Liste der am häufigsten verwendeten Passwörter haben. Er verschlüsselt sie problemlos und vergleicht sie mit unserem verschlüsselten Passwort. Dann ist es nicht schwierig, das Passwort für ein bestimmtes Konto zu finden.
Jetzt können wir uns mit unserem Konto und dem generierten Schlüssel anmelden.
authManager.login(key, user)
Natürlich sollte der Server wissen, was in unserer Salt-Variablen verschlüsselt ist. Das Backend kann Schlüssel mit demselben Algorithmus vergleichen, um den Benutzer zu identifizieren.
Die Verwendung dieses Ansatzes erhöht die Sicherheit Ihrer Anwendung erheblich.
Als Abschluss
Vernachlässigen Sie niemals die Sicherheit Ihrer Anwendung. In diesem Artikel haben wir zunächst herausgefunden, welche Konsequenzen das Speichern vertraulicher Daten in
UserDefaults haben kann und warum ein Schlüsselbund benötigt wird.
Im zweiten Teil werden wir über eine ernstere Sicherheitsstufe sprechen, Daten verschlüsseln, bevor sie gespeichert werden, und auch diskutieren, wie Informationen mit persönlichen Daten korrekt auf den Server übertragen werden.