Cifrado AES para un NSString en el iPhone

124

¿Alguien puede señalarme en la dirección correcta para poder cifrar una cadena, devolviendo otra cadena con los datos cifrados? (He estado intentando con el cifrado AES256). Quiero escribir un método que tome dos instancias de NSString, uno es el mensaje para cifrar y el otro es un 'código de acceso' para cifrarlo; sospecho que tendría que generar la clave de cifrado con el código de acceso, de manera que se pueda revertir si el código de acceso se suministra con los datos cifrados. El método debería devolver un NSString creado a partir de los datos cifrados.

He probado la técnica detallada en el primer comentario en esta publicación , pero hasta ahora no he tenido suerte. CryptoExercise de Apple ciertamente tiene algo, pero no puedo entenderlo ... He visto muchas referencias a CCCrypt , pero ha fallado en todos los casos en que lo he usado.

También tendría que poder descifrar una cadena encriptada, pero espero que sea tan simple como kCCEncrypt / kCCDecrypt.

Boz
fuente
1
Tenga en cuenta que he dado una recompensa por una respuesta de Rob Napier, que ha proporcionado una versión segura de la respuesta.
Maarten Bodewes

Respuestas:

126

Como no ha publicado ningún código, es difícil saber exactamente qué problemas está encontrando. Sin embargo, la publicación de blog a la que enlaza parece funcionar bastante bien ... aparte de la coma adicional en cada llamada a la CCCrypt()que causó errores de compilación.

Un comentario posterior en esa publicación incluye este código adaptado , que funciona para mí, y parece un poco más directo. Si incluye su código para la categoría NSData, puede escribir algo como esto: (Nota: Las printf()llamadas son solo para demostrar el estado de los datos en varios puntos; en una aplicación real, no tendría sentido imprimir tales valores .)

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

Dado este código, y el hecho de que los datos cifrados no siempre se traducirán bien en un NSString, puede ser más conveniente escribir dos métodos que envuelvan la funcionalidad que necesita, hacia adelante y hacia atrás ...

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

Esto definitivamente funciona en Snow Leopard, y @Boz informa que CommonCrypto es parte del Core OS en el iPhone. Tanto 10.4 como 10.5 tienen /usr/include/CommonCrypto, aunque 10.5 tiene una página de manual para CCCryptor.3ccy 10.4 no, así que YMMV.


EDITAR: consulte esta pregunta de seguimiento sobre el uso de la codificación Base64 para representar bytes de datos cifrados como una cadena (si lo desea) utilizando conversiones seguras y sin pérdidas.

Quinn Taylor
fuente
1
Gracias. CommonCrypto es parte del sistema operativo Core en el iPhone, y también estoy ejecutando 10.6.
Boz
1
Hice -1, porque el código referenciado es peligrosamente inseguro. Mire la respuesta de Rob Napier en su lugar. Su entrada de blog " robnapier.net/aes-commoncrypto detalla exactamente por qué esto es inseguro."
Erik Engheim
1
Esta solución no funciona en mi caso. Tengo una cadena que quiero decodificar: U2FsdGVkX1 + MEhsbofUNj58m + 8tu9ifAKRiY / Zf8YIw = y tengo la clave: 3841b8485cd155d932a2d601b8cee2ec. No puedo descifrar la cadena usando la clave con su solución. Gracias
George
Esta solución no funciona en una aplicación Cocoa en El Capitan con XCode7. ARC prohíbe el autorelease.
Volomike
@QuinnTaylor Puedo editar esta respuesta, pero quería darle la oportunidad de cambiarla como mejor le parezca. Reparé tu código aquí . Además, es posible que desee señalar que sin ese código adaptado , no se compilará. Entonces, lo hice funcionar en una aplicación de Cocoa en El Capitan con XCode7. Ahora lo que intento hacer es descubrir cómo Base64Encode / Base64Decode estos datos para que puedan transmitirse sin ser molestados en tránsito, en lugar de devolver datos sin procesar.
Volomike
46

He reunido una colección de categorías para NSData y NSString que utiliza soluciones que se encuentran en el blog de Jeff LaMarche y algunos consejos de Quinn Taylor aquí en Stack Overflow.

Utiliza categorías para extender NSData para proporcionar el cifrado AES256 y también ofrece una extensión de NSString a los datos cifrados con codificación BASE64 de forma segura en cadenas.

Aquí hay un ejemplo para mostrar el uso para cifrar cadenas:

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

Obtenga el código fuente completo aquí:

https://gist.github.com/838614

¡Gracias por todos los consejos útiles!

-- Miguel

Michael Thiel
fuente
NSString * key = @ "YourEncryptionKey"; // debe ser proporcionado por un usuario ¿Podemos generar una clave aleatoria segura de 256 bits, en lugar de una proporcionada por el usuario?
Pranav Jaiswal
El enlace Jeff LaMarche está roto
whyoz
35

@owlstead, con respecto a su solicitud de "una variante criptográficamente segura de una de las respuestas dadas", consulte RNCryptor . Fue diseñado para hacer exactamente lo que está solicitando (y fue creado en respuesta a los problemas con el código que se enumera aquí).

RNCryptor utiliza PBKDF2 con sal, proporciona un IV aleatorio y conecta HMAC (también generado a partir de PBKDF2 con su propia sal. Admite operaciones síncronas y asíncronas).

Rob Napier
fuente
Código interesante, y probablemente valga la pena. ¿Cuál es el recuento de iteraciones para el PBKDF2 y para qué calcula el HMAC? Presumo solo los datos cifrados? No pude encontrar eso fácilmente en la documentación proporcionada.
Maarten Bodewes
Consulte "Seguridad de mejores prácticas" para obtener detalles. Recomiendo 10k iteraciones en iOS (~ 80ms en un iPhone 4). Y sí, cifrar que HMAC. Probablemente revisaré la página "Formato de datos" esta noche para asegurarme de que esté actualizada en v2.0 (los documentos principales están actualizados, pero no recuerdo si revisé la página de formato de datos).
Rob Napier
Ah, sí, encontré el número de rondas en los documentos y busqué el código. Veo funciones de limpieza y HMAC y claves de cifrado separadas allí. Si el tiempo lo permite, intentaré echar un vistazo más profundo mañana. Entonces asignaré los puntos.
Maarten Bodewes
55
Cifre a NSData y use uno de los muchos codificadores Base64 para convertirlo en una cadena. No hay forma de cifrar de una cadena a una cadena sin un codificador de datos a cadena.
Rob Napier
1
@Jack Siguiendo el consejo de mi abogado (quien describió mi falta de experiencia en la ley de cumplimiento de exportaciones en términos extremadamente coloridos ...), ya no doy consejos sobre la ley de cumplimiento de exportaciones. Tendrá que hablar con su abogado.
Rob Napier
12

Esperé un poco en @QuinnTaylor para actualizar su respuesta, pero como no lo hizo, aquí está la respuesta un poco más clara y de una manera que se cargará en XCode7 (y tal vez mejor). Utilicé esto en una aplicación de Cocoa, pero es probable que también funcione bien con una aplicación de iOS. No tiene errores de ARC.

Pegue antes de cualquier sección @implementation en su archivo 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

Pegue estas dos funciones en la clase @implementation que desee. En mi caso, elegí @implementation AppDelegate en mi archivo 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];
}
Volomike
fuente
Nota: 1. Al descifrar, el tamaño de salida será menor que el tamaño de entrada cuando haya relleno (PKCS # 7). No hay razón para aumentar el tamaño del búfer, solo use el tamaño de los datos cifrados. 2. En lugar de asignar mal un búfer y luego dataWithBytesNoCopysimplemente asignar un NSMutableDatacon dataWithLengthy usar la mutableBytespropiedad para el puntero de bytes y luego cambiar el tamaño configurando su lengthpropiedad. 3. El uso de cadenas directamente para un cifrado es muy inseguro, se debe usar una clave derivada como la creada por PBKDF2.
zaph
@zaph, ¿puedes hacer un pastebin / pastie en alguna parte para que pueda ver los cambios? Por cierto, en el código anterior, simplemente adapté el código que vi de Quinn Taylor para que funcione. Todavía estoy aprendiendo este negocio a medida que avanzo, y su opinión será muy útil para mí.
Volomike
Vea esta respuesta SO e incluso tiene un manejo mínimo de errores y maneja tanto el cifrado como el descifrado. No hay necesidad de ampliar el búfer en el descifrado, es solo menos código que no se especializa con un adicional si no hay mucho que ganar. En caso que se extiende la tecla con nulos se deseadas (que no se debe hacer) acaba de crear una versión mutable de la llave y establecer la longitud: keyData.length = kCCKeySizeAES256;.
zaph
Vea esta respuesta SO para usar PBKDF2 para crear una clave a partir de una cadena.
zaph
@Volomike Si uso esto, ¿debería seleccionar Exportar información de cumplimiento (SÍ) en iTunes-Connect?
Jack