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


124

Чи може хтось вказати мені в правильному напрямку, щоб мати можливість шифрувати рядок, повертаючи ще одну рядок із зашифрованими даними? (Я намагався з шифруванням AES256.) Я хочу написати метод, який вимагає двох екземплярів NSString, один - повідомлення для шифрування, а інший - "пароль", щоб зашифрувати його - я підозрюю, що мені доведеться генерувати ключ шифрування із кодом доступу таким чином, що його можна змінити, якщо пароль надається із зашифрованими даними. Потім метод повинен повернути NSString, створений із зашифрованих даних.

Я спробував техніку, детально описану в першому коментарі до цього поста , але до цього часу мені не пощастило. У CryptoExercise Apple, безумовно, є щось, але я не можу зрозуміти це ... Я бачив безліч посилань на CCCrypt , але це не вдалося в кожному випадку, коли я його використовував.

Я також повинен мати можливість розшифрувати зашифрований рядок, але я сподіваюся, що це так просто, як kCCEncrypt / kCCDecrypt.


1
Зверніть увагу, що я дав нагороду за відповідь Роб Нап'є, який надав безпечну версію відповіді.
Maarten Bodewes

Відповіді:


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 є частиною основної ОС на iPhone, і я також працюю 10.6.
Боз

1
Я зробив -1, тому що згаданий код небезпечно небезпечний. Подивіться замість відповіді Роб Неп'єр. Запис у його щоденнику " robnapier.net/aes-commoncrypto детально пояснює, чому це небезпечно.
Ерік Енгхайм

1
У моєму випадку це рішення не працює. У мене є рядок, який я хочу розшифрувати: U2FsdGVkX1 + MEhsbofUNj58m + 8tu9ifAKRiY / Zf8YIw = і у мене є ключ: 3841b8485cd155d932a2d601b8cee2ec. Я не можу розшифрувати рядок, використовуючи ключ із вашим рішенням. Спасибі
Джордж

Це рішення не працює в додатку какао на El Capitan з XCode7. ARC забороняє autorelease.
Воломійка

@QuinnTaylor Я можу редагувати цю відповідь, але хотів надати вам можливість змінити її так, як вважаєте за потрібне. Я відремонтував тут ваш код . Також ви можете зазначити, що без цього адаптованого коду він не буде компілюватися. Отже, я змусив це працювати над додатком какао на El Capitan з XCode7. Тепер те, що я намагаюся зробити, - це зрозуміти, як Base64Encode / Base64Deкодувати ці дані, щоб вони передавались, не порушуючи їх під час транзиту, а не повертати необроблені дані.
Воломіке

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-бітний ключ замість одного, наданого користувачем.
Пранів Джайсваль

Посилання на Jeff LaMarche розірвано
Whyoz

35

@owlstead, щодо вашого запиту на "криптографічно захищений варіант однієї із заданих відповідей", будь ласка, дивіться RNCryptor . Він був розроблений для того, щоб робити саме те, що ви запитуєте (і був побудований у відповідь на проблеми з переліченим тут кодом).

RNCryptor використовує PBKDF2 з сіллю, забезпечує випадковий ІV та приєднує HMAC (також генерується з PBKDF2 з власною сіллю. Він підтримує синхронну та асинхронну роботу.


Цікавий код і, мабуть, вартий балів. Яка кількість ітерацій для PBKDF2 і над чим ви обчислюєте HMAC? Я припускаю лише зашифровані дані? Я не міг так легко знайти в наданій документації.
Maarten Bodewes

Докладні відомості див. У "Захисті кращих практик". Я рекомендую 10k ітерацій на iOS (~ 80 мс на iPhone 4). І так, шифруйте, ніж HMAC. Я, певно, сьогодні перегляну сторінку "Формат даних", щоб переконатися, що вона актуальна на v2.0 (основні документи оновлені, але я не можу пригадати, чи переглянув сторінку формату даних).
Роб Нап'єр

Ага, так, знайшов кількість раундів у документах і подивився код. Я бачу функції очищення та окремі HMAC та ключі шифрування. Якщо час дозволить, я спробую завтра глибше заглянути. Тоді я призначу бали.
Maarten Bodewes

5
Зашифруйте NSData та скористайтеся одним із багатьох кодерів Base64 для перетворення цього на рядок. Не існує способу шифрування з рядка в рядок без датчика передачі даних у рядок.
Роб Нап'єр

1
@Jack За порадами мого юриста (який описав мою відсутність знань у законі про дотримання норм експорту в надзвичайно барвистих термінах…), я більше не даю порад щодо закону щодо дотримання експорту. Вам потрібно буде обговорити зі своїм юристом.
Роб Нап’єр

12

Я трохи зачекав на @QuinnTaylor, щоб оновити свою відповідь, але оскільки він цього не зробив, ось відповідь трохи чіткіше і таким чином, що він завантажиться на XCode7 (а можливо і більше). Я використовував це в додатку какао, але це, ймовірно, буде добре працювати з додатком для iOS. Не має помилок ARC.

Вставте перед будь-яким розділом @ виконання, у вашому файлі 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];
}

Примітка: 1. При розшифровці розмір виводу буде меншим, ніж розмір вхідного сигналу, коли є набивання (PKCS № 7). Немає причини збільшувати розмір bufferSize, просто використовуйте зашифрований розмір даних. 2. Замість того, щоб розміщувати буфер, а потім dataWithBytesNoCopyпросто виділити властивість NSMutableDataз dataWithLengthі використовувати mutableBytesвластивість для вказівника байта, а потім просто змінити розмір, встановивши його lengthвластивість. 3. Використання рядка безпосередньо для шифрування дуже небезпечно, слід використовувати похідний ключ, такий як створений PBKDF2.
zaph

@zaph, чи можна десь зробити пастин / пасти, щоб я міг побачити зміни? До речі, у наведеному вище коді я просто адаптував код, який я бачив від Квінн Тейлор, щоб змусити його працювати. Я все ще навчаюся цій справі, коли я йду, і ваш внесок буде дуже корисним для мене.
Воломіке

Подивіться на цю відповідь, і він навіть має мінімальну обробку помилок і обробляє як шифрування, так і дешифрування. Немає необхідності розміщувати буфер на дешифруванні, це лише менше коду, який не спеціалізується на додатковому, якщо мало що можна отримати. У разі , тягнеться ключ з нулями бажано (що не повинно бути зроблено) просто створити змінну версію ключа і встановити довжину: keyData.length = kCCKeySizeAES256;.
zaph

Дивіться цю відповідь ТА щодо використання PBKDF2 для створення ключа з рядка.
zaph

@Volomike Якщо я використовую це, то я повинен вибрати інформацію про відповідність експорту (ТАК) на iTunes-Connect?
Джек

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.