Advertisement
  1. Code
  2. iOS SDK

Sichern von iOS-Daten in Ruhe: Schützen der Benutzerdaten

Scroll to top
Read Time: 13 min
This post is part of a series called Securing iOS Data at Rest.
Securing iOS Data at Rest: The Keychain

() translation by (you can also view the original English article)

Das ist der erste von drei Artikeln zum Sichern von Benutzerdaten in Ruhe. In diesem Beitrag beginnen wir mit den Grundlagen zum Schutz von Daten unter iOS, damit Sie die aktuellen Best Practices zum sicheren Speichern von Daten mit Swift kennenlernen können.

Jede App, die die Daten des Benutzers speichert, muss für die Sicherheit und den Datenschutz dieser Daten sorgen. Wie wir bei den jüngsten Datenschutzverletzungen gesehen haben, kann dies schwerwiegende Folgen haben, wenn die gespeicherten Daten Ihrer Benutzer nicht geschützt werden. In diesem Tutorial lernen Sie einige bewährte Methoden zum Schutz der Daten Ihrer Benutzer kennen.

Berechtigungen

Bevor wir mit dem Speichern Ihrer benutzerdefinierten Daten beginnen, werfen wir einen Blick auf Daten, die von System-Apps gemeinsam genutzt werden können.

Für viele iOS-Versionen war es erforderlich, App-Berechtigungen anzufordern, um einige der privaten Daten des Benutzers zu verwenden und zu speichern, die außerhalb der App liegen, z. B. beim Speichern und Laden von Bildern in die Fotobibliothek. Ab iOS 10 müssen Sie für alle APIs, die auf die privaten Daten des Benutzers zugreifen, diesen Zugriff vorab in der Datei info.plist Ihres Projekts deklarieren.

Es gibt viele Frameworks, die auf Daten außerhalb Ihrer App zugreifen können, und jedes Framework verfügt über einen entsprechenden Datenschutzschlüssel.

  • Bluetooth-Freigabe: NSBluetoothPeripheralUsageDescription
  • Kalender: NSCalendarsUsageDescription
  • CallKit: NSVoIPUsageDescription
  • Kamera: NSCameraUsageDescription
  • Kontakte: NSContactsUsageDescription
  • Zustand: NSHealthShareUsageDescription, NSHealthUpdateUsageDescription
  • HomeKit: NSHomeKitUsageDescription
  • Speicherort: NSLocationAlwaysUsageDescription, NSLocationUsageDescription, NSLocationWhenInUseUsageDescription
  • Medienbibliothek: NSAppleMusicUsageDescription
  • Mikrofon: NSMicrophoneUsageDescription
  • Bewegung: NSMotionUsageDescription
  • Fotos: NSPhotoLibraryUsageDescription
  • Erinnerungen: NSRemindersUsageDescription
  • Spracherkennung: NSSpeechRecognitionUsageDescription
  • SiriKit: NSSiriUsageDescription
  • TV-Anbieter: NSVideoSubscriberAccountUsageDescription

Hier ist beispielsweise ein Eintrag in info.plist, mit dem Ihre App Werte laden und im Kalender speichern kann.

1
<key>NSCalendarsUsageDescription</key>
2
<string>View and add events to your calendar</string>

Wenn eine Verwendungsbeschreibung fehlt, wenn die API versucht, auf die Daten zuzugreifen, stürzt die App einfach ab.

Die Datenschutz-API

Bei allen Benutzerdaten, die in der App enthalten sind, müssen Sie zunächst überlegen, ob Sie die Informationen speichern müssen und welche Daten für die App wichtig sind. Bewahren Sie so viele dieser wichtigen Daten im Arbeitsspeicher und nicht im Dateispeicher auf. Dies ist besonders wichtig für personenbezogene Daten.

Wenn Sie jedoch Daten speichern müssen, empfiehlt es sich, den Datenschutz von Apple zu aktivieren.

Data Protection verschlüsselt den Inhalt des Containers Ihrer App. Es hängt davon ab, dass der Benutzer einen Passcode hat, und daher ist die Sicherheit der Verschlüsselung an die Stärke des Passcodes gebunden. Mit Touch ID und der in iOS 10.3 eingeführten aktualisierten Dateisystemverschlüsselung hat das Datenschutzsystem viele Verbesserungen erfahren. Sie können den Datenschutz in Ihrer App aktivieren, indem Sie den Datenschutz im Abschnitt Funktionen Ihrer Projektdatei aktivieren. Dadurch werden Ihr Bereitstellungsprofil und Ihre Berechtigungsdatei aktualisiert, um die Datenschutzfunktion einzuschließen. Data Protection bietet vier Schutzstufen, die in der FileProtectionType-Struktur dargestellt werden:

  • none: kein Schutz.
  • complete: Auf Daten kann nicht zugegriffen werden, solange das Gerät gesperrt ist. Das ist die empfohlene Einstellung für die meisten Anwendungen.
  • completeUnlessOpen: Auf Daten kann zugegriffen werden, wenn das Gerät entsperrt wird, und es bleibt weiterhin verfügbar, bis die Datei geschlossen wird, auch wenn der Benutzer das Gerät sperrt. Dateien können auch erstellt werden, wenn das Gerät gesperrt ist. Diese Option eignet sich, wenn Sie eine Datei zum Verarbeiten öffnen und den Vorgang fortsetzen müssen, auch wenn der Benutzer die App in den Hintergrund stellt und das Gerät sperrt. Ein Beispiel könnte ein Job sein, der eine Datei auf einen Server hochlädt.
  • completeUntilFirstUserAuthentication: Wenn das Gerät gestartet wird, kann auf Dateien erst zugegriffen werden, wenn der Benutzer das Gerät zum ersten Mal entsperrt. Danach sind Dateien auch dann verfügbar, wenn das Gerät wieder gesperrt ist. Die Option eignet sich für Dateien, auf die später im Hintergrund zugegriffen werden muss, wenn das Gerät gesperrt ist, z. B. während eines Hintergrundabrufauftrags.

complete ist die Standardstufe. Um Abstürze zu vermeiden, wenn Ihr Code versucht, auf gesperrte Daten zuzugreifen, können Sie sich über UIApplicationProtectedDataDidBecomeAvailable und UIApplicationProtectedDataWillBecomeUnavailable für Benachrichtigungen registrieren, um herauszufinden, wann die Daten verfügbar sind.

1
NotificationCenter.default.addObserver(forName: .UIApplicationProtectedDataDidBecomeAvailable, object: nil, queue: OperationQueue.main, using: { (notification) in
2
    //...

3
})
4
NotificationCenter.default.addObserver(forName: .UIApplicationProtectedDataWillBecomeUnavailable, object: nil, queue: OperationQueue.main, using: { (notification) in
5
    //...

6
})

Darüber hinaus können Sie auch das Flag UIApplication.shared.isProtectedDataAvailable überprüfen.

Eine wichtige Sache, die Sie beim Aktivieren des Datenschutzes beachten sollten, ist, dass dieser Code bei Verwendung von Hintergrunddiensten wie Hintergrundabruf möglicherweise Zugriff auf Ihre Daten im Hintergrund benötigt, wenn das Gerät gesperrt ist. Für diese Dateien müssen Sie eine Schutzstufe für completeUntilFirstUserAuthentication festlegen. Sie können die Schutzstufe jeer Datei einzeln steuern, wenn Sie Dateien und Verzeichnisse mit der FileManager-Klasse erstellen.

1
let ok = FileManager.default.createFile(atPath: somePath, contents: nil, attributes: [FileAttributeKey.protectionKey.rawValue: FileProtectionType.complete])
2
do
3
{
4
    try FileManager.default.createDirectory(atPath: somePath, withIntermediateDirectories: true, attributes: [FileAttributeKey.protectionKey.rawValue: FileProtectionType.complete])
5
}
6
catch
7
{
8
    print(error)
9
}

Sie können die Schutzstufe auch festlegen, wenn Sie in eine Datei schreiben. Das Data objekt verfügt über eine Methode, mit der die Daten in eine Datei geschrieben werden können. Sie können die Schutzstufe festlegen, wenn Sie diese Methode aufrufen.

1
let data = Data.init()
2
let fileURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("somedata.dat")
3
do
4
{
5
    try data.write(to: fileURL, options: ([.atomic, .completeFileProtection]))
6
}
7
catch
8
{
9
    print(error)
10
}

Sie können die Schutzstufe auch beim Einrichten Ihres Core Data-Modells festlegen.

1
let storeURL = docURL?.appendingPathComponent("Model.sqlite")
2
let storeOptions: [AnyHashable: Any] = [NSPersistentStoreFileProtectionKey: FileProtectionType.complete]
3
do
4
{
5
    try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: storeOptions)
6
}
7
catch
8
{
9
    print(error)
10
}

Verwenden Sie Folgendes, um die Schutzstufe einer vorhandenen Datei zu ändern:

1
do
2
{
3
    try FileManager.default.setAttributes([FileAttributeKey.protectionKey : FileProtectionType.complete], ofItemAtPath: path)
4
}
5
catch
6
{
7
    print(error)
8
}

Datenintegrität

Zum Schutz Ihrer gespeicherten Daten gehört auch die Überprüfung ihrer Integrität. Es wird empfohlen, den Daten, die Sie aus dem Speicher laden, nicht blind zu vertrauen. Möglicherweise wurde es versehentlich oder in böswilliger Absicht geändert. Mit dem NSSecureCoding-Protokoll können Sie Ihre Datenobjekte sicher laden und aus dem Speicher speichern. Dadurch wird sichergestellt, dass die von Ihnen geladenen Objekte die erwarteten Daten enthalten. Wenn Sie Ihr eigenes Objekt speichern, können Sie sich an das sichere Codierungsprotokoll in Ihrer Klasse halten.

1
class ArchiveExample : NSObject, NSSecureCoding
2
{
3
    var stringExample : String?
4
    ...

Die Klasse muss von NSObject geerbt werden. Um dann die sichere Codierung zu aktivieren, überschreiben Sie die Protokollmethode supportSecureCoding.

1
static var supportsSecureCoding : Bool
2
{
3
    get
4
    {
5
        return true
6
    }
7
}

Wenn Ihr benutzerdefiniertes Objekt mit init?(coder aDecoder: NSCoder), sollte die Methode decodeObject(forKey:) durch decodeObject(of: forKey:) ersetzt werden, um sicherzustellen, dass die richtigen Objekttypen aus dem Speicher entpackt werden.

1
required init?(coder aDecoder: NSCoder)
2
{
3
    stringExample = aDecoder.decodeObject(of: NSString.self, forKey: "string_example") as String?
4
}
5
func encode(with aCoder: NSCoder)
6
{
7
    aCoder.encode(stringExample, forKey:"string_example")
8
}

Wenn Sie NSKeyedUnarchiver zum Laden von Daten aus dem Speicher verwenden, müssen Sie die Eigenschaft requireSecureCoding festlegen.

1
class func loadFromSavedData() -> ArchiveExample?
2
{
3
    var object : ArchiveExample? = nil
4
    let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
5
    let url = NSURL(fileURLWithPath: path)
6
    let fileURL = url.appendingPathComponent("ArchiveExample.plist")
7
    if FileManager.default.fileExists(atPath: (fileURL?.path)!)
8
    {
9
        do
10
        {
11
            let data = try Data.init(contentsOf: fileURL!)
12
            let unarchiver = NSKeyedUnarchiver.init(forReadingWith: data)
13
            unarchiver.requiresSecureCoding = true
14
            object = unarchiver.decodeObject(of: ArchiveExample.self, forKey: NSKeyedArchiveRootObjectKey)
15
            unarchiver.finishDecoding()
16
        }
17
        catch
18
        {
19
            print(error)
20
        }
21
    }
22
    return object;
23
}

Durch Aktivieren der sicheren Codierung für Ihre Sicherungsvorgänge wird verhindert, dass Sie versehentlich ein Objekt archivieren, das nicht dem sicheren Codierungsprotokoll entspricht.

1
func save()
2
{
3
    let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
4
    let url = NSURL(fileURLWithPath: path)
5
    let filePath = url.appendingPathComponent("ArchiveExample.plist")?.path
6
    let data = NSMutableData.init()
7
    let archiver = NSKeyedArchiver.init(forWritingWith: data)
8
    archiver.requiresSecureCoding = true
9
    archiver.encode(self, forKey: NSKeyedArchiveRootObjectKey)
10
    archiver.finishEncoding()
11
    
12
    let options : NSData.WritingOptions = [.atomic, .completeFileProtection]
13
    do
14
    {
15
        try data.write(toFile: filePath!, options:options)
16
    }
17
    catch
18
    {
19
        print(error)
20
    }
21
}

Über NSSecureCoding hinaus ist es immer gut, beim Entpacken eines Archivs oder beim Empfang beliebiger Eingaben im Allgemeinen eigene Datenüberprüfungen durchzuführen.

Datenpfade

Während sich iOS weiterentwickelt, gibt es immer neue Funktionen, die möglicherweise gespeicherte Daten verlieren. Ab iOS 9 können Sie Ihre Inhalte in der Spotlight-Suche indizieren lassen, und unter iOS 10 können Sie Ihre Inhalte Widgets wie dem Heute-Widget aussetzen, das auf dem Sperrbildschirm angezeigt wird. Seien Sie vorsichtig, wenn Sie Ihre Inhalte mit diesen neuen Funktionen verfügbar machen möchten. Möglicherweise teilen Sie am Ende mehr als geplant!

iOS 10 fügt außerdem eine neue Übergabefunktion hinzu, mit der Ihre kopierten Pasteboard-Daten automatisch zwischen Geräten geteilt werden. Achten Sie auch hier darauf, dass keine sensiblen Daten im Pasteboard der Übergabe ausgesetzt werden. Sie können dies tun, indem Sie den vertraulichen Inhalt als localOnly markieren. Sie können auch ein Ablaufdatum und eine Ablaufzeit für die Daten festlegen.

1
let stringToCopy = "copy me to pasteboard"
2
let pasteboard = UIPasteboard.general
3
if #available(iOS 10, *)
4
{
5
    let tomorrow = Date().addingTimeInterval(60 * 60 * 24)
6
    pasteboard.setItems([[kUTTypeUTF8PlainText as String : stringToCopy]], options: [UIPasteboardOption.localOnly : true, UIPasteboardOption.expirationDate: tomorrow])
7
}
8
else
9
{
10
    pasteboard.string = stringToCopy
11
}

Dateien, die im Speicher des Geräts gespeichert sind, können automatisch gesichert werden, entweder in iTunes oder in iCloud. Obwohl Backups verschlüsselt werden können, ist es eine gute Idee, vertrauliche Dateien auszuschließen, die das Gerät nicht einmal verlassen müssen. Dies kann durch Setzen des isExcludedFromBackup-Flags in der Datei erfolgen.

1
let path: String = ...
2
var url = URL(fileURLWithPath: path)
3
do
4
{
5
    var resourceValues = URLResourceValues()
6
    //or if you want to first check the flag:

7
    //var resourceValues = try url.resourceValues(forKeys: [.isExcludedFromBackupKey])

8
    
9
    resourceValues.isExcludedFromBackup = true;
10
    try url.setResourceValues(resourceValues)
11
}
12
catch
13
{
14
    print(error)
15
}

Die Animation, die beim Einfügen einer App in den Hintergrund ausgeführt wird, wird von iOS erzielt, indem ein Screenshot Ihrer App erstellt wird, der dann für die Animation verwendet wird. Wenn Sie sich die Liste der geöffneten Apps im App Switcher ansehen, wird dieser Screenshot auch dort verwendet. Der Screenshot wird auf dem Gerät gespeichert.

Es ist eine gute Idee, alle Ansichten auszublenden, die vertrauliche Daten enthüllen, damit die Daten nicht im Screenshot erfasst werden. Richten Sie dazu eine Benachrichtigung ein, wenn die Anwendung in den Hintergrund tritt, und legen Sie die versteckte Eigenschaft für die Benutzeroberflächenelemente fest, die Sie ausschließen möchten. Sie werden ausgeblendet, bevor iOS den Bildschirm erfasst. Wenn Sie dann in den Vordergrund kommen, können Sie die UI-Elemente einblenden.

1
NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: .UIApplicationDidEnterBackground, object: nil)
2
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: .UIApplicationWillEnterForeground, object: nil)

Entfernen Sie Ihre Benachrichtigungen, wenn die Ansicht verschwindet.

1
NotificationCenter.default.removeObserver(self, name: .UIApplicationDidEnterBackground, object: nil)
2
NotificationCenter.default.removeObserver(self, name: .UIApplicationWillEnterForeground, object: nil)

Ihre App verfügt außerdem über einen Tastaturcache für Textfelder, für die die automatische Korrektur aktiviert ist. Vom Benutzer eingegebener Text wird zusammen mit neu erlernten Wörtern im Cache gespeichert, sodass verschiedene Wörter abgerufen werden können, die der Benutzer zuvor in Ihre Anwendung eingegeben hat. Die einzige Möglichkeit, den Tastaturcache zu deaktivieren, besteht darin, die Option zur automatischen Korrektur zu deaktivieren.

1
textField.autocorrectionType = UITextAutocorrectionType.no

Sie müssen Kennwortfelder als sichere Texteingabe markieren. In sicheren Textfeldern wird weder das Kennwort angezeigt noch der Tastaturcache verwendet.

1
textField.isSecureTextEntry = true

Debug-Protokolle werden in einer Datei gespeichert und können für Produktions-Builds Ihrer App abgerufen werden. Achten Sie auch beim Codieren und Debuggen Ihrer App darauf, keine vertraulichen Informationen wie Kennwörter und Schlüssel in der Konsole zu protokollieren. Vielleicht vergessen Sie, diese Informationen aus den Protokollen zu entfernen, bevor Sie Ihren Code an den App Store senden! Während des Debuggens ist es sicherer, stattdessen einen Haltepunkt zum Anzeigen vertraulicher Variablen zu verwenden.

Netzwerkverbindungen werden möglicherweise auch im Speicher zwischengespeichert. Weitere Informationen zum Entfernen und Deaktivieren des Netzwerkcaches finden Sie im Artikel Sichern der Kommunikation unter iOS.

Daten zerstören

Möglicherweise wissen Sie bereits, dass beim Löschen einer Datei auf einem Computer häufig die Datei selbst nicht entfernt wird. Nur die Referenz für die Datei wird entfernt. Um die Datei tatsächlich zu entfernen, können Sie die Datei mit zufälligen Daten überschreiben, bevor Sie sie entfernen.

Die Umstellung auf Solid-State-Laufwerke hat es schwierig gemacht, die Zerstörung der Daten zu gewährleisten, und der beste Weg, Daten sicher zu löschen, ist umstritten. Dieses Tutorial wäre jedoch ohne ein Beispiel zum Löschen von Daten aus dem Speicher nicht vollständig. Aufgrund einiger anderer Debatten über das Swift-Optimierungsprogramm und weil wir hoffen, dass jedes Byte der Datei tatsächlich überschrieben wird, implementieren wir diese Funktion in C.

Die folgende Implementierung kann sich in einer .c-Datei befinden. Sie müssen die Funktionsdefinition oder die Datei, die die Funktion enthält, in Ihren Bridging-Header einfügen, um die Funktion von Swift verwenden zu können. Sie können diese Funktion dann direkt vor Stellen aufrufen, an denen Sie die removeFile-Methoden von FileManager verwenden. Vielleicht möchten Sie die in diesem und den kommenden Tutorials beschriebenen Best Practices für ein App-Update implementieren. Sie können dann die vorherigen ungeschützten Daten während der Migration löschen.

1
#import <string.h>

2
#import <sys/stat.h>

3
#import <unistd.h>

4
#import <errno.h>

5
#import <fcntl.h>

6
#import <stdio.h>

7
8
#define MY_MIN(a, b) (((a) < (b)) ? (a) : (b))

9
int SecureWipeFile(const char *filePath)
10
{
11
    int lastStatus = -1;
12
    for (int pass = 1; pass < 4; pass++)
13
    {
14
        //setup local vars

15
        int fileHandleInt = open(filePath, O_RDWR);
16
        struct stat stats;
17
        unsigned char charBuffer[1024];
18
        
19
        //if can open file

20
        if (fileHandleInt >= 0)
21
        {
22
            //get file descriptors

23
            int result = fstat(fileHandleInt, &stats);
24
            if (result == 0)
25
            {
26
                switch (pass)
27
                {
28
                        //DOD 5220.22-M implementation states that we write over with three passes first with 10101010, 01010101 and then the third with random data

29
                    case 1:
30
                        //write over with 10101010

31
                        memset(charBuffer, 0x55, sizeof(charBuffer));
32
                        break;
33
                    case 2:
34
                        //write over with 01010101

35
                        memset(charBuffer, 0xAA, sizeof(charBuffer));
36
                        break;
37
                    case 3:
38
                        //write over with arc4random

39
                        for (unsigned long i = 0; i < sizeof(charBuffer); ++i)
40
                        {
41
                            charBuffer[i] = arc4random() % 255;
42
                        }
43
                        break;
44
                        
45
                    default:
46
                        //at least write over with random data

47
                        for (unsigned long i = 0; i < sizeof(charBuffer); ++i)
48
                        {
49
                            charBuffer[i] = arc4random() % 255;
50
                        }
51
                        break;
52
                }
53
                
54
                //get file size in bytes

55
                off_t fileSizeInBytes = stats.st_size;
56
                
57
                //rewrite every byte of the file

58
                ssize_t numberOfBytesWritten;
59
                for ( ; fileSizeInBytes; fileSizeInBytes -= numberOfBytesWritten)
60
                {
61
                    //write bytes from the buffer into the file

62
                    numberOfBytesWritten = write(fileHandleInt, charBuffer, MY_MIN((size_t)fileSizeInBytes, sizeof(charBuffer)));
63
                }
64
                
65
                //close the file

66
                lastStatus = close(fileHandleInt);
67
            }
68
        }
69
    }
70
    return lastStatus;
71
}

Abschluss

In diesem Artikel erfahren Sie, wie Sie Berechtigungen für die Daten festlegen, auf die Ihre App Zugriff hat, und wie Sie den grundlegenden Schutz und die Integrität von Dateien sicherstellen. Wir haben auch einige Möglichkeiten untersucht, wie Benutzerdaten versehentlich aus Ihrer App verloren gehen können. Ihre Benutzer vertrauen Ihnen, um ihre Daten zu schützen. Das Befolgen dieser Best Practices hilft Ihnen dabei, dieses Vertrauen zurückzuzahlen.

Schauen Sie sich während Ihres Aufenthalts einige unserer anderen Beiträge zur Entwicklung von iOS-Apps an!


Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.