2009-09-09 14 views
118

Czy ktoś może wskazać mi właściwy kierunek, aby móc zaszyfrować ciąg, zwracając kolejny ciąg z zaszyfrowanymi danymi? (Próbowałem z szyfrowaniem AES256.) Chcę napisać metodę, która pobiera dwie instancje NSString, z których jedna jest komunikatem do szyfrowania, a druga jest "hasłem" do zaszyfrowania - podejrzewam, że musiałbym wygenerować klucz szyfrowania z hasłem, 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.Szyfrowanie AES dla NSString na iPhonie

Próbowałem już techniki opisanej w the first comment on this post, ale do tej pory nie miałem szczęścia. Apple's CryptoExercise z pewnością ma coś, ale nie mogę tego zrozumieć ... Widziałem wiele odniesień do CCCrypt, ale nie powiodło się w każdym przypadku, w którym go użyłem.

Musiałbym również móc odszyfrować zaszyfrowany ciąg znaków, ale mam nadzieję, że jest tak prosty jak kCCEncrypt/kCCDecrypt.

+1

Należy pamiętać, że dałem bounty do odpowiedzi przez Rob Napier, który dostarczył ** ** bezpieczną wersję odpowiedzi. –

Odpowiedz

124

Ponieważ nie wysłałeś żadnego kodu, trudno jest dokładnie określić, jakie problemy napotykasz. Jednak wpis na blogu, do którego prowadzi link, wydaje się działać całkiem przyzwoicie ... oprócz dodatkowego przecinka w każdym wywołaniu CCCrypt(), który powodował błędy kompilacji.

Późniejszy komentarz do tego postu zawiera this adapted code, który działa dla mnie i wydaje się nieco prostszy. Jeśli umieścisz swój kod dla kategorii NSData, możesz napisać coś takiego: (Uwaga: połączenia printf() służą tylko do demonstracji stanu danych w różnych punktach - w prawdziwej aplikacji nie ma sensu drukowanie takich danych 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, a fakt, że zaszyfrowane dane nie zawsze przekładają się ładnie do NSString, może być bardziej wygodne do pisania dwóch metod, które owinąć funkcjonalność trzeba, 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 na pewno działa na Snow Leopard i @Boz informuje, że CommonCrypto jest częścią Core OS na iPhone. Zarówno 10.4 jak i 10.5 mają /usr/include/CommonCrypto, chociaż 10.5 ma stronę podręcznika dla CCCryptor.3cc, a 10.4 nie, więc YMMV.


Edycja: Patrz this follow-up question na użyciu kodowania base64 do reprezentowania zaszyfrowanych bajtów danych w postaci łańcucha (jeśli jest pożądany), przy użyciu bezpiecznych, konwersji bezstratnej.

+1

Dzięki. CommonCrypto jest częścią Core OS na iPhone'a, a ja też mam 10,6. – Boz

+0

Dobra odpowiedź Quinn. – GSD

+0

Zrobiłem -1, ponieważ kod odniesienia jest niebezpiecznie niebezpieczny. Zamiast tego spójrz na odpowiedź Roba Napiera. Jego wpis na blogu "http://robnapier.net/aes-commoncrypto dokładnie wyjaśnia, dlaczego jest to niebezpieczne." –

45

Połączyłem kolekcję kategorii dla NSData i NSString, która wykorzystuje rozwiązania znalezione na Jeff LaMarche's blog i some hints przez Quinn Taylor tutaj na Stack Overflow.

Używa kategorii do rozszerzenia NSData, aby zapewnić szyfrowanie AES256, a także oferuje rozszerzenie NSString do szyfrowanych danych BASE64 bezpiecznie do ciągów.

Oto przykład, aby pokazać wykorzystanie szyfrowania ciągi:

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 dla wszystkich pomocnych wskazówek!

- Michael

+4

świetny kod !! Dzięki za podpowiedzi ... – Nick

+0

NSString * key = @ "YourEncryptionKey"; // powinien dostarczyć użytkownik Może wygenerować losowy bezpieczny klucz 256-bitowy zamiast jednego dostarczonego przez użytkownika. –

+0

Łącze Jeff LaMarche jest zepsute – whyoz

31

@owlstead, dotyczące żądania dla „zaszyfrowanych wariant jednej z podanych odpowiedzi,” proszę zobaczyć RNCryptor. Został zaprojektowany do wykonywania dokładnie tego, o co prosisz (i został zbudowany w odpowiedzi na problemy z kodem tutaj wymienionym).

RNCryptor wykorzystuje PBKDF2 solą, zapewnia losową IV, a także przywiązuje HMAC (wygenerowany z PBKDF2 z własnym soli. To wsparcie operacji synchronicznych i asynchronicznych.

+0

Interesujący kod i prawdopodobnie warty punktów. Jaka jest liczba iteracji dla PBKDF2 i na czym polega obliczenie HMAC? Zakładam tylko zaszyfrowane dane? Nie mogłem tego łatwo znaleźć w dostarczonej dokumentacji. –

+0

Zobacz szczegóły "Bezpieczeństwo w najlepszej praktyce". Polecam iteracje 10k na iOS (~ 80 ms na iPhone 4). I tak, zaszyfruj niż HMAC. Prawdopodobnie obejrzę dzisiaj stronę "Format danych", aby upewnić się, że jest ona aktualna w wersji 2.0 (główne dokumenty są aktualne, ale nie pamiętam, czy zmieniłem stronę formatu danych). –

+0

Ach, tak, znalazłem liczbę rund w dokumentach i spojrzałem na kod. Widzę tam funkcje czyszczenia i oddzielne klucze HMAC i klucze szyfrowania. Jeśli czas pozwoli, spróbuję jutro przyjrzeć się głębiej. Potem przydzielę punkty. –

7

czekałem trochę na @QuinnTaylor zaktualizować swoją odpowiedź , ale ponieważ tak się nie stało, oto odpowiedź nieco jaśniej i w pewnym sensie, że załaduje się na XCode7 (i być może jeszcze więcej) .Zrobiłem to w aplikacji Cocoa, ale prawdopodobnie będzie dobrze działać z aplikacją na iOS dobrze. ma błędów łuku.

Wklej przed każdym odcinku @implementation w swojej AppDelegate.m lub pliku 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 żądanej klasie @implementation. W moim przypadku wybrałem opcję @implementation AppDelegate w 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]; 
} 
+0

Uwaga: 1. Podczas odszyfrowywania rozmiar wyjściowy będzie mniejszy niż rozmiar wejściowy w przypadku dopełnienia (PKCS # 7). Nie ma powodu, aby zwiększać rozmiar bufora, wystarczy użyć zaszyfrowanego rozmiaru danych. 2. Zamiast malloc'ing bufora, a następnie 'dataWithBytesNoCopy' wystarczy przydzielić' NSMutableData' z 'dataWithLength' i użyć właściwości' mutableBytes' dla wskaźnika bajtów, a następnie po prostu zmienić rozmiar, ustawiając właściwość 'length'. 3. Używanie łańcucha bezpośrednio do szyfrowania jest bardzo niebezpieczne, należy użyć klucza pochodnego, takiego jak stworzony przez PBKDF2. – zaph

+0

@zaph, czy możesz gdzieś zrobić pastebin/pastie, abym mógł zobaczyć zmiany? BTW, na powyższym kodzie, po prostu zaadaptowałem kod, który widziałem od Quinna Taylora, aby to zadziałało. Wciąż się uczę tego biznesu, a ja, Twój wkład, będzie dla mnie bardzo przydatny. – Volomike

+0

Zobacz tę odpowiedź [SO answer] (http://stackoverflow.com/a/23641521/451475), która ma nawet minimalną obsługę błędów i obsługuje zarówno szyfrowanie, jak i odszyfrowywanie. Nie ma potrzeby rozszerzania bufora przy deszyfrowaniu, jest to po prostu mniej kodu, który nie specjalizuje się w dodatkowym, jeśli nie ma wiele do zdobycia. W przypadku, gdy pożądane jest rozszerzenie klucza o wartości zerowe (nie należy tego robić), po prostu utwórz zmienną wersję klucza i ustaw długość: 'keyData.length = kCCKeySizeAES256;'. – zaph

Powiązane problemy