AES-Verschlüsselung für einen NSString auf dem iPhone

124

Kann mich jemand in die richtige Richtung weisen, um eine Zeichenfolge verschlüsseln zu können und eine andere Zeichenfolge mit den verschlüsselten Daten zurückzugeben? (Ich habe versucht, mit AES256-Verschlüsselung zu arbeiten.) Ich möchte eine Methode schreiben, die zwei NSString-Instanzen benötigt, von denen eine die zu verschlüsselnde Nachricht und die andere ein Passcode zum Verschlüsseln ist. Ich vermute, ich müsste sie generieren den Verschlüsselungsschlüssel mit dem Passcode auf eine Weise, die umgekehrt werden kann, wenn der Passcode mit den verschlüsselten Daten geliefert wird. Die Methode sollte dann einen NSString zurückgeben, der aus den verschlüsselten Daten erstellt wurde.

Ich habe die im ersten Kommentar zu diesem Beitrag beschriebene Technik ausprobiert , aber bisher hatte ich kein Glück. Apples CryptoExercise hat sicherlich etwas, aber ich kann es nicht verstehen ... Ich habe viele Verweise auf CCCrypt gesehen , aber es ist in jedem Fall fehlgeschlagen, in dem ich es verwendet habe.

Ich müsste auch in der Lage sein, eine verschlüsselte Zeichenfolge zu entschlüsseln, aber ich hoffe, das ist so einfach wie kCCEncrypt / kCCDecrypt.

Boz
quelle
1
Bitte beachten Sie, dass ich einer Antwort von Rob Napier, der eine sichere Version der Antwort bereitgestellt hat, ein Kopfgeld gegeben habe .
Maarten Bodewes

Antworten:

126

Da Sie keinen Code veröffentlicht haben, ist es schwierig, genau zu wissen, auf welche Probleme Sie stoßen. Der Blog-Beitrag, auf den Sie verlinken, scheint jedoch recht anständig zu funktionieren ... abgesehen von dem zusätzlichen Komma in jedem Aufruf, CCCrypt()das Kompilierungsfehler verursacht hat.

Ein späterer Kommentar zu diesem Beitrag enthält diesen angepassten Code , der für mich funktioniert und etwas einfacher zu sein scheint. Wenn Sie den Code für die Kategorie NSData einfügen, können Sie Folgendes schreiben: (Hinweis: Die printf()Aufrufe dienen nur dazu, den Status der Daten an verschiedenen Stellen zu demonstrieren. In einer realen Anwendung wäre es nicht sinnvoll, solche Werte zu drucken .)

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSString *key = @"my password";
    NSString *secret = @"text to encrypt";

    NSData *plain = [secret dataUsingEncoding:NSUTF8StringEncoding];
    NSData *cipher = [plain AES256EncryptWithKey:key];
    printf("%s\n", [[cipher description] UTF8String]);

    plain = [cipher AES256DecryptWithKey:key];
    printf("%s\n", [[plain description] UTF8String]);
    printf("%s\n", [[[NSString alloc] initWithData:plain encoding:NSUTF8StringEncoding] UTF8String]);

    [pool drain];
    return 0;
}

Angesichts dieses Codes und der Tatsache, dass verschlüsselte Daten nicht immer gut in einen NSString übersetzt werden können, ist es möglicherweise bequemer, zwei Methoden zu schreiben, die die von Ihnen benötigte Funktionalität vorwärts und rückwärts einschließen ...

- (NSData*) encryptString:(NSString*)plaintext withKey:(NSString*)key {
    return [[plaintext dataUsingEncoding:NSUTF8StringEncoding] AES256EncryptWithKey:key];
}

- (NSString*) decryptData:(NSData*)ciphertext withKey:(NSString*)key {
    return [[[NSString alloc] initWithData:[ciphertext AES256DecryptWithKey:key]
                                  encoding:NSUTF8StringEncoding] autorelease];
}

Dies funktioniert definitiv bei Snow Leopard und @Boz berichtet, dass CommonCrypto Teil des Core-Betriebssystems auf dem iPhone ist. Sowohl 10.4 als auch 10.5 haben /usr/include/CommonCrypto, obwohl 10.5 eine Manpage für CCCryptor.3ccund 10.4 nicht hat, also YMMV.


EDIT: Siehe Diese Folgefrage zur Verwendung von Base64 - Kodierung für verschlüsselte Daten , welche als eine Zeichenkette Bytes (falls gewünscht) unter Verwendung von sicherem, lossless Konvertierungen.

Quinn Taylor
quelle
1
Vielen Dank. CommonCrypto ist Teil des Core-Betriebssystems auf dem iPhone, und ich verwende auch 10.6.
Boz
1
Ich habe -1 gemacht, weil der Code, auf den verwiesen wird, gefährlich unsicher ist. Schauen Sie sich stattdessen Rob Napiers Antwort an. Sein Blogeintrag " robnapier.net/aes-commoncrypto beschreibt genau, warum dies unsicher ist.
Erik Engheim
1
Diese Lösung funktioniert in meinem Fall nicht. Ich habe eine Zeichenfolge, die ich dekodieren möchte: U2FsdGVkX1 + MEhsbofUNj58m + 8tu9ifAKRiY / Zf8YIw = und ich habe den Schlüssel: 3841b8485cd155d932a2d601b8cee2ec. Ich kann die Zeichenfolge mit dem Schlüssel Ihrer Lösung nicht entschlüsseln. Vielen Dank
George
Diese Lösung funktioniert nicht in einer Cocoa-App auf El Capitan mit XCode7. ARC verbietet die autorelease.
Volomike
@QuinnTaylor Ich kann diese Antwort bearbeiten, wollte Ihnen aber die Möglichkeit geben, sie nach Belieben zu ändern. Ich habe Ihren Code hier repariert . Möglicherweise möchten Sie auch darauf hinweisen, dass der angepasste Code ohne diesen angepassten Code nicht kompiliert werden kann. Also habe ich es geschafft, an einer Cocoa-Anwendung auf El Capitan mit XCode7 zu arbeiten. Ich versuche nun herauszufinden, wie Base64Encode / Base64Decode diese Daten so umsetzt, dass sie übertragbar sind, ohne während der Übertragung gestört zu werden, anstatt Rohdaten zurückzugeben.
Volomike
46

Ich habe eine Sammlung von Kategorien für NSData und NSString zusammengestellt, die Lösungen aus Jeff LaMarches Blog und einige Hinweise von Quinn Taylor hier auf Stack Overflow verwenden.

Es verwendet Kategorien, um NSData zu erweitern, um eine AES256-Verschlüsselung bereitzustellen, und bietet auch eine Erweiterung von NSString, um verschlüsselte Daten mit BASE64-Codierung sicher in Zeichenfolgen zu codieren.

Hier ist ein Beispiel, um die Verwendung zum Verschlüsseln von Zeichenfolgen zu zeigen:

NSString *plainString = @"This string will be encrypted";
NSString *key = @"YourEncryptionKey"; // should be provided by a user

NSLog( @"Original String: %@", plainString );

NSString *encryptedString = [plainString AES256EncryptWithKey:key];
NSLog( @"Encrypted String: %@", encryptedString );

NSLog( @"Decrypted String: %@", [encryptedString AES256DecryptWithKey:key] );

Den vollständigen Quellcode erhalten Sie hier:

https://gist.github.com/838614

Vielen Dank für alle hilfreichen Tipps!

- Michael

Michael Thiel
quelle
NSString * key = @ "YourEncryptionKey"; // sollte von einem Benutzer bereitgestellt werden Können wir einen zufälligen sicheren 256-Bit-Schlüssel anstelle eines vom Benutzer bereitgestellten generieren?
Pranav Jaiswal
Der Jeff LaMarche Link ist unterbrochen
Whyoz
35

@owlstead, bezüglich Ihrer Anfrage nach "einer kryptografisch sicheren Variante einer der angegebenen Antworten", siehe RNCryptor . Es wurde entwickelt, um genau das zu tun, was Sie anfordern (und wurde als Reaktion auf die Probleme mit dem hier aufgeführten Code erstellt).

RNCryptor verwendet PBKDF2 mit Salt, stellt eine zufällige IV bereit und hängt HMAC (ebenfalls aus PBKDF2 mit seinem eigenen Salt generiert) an. Es unterstützt den synchronen und asynchronen Betrieb.

Rob Napier
quelle
Interessanter Code und wahrscheinlich die Punkte wert. Was ist die Iterationszahl für das PBKDF2 und worüber berechnen Sie den HMAC? Ich nehme nur die verschlüsselten Daten an? Das konnte ich in der mitgelieferten Dokumentation nicht so leicht finden.
Maarten Bodewes
Weitere Informationen finden Sie unter "Best Practice-Sicherheit". Ich empfehle 10.000 Iterationen unter iOS (~ 80 ms auf einem iPhone 4). Und ja, verschlüsseln als HMAC. Ich werde heute Abend wahrscheinlich die Seite "Datenformat" durchsehen, um sicherzustellen, dass sie auf Version 2.0 auf dem neuesten Stand ist (die Hauptdokumente sind auf dem neuesten Stand, aber ich kann mich nicht erinnern, ob ich die Seite im Datenformat überarbeitet habe).
Rob Napier
Ah, ja, fand die Anzahl der Runden in den Dokumenten und sah den Code. Ich sehe dort Bereinigungsfunktionen und separate HMAC- und Verschlüsselungsschlüssel. Wenn es die Zeit erlaubt, werde ich versuchen, morgen einen genaueren Blick darauf zu werfen. Dann werde ich die Punkte vergeben.
Maarten Bodewes
5
Verschlüsseln Sie in NSData und verwenden Sie einen der vielen Base64-Encoder, um diese in eine Zeichenfolge zu konvertieren. Es gibt keine Möglichkeit, von einer Zeichenfolge zu einer Zeichenfolge ohne einen Daten-zu-Zeichenfolge-Encoder zu verschlüsseln.
Rob Napier
1
@Jack Auf Anraten meines Anwalts (der meine mangelnde Expertise im Export-Compliance-Recht äußerst farbenfroh beschrieben hat…) gebe ich keine Ratschläge mehr zum Export-Compliance-Recht. Sie müssen mit Ihrem Anwalt besprechen.
Rob Napier
12

Ich habe ein bisschen auf @QuinnTaylor gewartet, um seine Antwort zu aktualisieren, aber da er es nicht getan hat, ist hier die Antwort etwas klarer und so, dass sie auf XCode7 (und vielleicht auch höher) geladen wird. Ich habe dies in einer Cocoa-Anwendung verwendet, aber es wird wahrscheinlich auch mit einer iOS-Anwendung funktionieren. Hat keine ARC-Fehler.

Fügen Sie vor jedem @ Implementierungsabschnitt in Ihre AppDelegate.m- oder AppDelegate.mm-Datei ein.

#import <CommonCrypto/CommonCryptor.h>

@implementation NSData (AES256)

- (NSData *)AES256EncryptWithKey:(NSString *)key {
    // 'key' should be 32 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
    bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

    // fetch key data
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    NSUInteger dataLength = [self length];

    //See the doc: For block ciphers, the output size will always be less than or 
    //equal to the input size plus the size of one block.
    //That's why we need to add the size of one block here
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);

    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                     keyPtr, kCCKeySizeAES256,
                                     NULL /* initialization vector (optional) */,
                                     [self bytes], dataLength, /* input */
                                     buffer, bufferSize, /* output */
                                     &numBytesEncrypted);
    if (cryptStatus == kCCSuccess) {
        //the returned NSData takes ownership of the buffer and will free it on deallocation
        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
    }

    free(buffer); //free the buffer;
    return nil;
}

- (NSData *)AES256DecryptWithKey:(NSString *)key {
    // 'key' should be 32 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
    bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

    // fetch key data
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    NSUInteger dataLength = [self length];

    //See the doc: For block ciphers, the output size will always be less than or 
    //equal to the input size plus the size of one block.
    //That's why we need to add the size of one block here
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);

    size_t numBytesDecrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                     keyPtr, kCCKeySizeAES256,
                                     NULL /* initialization vector (optional) */,
                                     [self bytes], dataLength, /* input */
                                     buffer, bufferSize, /* output */
                                     &numBytesDecrypted);

    if (cryptStatus == kCCSuccess) {
        //the returned NSData takes ownership of the buffer and will free it on deallocation
        return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
    }

    free(buffer); //free the buffer;
    return nil;
}

@end

Fügen Sie diese beiden Funktionen in die gewünschte @ Implementierungsklasse ein. In meinem Fall habe ich @implementation AppDelegate in meiner Datei AppDelegate.mm oder AppDelegate.m ausgewählt.

- (NSString *) encryptString:(NSString*)plaintext withKey:(NSString*)key {
    NSData *data = [[plaintext dataUsingEncoding:NSUTF8StringEncoding] AES256EncryptWithKey:key];
    return [data base64EncodedStringWithOptions:kNilOptions];
}

- (NSString *) decryptString:(NSString *)ciphertext withKey:(NSString*)key {
    NSData *data = [[NSData alloc] initWithBase64EncodedString:ciphertext options:kNilOptions];
    return [[NSString alloc] initWithData:[data AES256DecryptWithKey:key] encoding:NSUTF8StringEncoding];
}
Volomike
quelle
Hinweis: 1. Beim Entschlüsseln ist die Ausgabegröße geringer als die Eingabegröße beim Auffüllen (PKCS # 7). Es gibt keinen Grund, die bufferSize zu erhöhen. Verwenden Sie einfach die Größe der verschlüsselten Daten. 2. Anstatt einen Puffer dataWithBytesNoCopyzuzuordnen und dann einfach einen NSMutableDatawith zuzuweisen dataWithLengthund die mutableBytesEigenschaft für den Bytezeiger zu verwenden, und dann einfach die Größe zu ändern, indem Sie seine lengthEigenschaft festlegen . 3. Die direkte Verwendung einer Zeichenfolge für eine Verschlüsselung ist sehr unsicher. Es sollte ein abgeleiteter Schlüssel verwendet werden, wie er von PBKDF2 erstellt wurde.
Zaph
@zaph, kannst du irgendwo einen Pastebin / Pastie machen, damit ich die Änderungen sehen kann? Übrigens habe ich im obigen Code lediglich den Code, den ich von Quinn Taylor gesehen habe, angepasst, damit er funktioniert. Ich lerne dieses Geschäft immer noch, während ich gehe, und Ihre Eingabe wird mir sehr nützlich sein.
Volomike
Sehen Sie sich diese SO-Antwort an und sie hat sogar eine minimale Fehlerbehandlung und behandelt sowohl die Verschlüsselung als auch die Entschlüsselung. Es ist nicht erforderlich, den Puffer bei der Entschlüsselung zu erweitern. Es handelt sich lediglich um weniger Code, der sich nicht auf einen zusätzlichen Code spezialisiert hat, wenn wenig zu gewinnen ist. Wenn Sie den Schlüssel mit Nullen erweitern möchten (dies sollte nicht erfolgen), erstellen Sie einfach eine veränderbare Version des Schlüssels und legen Sie die Länge fest : keyData.length = kCCKeySizeAES256;.
Zaph
In dieser SO-Antwort finden Sie Informationen zur Verwendung von PBKDF2 zum Erstellen eines Schlüssels aus einer Zeichenfolge.
Zaph
@Volomike Wenn ich dies verwende, sollte ich dann bei iTunes-Connect Export Compliance Information (YES) auswählen ?
Jack