Crittografia AES per una NSString su iPhone


124

Qualcuno può indicarmi la giusta direzione per poter crittografare una stringa, restituendo un'altra stringa con i dati crittografati? (Ho provato con la crittografia AES256.) Voglio scrivere un metodo che richieda due istanze di NSString, una è il messaggio da crittografare e l'altra è un "passcode" con cui crittografarlo: sospetto che dovrei generare la chiave di crittografia con il codice di accesso, in un modo che può essere invertito se il codice di accesso viene fornito con i dati crittografati. Il metodo dovrebbe quindi restituire un NSString creato dai dati crittografati.

Ho provato la tecnica descritta nel primo commento su questo post , ma finora non ho avuto fortuna. CryptoExercise di Apple ha sicuramente qualcosa, ma non riesco a capirlo ... Ho visto molti riferimenti a CCCrypt , ma è fallito in ogni caso l'ho usato.

Dovrei anche essere in grado di decrittografare una stringa crittografata, ma spero che sia semplice come kCCEncrypt / kCCDecrypt.


1
Per favore nota che ho dato una ricompensa a una risposta di Rob Napier che ha fornito una versione sicura della risposta.
Maarten Bodewes

Risposte:


126

Poiché non hai pubblicato alcun codice, è difficile sapere esattamente quali problemi stai riscontrando. Tuttavia, il post del blog a cui ti colleghi sembra funzionare abbastanza decentemente ... a parte la virgola in più in ogni chiamata CCCrypt()che ha causato errori di compilazione.

Un commento successivo su quel post include questo codice adattato , che funziona per me e sembra un po 'più semplice. Se includi il loro codice per la categoria NSData, puoi scrivere qualcosa del genere: (Nota: le printf()chiamate servono solo a dimostrare lo stato dei dati in vari punti - in un'applicazione reale, non avrebbe senso stampare tali valori .)

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

Dato questo codice e il fatto che i dati crittografati non si tradurranno sempre bene in un NSString, potrebbe essere più conveniente scrivere due metodi che racchiudono le funzionalità necessarie, in avanti e all'indietro ...

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

Questo sicuramente funziona su Snow Leopard e @Boz riferisce che CommonCrypto fa parte del sistema operativo principale su iPhone. Sia 10.4 che 10.5 hanno /usr/include/CommonCrypto, sebbene 10.5 abbia una pagina man per CCCryptor.3cce 10.4 no, quindi YMMV.


MODIFICA: vedere questa domanda di follow-up sull'utilizzo della codifica Base64 per rappresentare byte di dati crittografati come una stringa (se lo si desidera) utilizzando conversioni sicure e senza perdite.


1
Grazie. CommonCrypto fa parte del sistema operativo principale su iPhone e anche io sto eseguendo la 10.6.
Boz,

1
Ho fatto -1, perché il codice a cui si fa riferimento è pericolosamente insicuro. Guarda invece la risposta di Rob Napier. Il suo post sul blog " robnapier.net/aes-commoncrypto spiega esattamente perché questo è insicuro.
Erik Engheim,

1
Questa soluzione non funziona nel mio caso. Ho una stringa che voglio decodificare: U2FsdGVkX1 + MEhsbofUNj58m + 8tu9ifAKRiY / Zf8YIw = e ho la chiave: 3841b8485cd155d932a2d601b8cee2ec. Non riesco a decrittografare la stringa utilizzando la chiave con la tua soluzione. Grazie
George,

Questa soluzione non funziona in un'app Cocoa su El Capitan con XCode7. ARC vieta il autorelease.
Volomike

@QuinnTaylor Posso modificare questa risposta, ma volevo darti l'opportunità di cambiarla come meglio credi. Ho riparato il tuo codice qui . Inoltre, potresti voler sottolineare che senza quel codice adattato , non verrà compilato. Quindi, l'ho fatto funzionare su un'applicazione Cocoa su El Capitan con XCode7. Ora quello che sto cercando di fare è capire come Base64Encode / Base64Decode questi dati in modo che siano trasmissibili senza essere disturbati durante il transito, piuttosto che restituire dati grezzi.
Volomike

46

Ho messo insieme una raccolta di categorie per NSData e NSString che utilizza soluzioni trovate sul blog di Jeff LaMarche e alcuni suggerimenti di Quinn Taylor qui su Stack Overflow.

Utilizza le categorie per estendere NSData per fornire la crittografia AES256 e offre anche un'estensione di NSString per codificare in modo sicuro i dati crittografati BASE64 alle stringhe.

Ecco un esempio per mostrare l'utilizzo per la crittografia delle stringhe:

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

Ottieni il codice sorgente completo qui:

https://gist.github.com/838614

Grazie per tutti i suggerimenti utili!

-- Michael


NSString * key = @ "YourEncryptionKey"; // dovrebbe essere fornito da un utente Possiamo generare una chiave sicura casuale a 256 bit, invece di quella fornita dall'utente.
Pranav Jaiswal

Il collegamento Jeff LaMarche è interrotto
whyoz

35

@owlstead, in merito alla tua richiesta di "una variante crittograficamente sicura di una delle risposte fornite", consulta RNCryptor . È stato progettato per fare esattamente ciò che richiedi (ed è stato creato in risposta ai problemi con il codice elencato qui).

RNCryptor utilizza PBKDF2 con salt, fornisce un IV casuale e collega HMAC (anch'esso generato da PBKDF2 con il proprio salt. Supporta il funzionamento sincrono e asincrono.


Codice interessante, e probabilmente vale i punti. Qual è il conteggio delle iterazioni per PBKDF2 e su cosa calcoli l'HMAC? Presumo solo i dati crittografati? Non sono riuscito a trovarlo facilmente nella documentazione fornita.
Maarten Bodewes

Guarda "Best practice per la sicurezza" per i dettagli. Consiglio 10k iterazioni su iOS (~ 80 ms su un iPhone 4). E sì, crittografa-di-HMAC. Probabilmente stasera esaminerò la pagina "Formato dati" per assicurarmi che sia aggiornata alla v2.0 (i documenti principali sono aggiornati, ma non ricordo se ho rivisto la pagina del formato dati).
Rob Napier

Ah, sì, ho trovato il numero di giri nei documenti e ho guardato il codice. Vedo funzioni di pulizia e chiavi di crittografia e HMAC separate. Se il tempo lo permette cercherò di dare un'occhiata più approfondita domani. Quindi assegnerò i punti.
Maarten Bodewes

5
Crittografa in NSData e utilizza uno dei tanti codificatori Base64 per convertirlo in una stringa. Non è possibile crittografare da una stringa a una stringa senza un codificatore da dati a stringa.
Rob Napier

1
@Jack Su consiglio del mio avvocato (che ha descritto la mia mancanza di esperienza nella legge sulla conformità all'esportazione in termini estremamente colorati ...), non fornisco più consulenza sulla legge sulla conformità all'esportazione. Dovrai discutere con il tuo avvocato.
Rob Napier,

12

Ho aspettato un po 'su @QuinnTaylor per aggiornare la sua risposta, ma dato che non l'ha fatto, ecco la risposta un po' più chiaramente e in un modo che verrà caricata su XCode7 (e forse maggiore). L'ho usato in un'applicazione Cocoa, ma probabilmente funzionerà bene anche con un'applicazione iOS. Non ha errori ARC.

Incolla prima di qualsiasi sezione @implementation nel tuo file AppDelegate.m o 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

Incolla queste due funzioni nella classe @implementation che desideri. Nel mio caso, ho scelto @implementation AppDelegate nel mio file AppDelegate.mm o 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];
}

Nota: 1. Al momento della decrittografia, la dimensione dell'output sarà inferiore alla dimensione dell'input quando c'è il riempimento (PKCS # 7). Non vi è alcun motivo per aumentare il bufferSize, basta utilizzare la dimensione dei dati crittografati. 2. Invece di eseguire il mallocing di un buffer e quindi dataWithBytesNoCopyallocare semplicemente un NSMutableDatacon dataWithLengthe utilizzare la mutableBytesproprietà per il puntatore di byte e quindi ridimensionare semplicemente impostando la sua lengthproprietà. 3. L'uso diretto di una stringa per una crittografia è molto insicuro, è necessario utilizzare una chiave derivata come quella creata da PBKDF2.
Zaph

@zaph, puoi fare un pastebin / pastie da qualche parte in modo che io possa vedere le modifiche? A proposito, sul codice sopra, ho semplicemente adattato il codice che ho visto da Quinn Taylor per farlo funzionare. Sto ancora imparando questo lavoro mentre procedo e il tuo contributo mi sarà molto utile.
Volomike

Vedi questa risposta SO e ha anche una gestione degli errori minima e gestisce sia la crittografia che la decrittografia. Non è necessario estendere il buffer sulla decrittazione, è solo un codice inferiore che non si specializza con un ulteriore se quando c'è poco da guadagnare. Nel caso in cui si estende la chiave con i null è desiderato (che non dovrebbe essere fatto) solo creare una versione mutabile della chiave e impostare la lunghezza: keyData.length = kCCKeySizeAES256;.
Zaph

Vedi questa risposta SO per l'utilizzo di PBKDF2 per creare una chiave da una stringa.
Zaph

@Volomike Se lo uso, devo selezionare Esporta informazioni sulla conformità (SÌ) su iTunes-Connect?
Jack

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.