Szyfrowanie AES dla NSString na iPhonie

124

Czy ktoś może wskazać mi właściwy kierunek, aby móc zaszyfrować ciąg, zwracając inny ciąg z zaszyfrowanymi danymi? (Próbowałem z szyfrowaniem AES256.) Chcę napisać metodę, która wymaga dwóch wystąpień NSString, z których jedna to wiadomość do zaszyfrowania, a druga to „hasło” do zaszyfrowania - podejrzewam, że musiałbym wygenerować klucz szyfrowania z kodem dostępu, w sposób, który można odwrócić, jeśli kod dostępu jest dostarczany z zaszyfrowanymi danymi. Metoda powinna następnie zwrócić NSString utworzony z zaszyfrowanych danych.

Wypróbowałem technikę opisaną w pierwszym komentarzu do tego postu , ale jak dotąd nie miałem szczęścia. CryptoExercise firmy Apple z pewnością ma coś, ale nie mogę tego zrozumieć ... Widziałem wiele odniesień do CCCrypt , ale zawiodło w każdym przypadku, gdy z niego korzystałem.

Musiałbym także być w stanie odszyfrować zaszyfrowany ciąg, ale mam nadzieję, że jest to tak proste, jak kCCEncrypt / kCCDecrypt.

Boz
źródło
1
Proszę zauważyć, że nagrodziłem odpowiedź Roba Napiera, który dostarczył bezpieczną wersję odpowiedzi.
Maarten Bodewes

Odpowiedzi:

126

Ponieważ nie opublikowałeś żadnego kodu, trudno jest dokładnie określić, jakie problemy napotykasz. Jednak post na blogu, do którego prowadzi łącze, wydaje się działać całkiem przyzwoicie ... poza dodatkowym przecinkiem w każdym wywołaniu, CCCrypt()który powodował błędy kompilacji.

Późniejszy komentarz do tego posta zawiera dostosowany kod , który działa dla mnie i wydaje się nieco prostszy. Jeśli dołączysz ich kod dla kategorii NSData, możesz napisać coś takiego: (Uwaga: printf()wywołania służą tylko do zademonstrowania stanu danych w różnych punktach - w prawdziwej aplikacji nie ma sensu drukować takich wartości .)

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;
}

Biorąc pod uwagę ten kod i fakt, że zaszyfrowane dane nie zawsze będą ładnie przekładać się na NSString, wygodniejsze może być napisanie dwóch metod, które zawijają potrzebną funkcjonalność, do przodu i do tyłu ...

- (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];
}

To zdecydowanie działa w systemie Snow Leopard, a @Boz informuje, że CommonCrypto jest częścią Core OS na iPhonie. Zarówno 10.4, jak i 10.5 mają /usr/include/CommonCrypto, chociaż 10.5 ma stronę CCCryptor.3ccpodręcznika dla, a 10.4 nie, więc YMMV.


EDYCJA: Zobacz to pytanie uzupełniające dotyczące używania kodowania Base64 do reprezentowania zaszyfrowanych bajtów danych jako ciągu (w razie potrzeby) przy użyciu bezpiecznych, bezstratnych konwersji.

Quinn Taylor
źródło
1
Dzięki. CommonCrypto jest częścią Core OS na iPhonie, ja też mam 10.6.
Boz
1
Zrobiłem -1, ponieważ wskazany kod jest niebezpiecznie niepewny. Zamiast tego spójrz na odpowiedź Roba Napiera. Jego wpis na blogu „ robnapier.net/aes-commoncrypto ” szczegółowo opisuje, dlaczego jest to niebezpieczne.
Erik Engheim
1
To rozwiązanie nie działa w moim przypadku. Mam ciąg, który chcę zdekodować: U2FsdGVkX1 + MEhsbofUNj58m + 8tu9ifAKRiY / Zf8YIw = i mam klucz: 3841b8485cd155d932a2d601b8cee2ec. Nie mogę odszyfrować ciągu przy użyciu klucza z Twoim rozwiązaniem. Dzięki
George,
To rozwiązanie nie działa w aplikacji Cocoa na El Capitan z XCode7. ARC zabrania autorelease.
Volomike
@QuinnTaylor Mogę edytować tę odpowiedź, ale chciałem dać Ci możliwość zmiany jej według własnego uznania. Tutaj naprawiłem twój kod . Możesz również zwrócić uwagę, że bez tego dostosowanego kodu nie będzie się kompilował. Tak więc uruchomiłem aplikację Cocoa na El Capitan z XCode7. Teraz próbuję dowiedzieć się, jak Base64Encode / Base64Decode te dane, aby można je było przesłać bez zakłóceń podczas przesyłania, zamiast zwracać surowe dane.
Volomike
46

Złożyłem zbiór kategorii dla NSData i NSString, który wykorzystuje rozwiązania znalezione na blogu Jeffa LaMarche i kilka wskazówek Quinna Taylora tutaj na Stack Overflow.

Używa kategorii, aby rozszerzyć NSData, aby zapewnić szyfrowanie AES256, a także oferuje rozszerzenie NSString do bezpiecznego szyfrowania danych BASE64 na łańcuchy.

Oto przykład pokazujący użycie do szyfrowania ciągów:

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] );

Pobierz pełny kod źródłowy tutaj:

https://gist.github.com/838614

Dzięki za wszystkie pomocne wskazówki!

-- Michael

Michael Thiel
źródło
NSString * key = @ "YourEncryptionKey"; // powinien być dostarczony przez użytkownika Czy możemy wygenerować losowy, bezpieczny klucz 256-bitowy zamiast klucza dostarczonego przez użytkownika.
Pranav Jaiswal
Link Jeffa LaMarche jest uszkodzony
dlaczegooz
35

@owlstead, jeśli chodzi o twoją prośbę o „kryptograficznie bezpieczny wariant jednej z podanych odpowiedzi”, zobacz RNCryptor . Został zaprojektowany, aby robić dokładnie to, o co prosisz (i został zbudowany w odpowiedzi na problemy z kodem wymienionym tutaj).

RNCryptor używa PBKDF2 z solą, zapewnia losowy IV i dołącza HMAC (również generowany z PBKDF2 z własną solą. Obsługuje działanie synchroniczne i asynchroniczne.

Rob Napier
źródło
Ciekawy kod i prawdopodobnie wart punktów. Jaka jest liczba iteracji dla PBKDF2 i na podstawie czego oblicza się HMAC? Zakładam, że tylko zaszyfrowane dane? Nie mogłem tego łatwo znaleźć w dostarczonej dokumentacji.
Maarten Bodewes
Szczegółowe informacje można znaleźć w sekcji „Najlepsze rozwiązania w zakresie bezpieczeństwa”. Polecam iteracje 10k na iOS (~ 80ms na iPhonie 4). I tak, szyfruj niż HMAC. Prawdopodobnie dziś wieczorem przejrzę stronę „Format danych”, aby upewnić się, że jest aktualna w wersji 2.0 (główne dokumenty są aktualne, ale nie pamiętam, czy zmieniłem stronę formatu danych).
Rob Napier
Ach, tak, znalazłem liczbę rund w dokumentach i sprawdziłem kod. Widzę tam funkcje czyszczenia i oddzielne klucze HMAC i szyfrowania. Jeśli czas na to pozwoli, spróbuję jutro przyjrzeć się dokładniej. Następnie przypiszę punkty.
Maarten Bodewes
5
Zaszyfruj do NSData i użyj jednego z wielu koderów Base64, aby przekonwertować go na ciąg. Nie ma możliwości zaszyfrowania ciągu do ciągu bez kodera danych do ciągu.
Rob Napier
1
@Jack Za radą mojego prawnika (który opisał mój brak wiedzy w zakresie przepisów eksportowych w niezwykle barwny sposób…) nie udzielam już porad dotyczących przepisów eksportowych. Będziesz musiał omówić to ze swoim prawnikiem.
Rob Napier
12

Czekałem trochę na @QuinnTaylor, aby zaktualizować jego odpowiedź, ale ponieważ tego nie zrobił, oto odpowiedź nieco jaśniejsza i w taki sposób, że załaduje się na XCode7 (i być może większy). Użyłem tego w aplikacji Cocoa, ale prawdopodobnie będzie działać dobrze również z aplikacją na iOS. Nie ma błędów ARC.

Wklej przed jakąkolwiek sekcją @implementation w pliku AppDelegate.m lub AppDelegate.mm.

#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

Wklej te dwie funkcje w wybranej klasie @implementation. W moim przypadku wybrałem @implementation AppDelegate w moim pliku AppDelegate.mm lub AppDelegate.m.

- (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
źródło
Uwaga: 1. Po odszyfrowaniu rozmiar wyjściowy będzie mniejszy niż rozmiar wejściowy, gdy występuje wypełnienie (PKCS # 7). Nie ma powodu, aby zwiększać wartość bufferSize, wystarczy użyć rozmiaru zaszyfrowanych danych. 2. Zamiast malloc'ować bufor, a następnie dataWithBytesNoCopypo prostu przydziel obiekt NSMutableDatawith dataWithLengthi użyj mutableByteswłaściwości wskaźnika bajtów, a następnie zmień rozmiar, ustawiając jego lengthwłaściwość. 3. Bezpośrednie użycie łańcucha do szyfrowania jest bardzo niebezpieczne, należy użyć klucza pochodnego, takiego jak utworzony przez PBKDF2.
zaph
@zaph, czy możesz gdzieś zrobić pastebin / pastie, abym mógł zobaczyć zmiany? Przy okazji, w powyższym kodzie dostosowałem tylko kod, który widziałem od Quinna Taylora, aby działał. Wciąż uczę się tego biznesu, a Twój wkład będzie dla mnie bardzo przydatny.
Volomike
Zobacz tę odpowiedź SO i ma nawet minimalną obsługę błędów i obsługuje zarówno szyfrowanie, jak i odszyfrowywanie. Nie ma potrzeby rozszerzania bufora przy deszyfrowaniu, po prostu mniej kodu nie specjalizuje się w dodatkowym, jeśli niewiele można zyskać. W przypadku przedłużającej klucz z null jest pożądana (które nie powinny być zrobione) wystarczy utworzyć zmienny wersję klucza i ustawić długość: keyData.length = kCCKeySizeAES256;.
zaph
Zobacz tę odpowiedź SO dotyczącą używania PBKDF2 do tworzenia klucza z ciągu.
zaph
@Volomike Jeśli używam tego, czy powinienem wybrać opcję Eksportuj informacje o zgodności (TAK) w iTunes-Connect?
Jack