Cryptage AES pour une NSString sur l'iPhone

124

Quelqu'un peut-il me diriger dans la bonne direction pour pouvoir crypter une chaîne, en renvoyant une autre chaîne avec les données cryptées? (J'ai essayé avec le cryptage AES256.) Je veux écrire une méthode qui prend deux instances NSString, l'une étant le message à crypter et l'autre étant un `` mot de passe '' pour le crypter avec - je suppose que je devrais générer la clé de chiffrement avec le mot de passe, d'une manière qui peut être inversée si le mot de passe est fourni avec les données chiffrées. La méthode doit ensuite renvoyer une NSString créée à partir des données chiffrées.

J'ai essayé la technique détaillée dans le premier commentaire sur ce post , mais je n'ai pas eu de chance jusqu'à présent. CryptoExercise d'Apple a certainement quelque chose, mais je ne peux pas y comprendre ... J'ai vu beaucoup de références à CCCrypt , mais il a échoué dans tous les cas où je l'ai utilisé.

Je devrais également être capable de déchiffrer une chaîne cryptée, mais j'espère que c'est aussi simple que kCCEncrypt / kCCDecrypt.

Boz
la source
1
Veuillez noter que j'ai donné une prime à une réponse de Rob Napier qui a fourni une version sécurisée de la réponse.
Maarten Bodewes

Réponses:

126

Comme vous n'avez pas publié de code, il est difficile de savoir exactement quels problèmes vous rencontrez. Cependant, le billet de blog vers CCCrypt()lequel vous créez un lien semble fonctionner assez décemment ... mis à part la virgule supplémentaire dans chaque appel qui a causé des erreurs de compilation.

Un commentaire ultérieur sur cet article inclut ce code adapté , qui fonctionne pour moi et semble un peu plus simple. Si vous incluez leur code pour la catégorie NSData, vous pouvez écrire quelque chose comme ceci: (Remarque: les printf()appels ne servent qu'à démontrer l'état des données à différents points - dans une application réelle, il n'aurait pas de sens d'imprimer de telles valeurs .)

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

Compte tenu de ce code, et du fait que les données chiffrées ne se traduiront pas toujours bien en NSString, il peut être plus pratique d'écrire deux méthodes qui enveloppent la fonctionnalité dont vous avez besoin, en avant et en arrière ...

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

Cela fonctionne définitivement sur Snow Leopard, et @Boz rapporte que CommonCrypto fait partie du Core OS sur l'iPhone. Les versions 10.4 et 10.5 ont toutes les deux /usr/include/CommonCrypto, bien que 10.5 ait une page de manuel pour CCCryptor.3ccet 10.4 pas, donc YMMV.


EDIT: Voir cette question de suivi sur l'utilisation du codage Base64 pour représenter les octets de données chiffrées sous forme de chaîne (si vous le souhaitez) à l'aide de conversions sûres et sans perte.

Quinn Taylor
la source
1
Merci. CommonCrypto fait partie du Core OS sur l'iPhone, et j'utilise également 10.6.
Boz
1
J'ai fait -1, car le code référencé est dangereusement dangereux. Regardez plutôt la réponse de Rob Napier. Son article de blog " robnapier.net/aes-commoncrypto détaille exactement pourquoi cela n'est pas sécurisé.
Erik Engheim
1
Cette solution ne fonctionne pas dans mon cas. J'ai une chaîne que je veux décoder: U2FsdGVkX1 + MEhsbofUNj58m + 8tu9ifAKRiY / Zf8YIw = et j'ai la clé: 3841b8485cd155d932a2d601b8cee2ec. Je ne peux pas déchiffrer la chaîne à l'aide de la clé avec votre solution. Merci
George
Cette solution ne fonctionne pas dans une application Cocoa sur El Capitan avec XCode7. ARC interdit le autorelease.
Volomike
@QuinnTaylor Je peux modifier cette réponse, mais je voulais vous donner la possibilité de la modifier comme bon vous semble. J'ai réparé votre code ici . De plus, vous voudrez peut-être souligner que sans ce code adapté , il ne sera pas compilé. Donc, je l'ai fait fonctionner sur une application Cocoa sur El Capitan avec XCode7. Maintenant, ce que j'essaie de faire est de comprendre comment Base64Encode / Base64Decode ces données afin qu'elles soient transmissibles sans être dérangées en transit, plutôt que de renvoyer des données brutes.
Volomike
46

J'ai rassemblé une collection de catégories pour NSData et NSString qui utilise des solutions trouvées sur le blog de Jeff LaMarche et quelques conseils de Quinn Taylor ici sur Stack Overflow.

Il utilise des catégories pour étendre NSData afin de fournir un cryptage AES256 et offre également une extension de NSString aux données cryptées encodées en BASE64 en toute sécurité vers des chaînes.

Voici un exemple pour montrer l'utilisation du chiffrement des chaînes:

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

Obtenez le code source complet ici:

https://gist.github.com/838614

Merci pour tous les conseils utiles!

-- Michael

Michael Thiel
la source
NSString * key = @ "YourEncryptionKey"; // doit être fourni par un utilisateur Pouvons-nous générer une clé aléatoire sécurisée de 256 bits, au lieu d'une clé fournie par l'utilisateur.
Pranav Jaiswal
Le lien Jeff LaMarche est rompu
Whyoz
35

@owlstead, concernant votre demande de "variante cryptographiquement sécurisée de l'une des réponses données", veuillez consulter RNCryptor . Il a été conçu pour faire exactement ce que vous demandez (et a été construit en réponse aux problèmes avec le code répertorié ici).

RNCryptor utilise PBKDF2 avec sel, fournit un IV aléatoire et attache HMAC (également généré à partir de PBKDF2 avec son propre sel. Il prend en charge le fonctionnement synchrone et asynchrone.

Rob Napier
la source
Code intéressant, et vaut probablement les points. Quel est le nombre d'itérations pour le PBKDF2 et sur quoi calculez-vous le HMAC? Je présume uniquement les données cryptées? Je n'ai pas pu trouver cela facilement dans la documentation fournie.
Maarten Bodewes
Consultez «Meilleures pratiques de sécurité» pour plus de détails. Je recommande des itérations 10k sur iOS (~ 80ms sur un iPhone 4). Et oui, chiffrez-que-HMAC. Je vais probablement examiner la page "Format de données" ce soir pour m'assurer qu'elle est à jour sur la v2.0 (les principaux documents sont à jour, mais je ne me souviens pas si j'ai révisé la page de format de données).
Rob Napier
Ah, ouais, j'ai trouvé le nombre de tours dans la documentation et j'ai regardé le code. Je vois des fonctions de nettoyage et des clés HMAC et de chiffrement séparées. Si le temps le permet, j'essaierai de regarder de plus près demain. Ensuite, je vais attribuer les points.
Maarten Bodewes
5
Chiffrez en NSData et utilisez l'un des nombreux encodeurs Base64 pour le convertir en chaîne. Il n'y a aucun moyen de chiffrer d'une chaîne à une chaîne sans encodeur data-to-string.
Rob Napier
1
@Jack Sur les conseils de mon avocat (qui a décrit mon manque d'expertise en droit de la conformité à l'exportation en termes extrêmement colorés…), je ne donne plus de conseils sur le droit de la conformité à l'exportation. Vous devrez discuter avec votre avocat.
Rob Napier
12

J'ai attendu un peu sur @QuinnTaylor pour mettre à jour sa réponse, mais comme il ne l'a pas fait, voici la réponse un peu plus clairement et de manière à ce qu'elle se charge sur XCode7 (et peut-être plus). J'ai utilisé ceci dans une application Cocoa, mais cela fonctionnera probablement aussi bien avec une application iOS. N'a aucune erreur ARC.

Collez avant toute section @implementation dans votre fichier AppDelegate.m ou 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

Collez ces deux fonctions dans la classe @implementation de votre choix. Dans mon cas, j'ai choisi @implementation AppDelegate dans mon fichier AppDelegate.mm ou 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
la source
Remarque: 1. Lors du décryptage, la taille de sortie sera inférieure à la taille d'entrée en cas de remplissage (PKCS # 7). Il n'y a aucune raison d'augmenter le bufferSize, utilisez simplement la taille des données chiffrées. 2. Au lieu de malaxer un tampon, puis d' dataWithBytesNoCopyallouer simplement un NSMutableDatawith dataWithLengthet d'utiliser la mutableBytespropriété du pointeur d'octet, puis de redimensionner simplement en définissant sa lengthpropriété. 3. L'utilisation directe d'une chaîne pour un chiffrement n'est pas sûre, une clé dérivée doit être utilisée telle que créée par PBKDF2.
zaph
@zaph, pouvez-vous faire un pastebin / pastie quelque part pour que je puisse voir les changements? BTW, sur le code ci-dessus, j'ai simplement adapté le code que j'ai vu de Quinn Taylor afin de le faire fonctionner. J'apprends toujours ce métier au fur et à mesure, et votre contribution me sera très utile.
Volomike
Voir cette réponse SO et il a même une gestion minimale des erreurs et gère à la fois le cryptage et le décryptage. Il n'y a pas besoin d'étendre le buffer lors du décryptage, c'est juste moins de code non spécialisé avec un supplémentaire si quand il y a peu à gagner. Dans le cas extension de la clé avec nulls est souhaitée (qui ne devrait pas être fait) il suffit de créer une version mutable de la clé et définir la longueur: keyData.length = kCCKeySizeAES256;.
zaph
Voir cette réponse SO pour utiliser PBKDF2 pour créer une clé à partir d'une chaîne.
zaph
@Volomike Si j'utilise ceci, dois-je sélectionner Exporter les informations de conformité (OUI) sur iTunes-Connect?
Jack