Шифрование AES для NSString на iPhone

124

Может ли кто-нибудь указать мне в правильном направлении, чтобы иметь возможность зашифровать строку, возвращая другую строку с зашифрованными данными? (Я пробовал использовать шифрование AES256.) Я хочу написать метод, который принимает два экземпляра NSString, один из которых представляет собой сообщение для шифрования, а другой - «код доступа» для его шифрования - я подозреваю, что мне придется сгенерировать ключ шифрования с паролем, который можно отменить, если код доступа предоставляется вместе с зашифрованными данными. Затем метод должен вернуть NSString, созданный из зашифрованных данных.

Я пробовал технику, описанную в первом комментарии к этому посту , но пока мне не повезло. В CryptoExercise от Apple определенно что-то есть, но я не могу понять этого ... Я видел много ссылок на CCCrypt , но он терпел неудачу во всех случаях, когда я его использовал.

Мне также нужно было бы расшифровать зашифрованную строку, но я надеюсь, что это так же просто, как kCCEncrypt / kCCDecrypt.

Боз
источник
1
Обратите внимание, что я назначил награду за ответ Роба Напьера, который предоставил безопасную версию ответа.
Маартен Бодевес,

Ответы:

126

Поскольку вы не разместили код, трудно точно определить, с какими проблемами вы столкнулись. Тем не менее, сообщение в блоге, на которое вы ссылаетесь, похоже, работает довольно прилично ... за исключением дополнительной запятой в каждом вызове, CCCrypt()вызывающем ошибки компиляции.

Более поздний комментарий к этому сообщению включает этот адаптированный код , который работает для меня и кажется немного более простым. Если вы включите их код для категории NSData, вы можете написать что-то вроде этого: (Примечание: printf()вызовы предназначены только для демонстрации состояния данных в различных точках - в реальном приложении не имеет смысла печатать такие значения .)

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

Учитывая этот код и тот факт, что зашифрованные данные не всегда хорошо переводятся в NSString, может быть удобнее написать два метода, которые обертывают нужную вам функциональность в прямом и обратном направлениях ...

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

Это определенно работает в Snow Leopard, и @Boz сообщает, что CommonCrypto является частью Core OS на iPhone. И 10.4, и 10.5 имеют /usr/include/CommonCrypto, хотя в 10.5 есть справочная страница, CCCryptor.3ccа в 10.4 нет, поэтому YMMV.


РЕДАКТИРОВАТЬ: См. Следующий вопрос об использовании кодировки Base64 для представления байтов зашифрованных данных в виде строки (при желании) с использованием безопасных преобразований без потерь.

Куинн Тейлор
источник
1
Спасибо. CommonCrypto является частью Core OS на iPhone, и я тоже использую 10.6.
Boz
1
Я сделал -1, потому что указанный код опасно небезопасен. Вместо этого посмотрите на ответ Роба Напьера. Его запись в блоге robnapier.net/aes-commoncrypto подробно описывает, почему это небезопасно.
Эрик Энгхейм
1
В моем случае это решение не работает. У меня есть строка, которую я хочу декодировать: U2FsdGVkX1 + MEhsbofUNj58m + 8tu9ifAKRiY / Zf8YIw = и у меня есть ключ: 3841b8485cd155d932a2d601b8cee2ec. Я не могу расшифровать строку с помощью ключа с вашим решением. Спасибо
Джордж
Это решение не работает в приложении Cocoa на El Capitan с XCode7. ARC запрещает autorelease.
Volomike
@QuinnTaylor Я могу отредактировать этот ответ, но хотел дать вам возможность изменить его по своему усмотрению. Я восстановил ваш код здесь . Кроме того, вы можете указать, что без адаптированного кода он не будет компилироваться. Итак, я заставил его работать над приложением Cocoa на El Capitan с XCode7. Теперь то, что я пытаюсь сделать, - это выяснить, как Base64Encode / Base64Decode эти данные, чтобы их можно было передавать без помех при передаче, а не возвращать необработанные данные.
Volomike
46

Я собрал коллекцию категорий для NSData и NSString, в которых используются решения, найденные в блоге Джеффа Ламарче, и некоторые подсказки Куинна Тейлора здесь, в Stack Overflow.

Он использует категории для расширения NSData для обеспечения шифрования AES256, а также предлагает расширение NSString для безопасного кодирования зашифрованных данных в строки с помощью BASE64.

Вот пример, показывающий использование для шифрования строк:

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

Получите полный исходный код здесь:

https://gist.github.com/838614

Спасибо за все полезные советы!

-- Майкл

Майкл Тиль
источник
NSString * key = @ "YourEncryptionKey"; // должен предоставляться пользователем. Можно ли сгенерировать случайный безопасный 256-битный ключ вместо ключа, предоставленного пользователем.
Пранав Джайсвал,
Ссылка Джеффа Ламарша не работает
почемуоз
35

@owlstead, относительно вашего запроса на «криптографически безопасный вариант одного из данных ответов», пожалуйста, обратитесь к RNCryptor . Он был разработан, чтобы делать именно то, что вы запрашиваете (и был построен в ответ на проблемы с кодом, перечисленным здесь).

RNCryptor использует PBKDF2 с солью, предоставляет случайный IV и присоединяет HMAC (также сгенерированный из PBKDF2 с его собственной солью. Он поддерживает синхронные и асинхронные операции.

Роб Напье
источник
Интересный код, который, вероятно, стоит того. Какое количество итераций для PBKDF2 и для чего вы рассчитываете HMAC? Я полагаю, это только зашифрованные данные? Я не мог легко найти это в предоставленной документации.
Маартен Бодевес,
См. Подробности в разделе «Передовая практика безопасности». Я рекомендую 10 тыс. Итераций на iOS (~ 80 мс на iPhone 4). И да, шифрование вместо HMAC. Я, вероятно, просмотрю страницу «Формат данных» сегодня вечером, чтобы убедиться, что она актуальна до версии 2.0 (основные документы актуальны, но я не могу вспомнить, редактировал ли я страницу формата данных).
Роб Нэпьер,
А, да, нашел в документации количество раундов и посмотрел код. Я вижу там функции очистки и отдельные HMAC и ключи шифрования. Если позволит время, завтра попробую взглянуть повнимательнее. Потом я выставлю баллы.
Маартен Бодевес,
5
Зашифруйте в NSData и используйте один из многих кодировщиков Base64, чтобы преобразовать его в строку. Невозможно зашифровать строку в строку без кодировщика данных в строку.
Роб Нэпьер,
1
@Jack По совету моего юриста (который очень красочно описал мою нехватку опыта в области экспортного законодательства ...) я больше не даю советов по экспортному законодательству. Вам нужно будет обсудить это со своим юристом.
Rob Napier
12

Я немного подождал, пока @QuinnTaylor обновит его ответ, но, поскольку он этого не сделал, вот ответ немного более четко и таким образом, чтобы он загрузился в XCode7 (и, возможно, больше). Я использовал это в приложении Какао, но он, вероятно, будет работать и с приложением iOS. Нет ошибок ARC.

Вставьте перед любым разделом @implementation в файле AppDelegate.m или 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

Вставьте эти две функции в нужный вам класс @implementation. В моем случае я выбрал @implementation AppDelegate в моем файле AppDelegate.mm или 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
источник
Примечание: 1. При расшифровке размер вывода будет меньше размера ввода, если есть заполнение (PKCS # 7). Нет причин увеличивать bufferSize, просто используйте размер зашифрованных данных. 2. Вместо malloc'ing буфера , а затем dataWithBytesNoCopyпросто выделить NSMutableDataс dataWithLengthи использовать mutableBytesсвойство для указателя байта , а затем просто изменить размер, установив это lengthсвойство. 3. Использование строки напрямую для шифрования очень небезопасно, следует использовать производный ключ, такой как созданный PBKDF2.
zaph
@zaph, можно где-нибудь вставить pastebin / pastie, чтобы я мог видеть изменения? Кстати, в приведенном выше коде я просто адаптировал код, который видел от Куинна Тейлора, чтобы он работал. Я все еще изучаю это дело по ходу дела, и ваш вклад будет мне очень полезен.
Volomike
См. Этот ответ SO, и он даже имеет минимальную обработку ошибок и обрабатывает как шифрование, так и дешифрование. Нет необходимости увеличивать буфер при расшифровке, это просто меньше кода, не специализирующегося на дополнительном, если мало что можно получить. В случае , простирающийся ключ с нулями желательно (что не должно быть сделано) просто создать изменяемую версию ключа и установить длину: keyData.length = kCCKeySizeAES256;.
zaph
См. Этот SO-ответ об использовании PBKDF2 для создания ключа из строки.
zaph
@Volomike Если я использую это, то должен ли я выбрать Экспорт информации о соответствии (ДА) в iTunes-Connect?
Джек