In diesem Beitrag werden wir uns erweiterte Anwendungen der Verschlüsselung von Benutzerdaten in iOS-Apps anschauen. Wir beginnen mit einem umfassenden Überblick über die AES-Verschlüsselung und dann einige Beispiele für die Implementierung der AES-Verschlüsselung in Swift.
Im letzten Post haben Sie gelernt, wie Sie Daten mit dem Schlüsselbund speichern. Dies ist gut für kleine Informationen wie Schlüssel, Kennwörter und Zertifikate.
Wenn Sie eine große Menge an benutzerdefinierten Daten speichern, die nur verfügbar sein sollen, nachdem sich der Benutzer oder das Gerät authentifiziert hat, sollten Sie die Daten besser mit einem Verschlüsselungs-Framework verschlüsseln. Beispielsweise verfügen Sie möglicherweise über eine App, die private Chatnachrichten, die vom Benutzer gespeichert wurden, oder private Fotos, die vom Benutzer aufgenommen wurden, archivieren oder die finanziellen Details des Benutzers speichern kann. In diesen Fällen möchten Sie wahrscheinlich eine Verschlüsselung verwenden.
In Anwendungen gibt es zwei übliche Abläufe zum Verschlüsseln und Entschlüsseln von Daten aus iOS-Apps. Entweder wird dem Benutzer ein Kennwortbildschirm angezeigt oder die Anwendung wird bei einem Server authentifiziert, der einen Schlüssel zum Entschlüsseln der Daten zurückgibt.
Es ist nie eine gute Idee, das Rad bei der Verschlüsselung neu zu erfinden. Daher verwenden wir den AES-Standard der iOS Common Crypto-Bibliothek.
AES ist ein Standard, der die Daten eines Schlüssels verschlüsselt. Derselbe Schlüssel, der zum Verschlüsseln der Daten verwendet wird, wird zum Entschlüsseln der Daten verwendet. Es gibt verschiedene Schlüsselgrößen und AES256 (256 Bit) ist die bevorzugte Länge für sensible Daten.
RNCryptor ist ein beliebter Verschlüsselungswrapper für iOS, der AES unterstützt. RNCryptor ist eine großartige Wahl, da es Sie sehr schnell zum Laufen bringt, ohne sich um die zugrunde liegenden Details kümmern zu müssen. Es ist auch Open Source, so dass Sicherheitsforscher den Code analysieren und prüfen können.
Wenn Ihre App jedoch sehr sensible Informationen enthält und Sie der Meinung sind, dass Ihre Anwendung gezielt angegriffen und geknackt wird, möchten Sie möglicherweise eine eigene Lösung schreiben. Der Grund dafür ist, dass, wenn viele Apps denselben Code verwenden, dies die Arbeit des Hackers erleichtern kann, indem er eine Cracking-App schreibt, die gängige Muster im Code findet und Patches darauf anwendet.
Beachten Sie jedoch, dass das Schreiben Ihrer eigenen Lösung einen Angreifer nur verlangsamt und automatisierte Angriffe verhindert. Der Schutz, den Sie durch Ihre Implementierung erhalten, besteht darin, dass ein Hacker Zeit und Hingabe aufwenden muss, um Ihre App allein zu knacken.
Unabhängig davon, ob Sie sich für eine Lösung von Drittanbietern oder für Ihre eigene Lösung entscheiden, ist es wichtig, sich mit der Funktionsweise von Verschlüsselungssystemen vertraut zu machen. Auf diese Weise können Sie entscheiden, ob ein bestimmtes Framework, das Sie verwenden möchten, wirklich sicher ist. Daher konzentriert sich der Rest dieses Tutorials auf das Erstellen Ihrer eigenen Lösung. Mit dem Wissen, das Sie aus diesem Tutorial lernen, können Sie feststellen, ob Sie ein bestimmtes Framework sicher verwenden.
Wir beginnen mit der Erstellung eines geheimen Schlüssels, der zum Verschlüsseln Ihrer Daten verwendet wird.
Ein sehr häufiger Fehler bei der AES-Verschlüsselung besteht darin, das Kennwort eines Benutzers direkt als Verschlüsselungsschlüssel zu verwenden. Was ist, wenn der Benutzer ein allgemeines oder ein schwaches Kennwort verwendet? Wie zwingen wir Benutzer, einen Schlüssel zu verwenden, der zufällig und stark genug ist (genügend Entropie hat), und ihn dann daran erinnern können?
Die Lösung ist Schlüsseldehnung. Das Key-Stretching leitet einen Schlüssel aus einem Kennwort ab, indem er viele Male mit einem Salt-Wert gehasht wird. Das Salz ist nur eine Folge von Zufallsdaten, und es ist ein häufiger Fehler, dieses Salz wegzulassen - das Salz gibt dem Schlüssel seine lebenswichtige Entropie, und ohne das Salz würde derselbe Schlüssel abgeleitet, wenn dasselbe Passwort von jemandem verwendet wurde sonst.
Ohne das Salz könnte ein Wörterbuch von Wörtern verwendet werden, um allgemeine Schlüssel abzuleiten, die dann zum Angriff auf Benutzerdaten verwendet werden könnten. Dies wird als "Wörterbuchangriff" bezeichnet. Zu diesem Zweck werden Tabellen mit allgemeinen Schlüsseln verwendet, die nicht gesalzten Kennwörtern entsprechen. Sie werden "Regenbogentabellen" genannt..
Eine weitere Falle beim Erstellen eines Salt ist die Verwendung einer Zufallszahlengenerierungsfunktion, die nicht für die Sicherheit entwickelt wurde. Ein Beispiel ist das rand ()
Funktion in C, auf die von Swift aus zugegriffen werden kann. Diese Ausgabe kann sehr vorhersehbar sein!
Um ein sicheres Salz zu erstellen, verwenden wir die Funktion SecRandomCopyBytes
kryptographisch sichere Zufallsbytes zu erstellen, dh schwer vorhersagbare Zahlen.
Um den Code zu verwenden, müssen Sie Folgendes in Ihren Bridging-Header einfügen:#einführen
Hier beginnt der Code, der ein Salt erzeugt. Wir werden diesen Code im Laufe der Zeit ergänzen:
var salt = Daten (Anzahl: 8) salt.withUnsafeMutableBytes (saltBytes: UnsafeMutablePointer.)) -> Nicht zulässig in saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes) //…
Jetzt sind wir bereit für das Key Stretching. Glücklicherweise steht uns bereits eine Funktion zur Verfügung, um die eigentliche Dehnung durchzuführen: die kennwortbasierte Schlüsselableitungsfunktion (PBKDF2). PBKDF2 führt viele Male eine Funktion aus, um den Schlüssel abzuleiten. Wenn Sie die Anzahl der Iterationen erhöhen, verlängert sich die Zeit, die bei einem Brute-Force-Angriff für die Bearbeitung eines Schlüsselsatzes erforderlich wäre. Es wird empfohlen, PBKDF2 zu verwenden, um Ihren Schlüssel zu generieren.
var setupSuccess = true var key = Data (Wiederholung: 0, count: kCCKeySizeAES256) var salt = Data (count: 8) salt.withUnsafeMutableBytes (saltBytes: UnsafeMutablePointer.)) -> Nicht zulässig in saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes), wenn saltStatus == errSecSuccess let passwordData = password.data (mit: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer.) ) in let derivateStatus = CCakeDerivationPBKDF (CCPBKDFAlgorithm (kCCPBKDF2), Kennwort, KennwortDaten.Zahl, SaltBytes, Salt.Zahl, Salz.Zahl, CCPseudoRandomAlgorithm (kCCPRFHmacAlgSHA512), 14271, KeyBytes, Schlüssel. else setupSuccess = false
Sie fragen sich vielleicht jetzt, in welchen Fällen Sie nicht verlangen möchten, dass Benutzer in Ihrer App ein Kennwort angeben. Vielleicht authentifizieren sie sich bereits mit einem einzigen Anmeldeschema. Lassen Sie Ihren Server in diesem Fall mit einem sicheren Generator einen 256-Bit-AES-Schlüssel (32 Byte) generieren. Der Schlüssel sollte für verschiedene Benutzer oder Geräte unterschiedlich sein. Bei der Authentifizierung bei Ihrem Server können Sie dem Server eine Geräte- oder Benutzer-ID über eine sichere Verbindung übergeben und den entsprechenden Schlüssel zurücksenden.
Dieses Schema hat einen großen Unterschied. Wenn der Schlüssel vom Server stammt, kann die Entität, die diesen Server steuert, die verschlüsselten Daten lesen, wenn das Gerät oder die Daten jemals abgerufen wurden. Es besteht auch die Möglichkeit, dass der Schlüssel zu einem späteren Zeitpunkt durchgesickert oder freigelegt wird.
Wenn der Schlüssel jedoch von etwas abgeleitet wird, das nur der Benutzer kennt (das Kennwort des Benutzers), kann nur der Benutzer diese Daten entschlüsseln. Wenn Sie Informationen wie private Finanzdaten schützen, sollte nur der Benutzer die Daten entsperren können. Wenn diese Informationen der Entität ohnehin bekannt sind, kann es akzeptabel sein, dass der Server den Inhalt über einen serverseitigen Schlüssel entsperrt.
Nun, da wir einen Schlüssel haben, verschlüsseln wir einige Daten. Es gibt verschiedene Verschlüsselungsmodi, aber wir werden den empfohlenen Modus verwenden: Cipher Block Chaining (CBC). Dies wirkt sich blockweise auf unsere Daten aus.
Eine häufige Gefahr bei CBC ist die Tatsache, dass jeder nächste unverschlüsselte Datenblock mit dem vorherigen verschlüsselten Block XOR-verknüpft ist, um die Verschlüsselung zu verstärken. Das Problem hierbei ist, dass der erste Block niemals so einzigartig ist wie alle anderen. Wenn eine zu verschlüsselnde Nachricht genauso beginnen würde wie eine andere zu verschlüsselnde Nachricht, wäre die verschlüsselte Ausgabe am Anfang dieselbe und würde einem Angreifer einen Hinweis darauf geben, was die Nachricht sein könnte.
Um diese potenzielle Schwäche zu umgehen, beginnen wir mit dem Speichern der Daten mit einem sogenannten Initialisierungsvektor (IV): einem Block aus zufälligen Bytes. Die IV wird mit dem ersten Block der Benutzerdaten XOR-verknüpft, und da jeder Block von allen bis zu diesem Zeitpunkt verarbeiteten Blöcken abhängt, wird sichergestellt, dass die gesamte Nachricht eindeutig verschlüsselt wird, auch wenn sie die gleichen Daten wie die anderen enthält Botschaft. Mit anderen Worten, identische Nachrichten, die mit demselben Schlüssel verschlüsselt sind, führen nicht zu identischen Ergebnissen. Obwohl Salze und IVs als öffentlich gelten, sollten sie nicht sequenziell oder wiederverwendet werden.
Wir werden das gleiche sicher verwenden SecRandomCopyBytes
Funktion zum Erstellen der IV.
var iv = Data.init (count: kCCBlockSizeAES128) iv.withUnsafeMutableBytes (ivBytes: UnsafeMutablePointer)) in let ivStatus = SecRandomCopyBytes (kSecRandomDefault, kCCBlockSizeAES128, ivBytes), wenn ivStatus! = errSecSuccess setupSuccess = false
Um unser Beispiel zu vervollständigen, verwenden wir die CCCrypt
Funktion mit entweder kCCEncrypt
oder kCCDecrypt
. Da wir eine Blockverschlüsselung verwenden und die Nachricht nicht gut in ein Vielfaches der Blockgröße passt, müssen Sie der Funktion mitteilen, dass am Ende automatisch eine Auffüllung hinzugefügt wird.
Wie bei der Verschlüsselung üblich, sollten am besten etablierte Standards befolgt werden. In diesem Fall definiert der Standard-PKCS7, wie die Daten aufgefüllt werden. Wir teilen unserer Verschlüsselungsfunktion mit, dass Sie diesen Standard verwenden möchten KCCOptionPKCS7Padding
Möglichkeit. Alles in allem ist hier der vollständige Code zum Ver- und Entschlüsseln eines Strings.
Klasse func encryptData (_ clearTextData: Data, withPassword-Kennwort: String) -> Dictionaryvar setupSuccess = true var outDictionary = Wörterbuch .init () var key = Data (Wiederholung: 0, count: kCCKeySizeAES256) var salt = Data (count: 8) salt.withUnsafeMutableBytes (saltBytes: UnsafeMutablePointer.) ) -> Nicht zulässig in saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes), wenn saltStatus == errSecSuccess let passwordData = password.data (mit: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer.) ) in let derivateStatus = CCakeDerivationPBKDF (CCPBKDFAlgorithm (kCCPBKDF2), Kennwort, KennwortDaten.Zahl, SaltBytes, Salt.Zahl, Salz.Zahl, CCPseudoRandomAlgorithm (kCCPRFHmacAlgSHA512), 14271, KeyBytes, Schlüssel. else setupSuccess = false var iv = Data.init (Anzahl: kCCBlockSizeAES128) iv.withUnsafeMutableBytes (ivBytes: UnsafeMutablePointer.) ) in let ivaCaCaCaCaCaBytes (kSecRandomDefault, kCCBlockSizeAES128, ivBytes), wenn ivStatus! = errSecSuccess setupSuccess = false wenn (setupSuccess) var numberOtBytesEncrypted: size_take_cakey: abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abbrechen abfräsen key.count, ivBytes, clearTextBytes, clearTextData.count, encryptedBytes, Größe, & numberOfBytesEncrypted) if cryptStatus == Intcrachedown32 (kCCSuccess) encrypted.count = numberOfBytesEncrypted outDictionary ["Durchführungsdatensatz]. = iv outDictionary ["EncryptionSalt"] = Salt return outDictionary;
Und hier ist der Entschlüsselungscode:
class func decryp (aus dem Wörterbuch: Wörterbuch, withPassword Kennwort: Zeichenfolge) -> Daten var setupSuccess = true let encrypted = dictionary ["EncryptionData"] let iv = dictionary ["EncryptionIV"] let salt = dictionary ["EncryptionSalt"] var key = Daten (Wiederholung: 0, count) : kCCKeySizeAES256) salt? .withUnsafeBytes (saltBytes: UnsafePointer ) -> Annulliere passwordData = password.data (mit: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer.) ) in let kanutestatus = CCKeyDerivationPBKDF (CCPBKDFAlgorithm (kCCPBKDF2), Kennwort, KennwortDaten.Zahl, SaltBytes, Salt! .Zahl, Anzahl der Kanalspeicher) (CCCseudoRandomAlgorithm (kCCPRFHmacAlgSHA512), 14271, Schlüssel var decryptSuccess = false let size = (verschlüsselt? .count)! abbrechen löschenKontierenKontierenKontierenKontierenKontierenKontierenAbbrechenAbbrechenAbbrechenAbbrechenAbbrechenAbbrechenAbbrechenAbbrechenAbbrechenAbbrechenAbbrechenAbbrechenAbbrechenAbbrechenAbbrechenAbbrechenAbbrechenAbbrechenAbbrechenAblaufAbb.Abb keyByake in ccrypt (CCOperation (kCCDecrypt), CCAlgorithm (kCCAlgorithmAES128), CCOptions (kCCOptionPKCS7Padding), keyBytes, key.count, ivBytes, encryptedBytes (verschlüsselt? .count) ! == Int32 (kCCSuccess) clearTextData.count = numberOfBytesDecrypted decryptSuccess = true decryptSuccess zurückgeben? clearTextData: Data.init (Anzahl: 0)
Zum Schluss noch ein Test, um sicherzustellen, dass die Daten nach der Verschlüsselung korrekt entschlüsselt werden:
class func encryptTest () let clearTextData = "etwas Klartext zum Verschlüsseln" .data (mit: String.Encoding.utf8)! let dictionary = encryptData (clearTextData, withPassword: "123456") let decrypted = decryp (fromDictionary: dictionary, withPassword: "123456") let decryptedString = String (Daten entschlüsselt, Kodierung: String.Encoding.utf8) result - ", decryptedString ??" Fehler: Daten konnten nicht in Zeichenfolge konvertiert werden ")
In unserem Beispiel packen wir alle notwendigen Informationen zusammen und senden sie als Wörterbuch
damit alle Teile später zum erfolgreichen Entschlüsseln der Daten verwendet werden können. Sie müssen lediglich IV und Salt speichern, entweder im Schlüsselbund oder auf Ihrem Server.
Damit ist die dreiteilige Serie zur Datensicherung in Ruhe abgeschlossen. Wir haben gesehen, wie Passwörter, vertrauliche Informationen und große Mengen an Benutzerdaten ordnungsgemäß gespeichert werden. Diese Techniken dienen als Grundlage zum Schutz der in Ihrer App gespeicherten Benutzerinformationen.
Es ist ein enormes Risiko, wenn das Gerät eines Benutzers verloren geht oder gestohlen wird, insbesondere bei den jüngsten Angriffen, um Zugriff auf ein gesperrtes Gerät zu erhalten. Während viele Systemschwachstellen mit einem Softwareupdate gepatcht werden, ist das Gerät selbst nur so sicher wie der Benutzercode und die Version von iOS. Daher ist es Sache des Entwicklers jeder App, einen starken Schutz der gespeicherten sensiblen Daten zu gewährleisten.
Alle bisher behandelten Themen nutzen Apples Frameworks. Ich werde eine Idee mit Ihnen zum Nachdenken hinterlassen. Was passiert, wenn die Verschlüsselungsbibliothek von Apple angegriffen wird??
Wenn eine häufig verwendete Sicherheitsarchitektur gefährdet ist, sind auch alle darauf basierenden Apps gefährdet. Jede dynamisch verknüpfte iOS-Bibliothek, insbesondere auf Geräten mit Jailbreak, kann gepatcht und durch schädliche ersetzt werden.
Eine statische Bibliothek, die mit der Binärdatei Ihrer App gebündelt ist, ist jedoch vor dieser Art von Angriff geschützt. Wenn Sie versuchen, sie zu patchen, müssen Sie die Binärdatei der App ändern. Dadurch wird die Codesignatur der App beschädigt, sodass sie nicht gestartet werden kann. Wenn Sie beispielsweise OpenSSL für Ihre Verschlüsselung importiert und verwendet haben, ist Ihre App nicht anfällig für einen weit verbreiteten Apple-API-Angriff. Sie können OpenSSL selbst kompilieren und statisch in Ihre App einbinden.
Es gibt also immer mehr zu lernen und die Zukunft der App-Sicherheit auf iOS entwickelt sich ständig weiter. Die iOS-Sicherheitsarchitektur unterstützt jetzt auch kryptografische Geräte und Chipkarten! Zum Schluss kennen Sie nun die Best Practices für die Sicherung der Ruhezustandsdaten. Daher müssen Sie sie befolgen!
In der Zwischenzeit können Sie sich auch mit anderen Inhalten zur Entwicklung von iOS-Apps und zur Anwendungssicherheit beschäftigen.