Усеченный расшифрованный текст AES128 на iOS 7, без проблем на iOS 8

Используя зашифрованный текст, зашифрованный с помощью AES128 с использованием режима ECB (это игрушечное шифрование) и заполнения PKCS7, следующий блок кода приводит к полному открытому тексту, восстанавливаемому в iOS 8.

Выполнение того же блока кода под iOS 7 приводит к правильному открытому тексту, но усеченному. Почему это?

#import "NSData+AESCrypt.h" // <-- a category with the below function
#import <CommonCrypto/CommonCryptor.h>

- (NSData *)AES128Operation:(CCOperation)operation key:(NSString *)key iv:(NSString *)iv
{
    char keyPtr[kCCKeySizeAES128 + 1];
    bzero(keyPtr, sizeof(keyPtr));
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    char ivPtr[kCCBlockSizeAES128 + 1];
    bzero(ivPtr, sizeof(ivPtr));
    if (iv) {
        [iv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding];
    }

    NSUInteger dataLength = [self length];                      
    size_t bufferSize = dataLength + kCCBlockSizeAES128;        
    void *buffer = malloc(bufferSize);

    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(operation,
                                          kCCAlgorithmAES128,
                                          kCCOptionPKCS7Padding | kCCOptionECBMode,
                                          keyPtr,               
                                          kCCBlockSizeAES128,   
                                          ivPtr,                
                                          [self bytes],
                                          dataLength,           
                                          buffer,
                                          bufferSize,           
                                          &numBytesEncrypted);  
    if (cryptStatus == kCCSuccess) {
        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
    }
    free(buffer);
    return nil;
}

Ниже я добавил автономный тестовый комплект с результатами.

Тестовая система:

NSString *key = @"1234567890ABCDEF";
NSString *ciphertext = @"I9JIk5BskZMZKJFB/EAs+N2AYzkVR15DoBbUL7cBydBkWGlujVnzRHvBNvSVbcKh";

NSData *encData = [[NSData alloc]initWithBase64EncodedString:ciphertext options:0];
NSData *plainData = [encData AES128Operation:kCCDecrypt key:key iv:nil];

NSString *plaintext = [NSString stringWithUTF8String:[plainData bytes]];

DLog(@"key: %@\nciphertext: %@\nplaintext: %@", key, ciphertext, plaintext);

Результаты для iOS 8:

key: 1234567890ABCDEF
ciphertext: I9JIk5BskZMZKJFB/EAs+N2AYzkVR15DoBbUL7cBydBkWGlujVnzRHvBNvSVbcKh
plaintext: the quick brown fox jumped over the fence

Результаты для iOS 7:

key: 1234567890ABCDEF
ciphertext: I9JIk5BskZMZKJFB/EAs+N2AYzkVR15DoBbUL7cBydBkWGlujVnzRHvBNvSVbcKh
plaintext: the quick brown fox jumped over 0

и последующие результаты:

plaintext: the quick brown fox jumped over 
plaintext: the quick brown fox jumped over *

Обновление: загадка: когда я меняюсь

kCCOptionPKCS7Заполнение | kCCOptionECBMode ⇒ kCCOptionECBMode

результаты в iOS 7 соответствуют ожиданиям. Почему это?? Я знаю, что количество байтов выровнено по блокам, потому что зашифрованный текст дополнен дополнением PKCS7, так что это имеет смысл, но почему установка kCCOptionPKCS7Padding | kCCOptionECBMode вызывает усеченное поведение только в iOS 7?


Редактировать. Приведенный выше тестовый зашифрованный текст был сгенерирован как с этого веб-сайта, так и независимо с использованием PHP. mcrypt с ручным заполнением PKCS7 в следующей функции:

function encryptAES128WithPKCS7($message, $key)
{
    if (mb_strlen($key, '8bit') !== 16) {
        throw new Exception("Needs a 128-bit key!");
    }

    // Add PKCS7 Padding
    $block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128);
    $pad = $block - (mb_strlen($message, '8bit') % $block);
    $message .= str_repeat(chr($pad), $pad);

    $ciphertext = mcrypt_encrypt(
        MCRYPT_RIJNDAEL_128,
        $key,
        $message,
        MCRYPT_MODE_ECB
    );

    return $ciphertext;
}

// Demonstration encryption
echo base64_encode(encryptAES128WithPKCS7("the quick brown fox jumped over the fence", "1234567890ABCDEF"));

Вне:

I9JIk5BskZMZKJFB/EAs+N2AYzkVR15DoBbUL7cBydBkWGlujVnzRHvBNvSVbcKh


Обновление. Правильно дополненный PKCS#7 зашифрованный текст будет

I9JIk5BskZMZKJFB/EAs+N2AYzkVR15DoBbUL7cBydA6aE5a3JrRst9Gn3sb3heC

Вот почему этого не было.


person Drakes    schedule 06.08.2015    source источник
comment
И входные данные одинаковы на обеих версиях?   -  person trojanfoe    schedule 06.08.2015
comment
@trojanfoe Абсолютно. Я использую XCode 6 и запускаю одно и то же приложение в двух вышеупомянутых симуляторах iOS. Я просматриваю код и подтверждаю, что входные данные и ключи идентичны вплоть до заполнения PKCS7. Код не изменяется между запусками симулятора.   -  person Drakes    schedule 06.08.2015
comment
@trojanfoe Я добавил к своему вопросу тестовую программу, которая воспроизводит результаты, которые я описываю. Что может вызвать различное поведение в режиме ECB между iOS 7 и iOS 8?   -  person Drakes    schedule 06.08.2015
comment
В вызове CCCrypt вы указываете размер ключа с помощью определения: kCCBlockSizeAES128, но это неверно, нужно указывать размер ключа, а не размер блока. Используйте kCCKeySizeAES128.   -  person zaph    schedule 06.08.2015
comment
Кстати, использование и расширение затрудняют тестирование.   -  person zaph    schedule 06.08.2015
comment
@zaph Спасибо за помощь. Я проверил, но kCCKeySizeAES128 = 16 и kCCBlockSizeAES128 = 16 в CommonCryptor.h   -  person Drakes    schedule 06.08.2015
comment
Да, но вы должны использовать правильное определение, на этот раз вам просто повезло.   -  person zaph    schedule 06.08.2015
comment
@zaph Я ценю это. Я предложу патч для категории. FWIW, это не моя категория, чтобы поддерживать. Мне поручено выявить несоответствие в этом вопросе.   -  person Drakes    schedule 06.08.2015
comment
Где/как выполняется шифрование? На другой платформе/языке/библиотеке?   -  person zaph    schedule 06.08.2015
comment
@zaph Зашифрованный текст в обвязке был сгенерирован одинаково как с помощью mcrypt PHP с ручным заполнением PKCS7, так и с помощью aesencryption.net для сравнения.   -  person Drakes    schedule 06.08.2015
comment
Это все объясняет, смотрите мой ответ.   -  person zaph    schedule 06.08.2015


Ответы (3)


Данные шифруются не с заполнением PKCS#7, а с нулевым заполнением. Вы можете узнать это, записав plainData:

NSData *fullData = [NSData dataWithBytes:buffer length:dataLength];
NSLog(@"\nfullData: %@", fullData);

Вывод:
plainData: 74686520 71756963 6b206272 6f776e20 666f7820 6a756d70 6564206f 76657220 74686520 66656e63 65000000 00000000

Метод PHP mcrypt делает это, он нестандартен.

mcrypt(), в то время как популярная была написана некоторыми бозо и использует нестандартное заполнение нулями, которое одновременно небезопасно и не будет работать, если последний байт данных равен 0x00.

Ранние версии CCCrypt возвращали ошибку, если заполнение было явно неверным, это была ошибка безопасности, которая позже была исправлена. IIRC iOS7 была последней версией, которая сообщала о неправильном заполнении как об ошибке.

Решение состоит в том, чтобы добавить заполнение PKCS # 7 перед шифрованием:

Заполнение PKCS#7 всегда добавляет заполнение. Заполнение представляет собой серию байтов со значением количества добавленных байтов заполнения. Длина заполнения равна block_size - (length(data) % block_size.

Для AES, где блок имеет размер 16 байт (и надеюсь, что php действителен, это было давно):

$pad_count = 16 - (strlen($data) % 16);
$data .= str_repeat(chr($pad_count), $pad_count);

Или удалите конечные байты 0x00 после расшифровки.

См. PKCS7.

Ранние версии CCCrypt возвращали ошибку, если заполнение было явно неверным, это была ошибка безопасности, которая позже была исправлена. Это несколько раз обсуждалось на форумах Apple, Куинн участвовал во многих дискуссиях. Но это недостаток безопасности, поэтому проверка четности была удалена, и несколько разработчиков были расстроены/враждебны. Теперь, если есть неправильная четность, об ошибке не сообщается.

person zaph    schedule 06.08.2015
comment
Вы абсолютно правы насчет mcrypt. Я провел день, исследуя слабые места mcrypt, и мне запомнилась фраза, написанная какими-то придурками. ;) Пожалуйста, посмотрите мой обновленный вопрос, который включает заполнение PKCS # 7, примененное до вызова mcrypt_encrypt. Возможно, у него есть недостаток? - person Drakes; 06.08.2015
comment
Да, ваше последнее редактирование использует ручное заполнение PKCS7, очень похожее на то, которое в настоящее время используется для создания тестовых данных. - person Drakes; 06.08.2015
comment
Спасибо за помощь. Я следил за вашими предыдущими сообщениями на похожие темы. Мне здесь поручено определить, почему это проклятое шифрование ECB возвращает результаты, указанные в моем посте в iOS7, и если в iOS 7 действительно есть ошибка заполнения, мне нужно будет передать ссылку на этот эффект... что бы быть достаточно хорошим для этого вопроса в это время. - person Drakes; 06.08.2015
comment
Ясно, что тестовые данные не были дополнены PKCS#7, потому что они содержат пустые байты заполнения. PKCS#7 проходит с байтами, равными длине заполнения. fullData будет: 74686520 71756963 6b206272 6f776e20 666f7820 6a756d70 6564206f 76657220 74686520 66656e63 65070707 07070707 - person zaph; 06.08.2015
comment
Совершенно верно. Из всех глупостей, которые мне давали и уверяли, эта, должно быть, самая глупая. Вы заслуживаете похвалы за то, что глубже погрузились в простые данные, чтобы продемонстрировать, что на самом деле это не PKCS # 7, дополненный, когда это было гарантировано. Мне нужно написать, где лежит ошибка кода. - person Drakes; 06.08.2015
comment
IIRC iOS7 была последней версией, которая сообщала о неправильном заполнении как об ошибке. Мне понадобится ваша лучшая ссылка на это, потому что ни одна версия iOS не сообщила об ошибке, просто счастливое усечение в iOS7. Это должно быть официально процитировано для будущей работы. - person Drakes; 06.08.2015
comment
Все, что нужно указать, это убедиться, что заполнение PKCS#7 добавляется к данным до mcrypt. В противном случае зайдите на форумы Apple и спросите об этом, Куинн хорошо осведомлен о проблеме с ошибкой заполнения. - person zaph; 06.08.2015
comment
Пожалуйста, поддержите меня и добавьте то, что вы написали в комментариях о четности и проблемах безопасности в iOS 7 и iOS 8... так как это заголовок вопроса, который будут искать будущие посетители SO - он должен немного отличаться от ответ из похожего ответа, который я наткнулся ;) Хорошая работа снова. - person Drakes; 06.08.2015

Я не могу воспроизвести вашу проблему со следующим кодом:

@implementation ViewController
{
    NSData *_data;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSLog(@"system version: %@", [[UIDevice currentDevice] systemVersion]);

    NSMutableString *text = [NSMutableString string];
    for (int i = 0; i < 80; i+=4)
    {
        [text appendFormat:@"ABCD"];
    }
    _data = [text dataUsingEncoding:NSUTF8StringEncoding];

    NSString *key = @"password";
    NSString *iv = @"12345678";
    NSData *encrypted = [self AES128Operation:kCCEncrypt key:key iv:iv];
    NSLog(@"encrypted: %@", encrypted);

    _data = encrypted;
    NSData *decrypted = [self AES128Operation:kCCDecrypt key:key iv:iv];
    NSLog(@"decrypted: %@ (%@)", decrypted, [[NSString alloc] initWithData:decrypted encoding:NSUTF8StringEncoding]);
}

- (NSData *)AES128Operation:(CCOperation)operation key:(NSString *)key iv:(NSString *)iv
{
    char keyPtr[kCCKeySizeAES128 + 1];
    bzero(keyPtr, sizeof(keyPtr));
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    char ivPtr[kCCBlockSizeAES128 + 1];
    bzero(ivPtr, sizeof(ivPtr));
    if (iv) {
        [iv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding];
    }

    NSUInteger dataLength = [_data length];
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);

    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(operation,
                                          kCCAlgorithmAES128,
                                          kCCOptionPKCS7Padding | kCCOptionECBMode,
                                          keyPtr,
                                          kCCBlockSizeAES128,
                                          ivPtr,
                                          [_data bytes],
                                          dataLength,
                                          buffer,
                                          bufferSize,
                                          &numBytesEncrypted);
    if (cryptStatus == kCCSuccess) {
        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
    }
    free(buffer);
    return nil;
}

Вот результаты:

2015-08-06 12:39:29.716 Test[37788:13220246] system version: 8.4
2015-08-06 12:39:29.717 Test[37788:13220246] encrypted: <17445b45 da8b6f93 7787e80a 3feb6948 17445b45 da8b6f93 7787e80a 3feb6948 17445b45 da8b6f93 7787e80a 3feb6948 17445b45 da8b6f93 7787e80a 3feb6948 17445b45 da8b6f93 7787e80a 3feb6948 c6b4234e 1d0709c9 45113e4f 2a9607f7>
2015-08-06 12:39:29.717 Test[37788:13220246] decrypted: <41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344> (ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD)


2015-08-06 13:39:50.270 Test[37841:607] system version: 7.1
2015-08-06 13:39:50.272 Test[37841:607] encrypted: <17445b45 da8b6f93 7787e80a 3feb6948 17445b45 da8b6f93 7787e80a 3feb6948 17445b45 da8b6f93 7787e80a 3feb6948 17445b45 da8b6f93 7787e80a 3feb6948 17445b45 da8b6f93 7787e80a 3feb6948 c6b4234e 1d0709c9 45113e4f 2a9607f7>
2015-08-06 13:39:50.273 Test[37841:607] decrypted: <41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344 41424344> (ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD)

Также из документации:

Вектор инициализации, необязательный. Используется для режима цепочки блоков шифрования (CBC). Если присутствует, то должен быть такой же длины, как и размер блока выбранного алгоритма. Если выбран режим CBC (из-за отсутствия каких-либо битов режима в флагах опций) и IV не присутствует, IV будет использоваться NULL (все нули). Это игнорируется, если используется режим ECB или если выбран алгоритм потокового шифрования.

Таким образом, IV бесполезен в режиме ECB.

person John Tracid    schedule 06.08.2015
comment
Я добавил к моему вопросу тестовую программу, которая воспроизводит результаты, которые я описываю. Кроме того, я не использую режим CBC, а iv равен нулю и игнорируется в режиме ECB. Чем может быть вызвано различное поведение в режиме ECB между iOS 7 и iOS 8? - person Drakes; 06.08.2015
comment
Режим ECB не использует iv. Если вам нужна хорошая и бесплатная книга, приобретите Руководство по прикладной криптографии. , это бесплатная загрузка. - person zaph; 06.08.2015
comment
@Drakes, стоит упомянуть, что вы используете шифрование из другого приложения. - person John Tracid; 06.08.2015

Правильно оказывается, что в ручном дополнении PKCS#7 есть небольшой недостаток подпрограмма, примененная до PHP mcrypt.

хорошо известно, что mcrypt использует нулевое заполнение, поэтому чтобы сделать его совместимым с PKCS7, mcrypt потребовалось вручную выровнять данные по байтам, добавив n количество байтов, содержащих значение n в каждом из них. В случае этого вопроса это было сделано с помощью этого драгоценного кода:

// Add PKCS#7 Padding
$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128);
$pad = $block - (mb_strlen($message, '8bit') % $block);
$message .= str_repeat(chr($pad), $pad);

$ciphertext = mcrypt_encrypt(
    MCRYPT_RIJNDAEL_128,
    $key,
    $message,
    MCRYPT_MODE_ECB
);

Мы поступаем правильно, не так ли? Мне нужно было сосредоточиться на проблеме iOS 7/8. Однако оказывается

$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128); // null

ничего не делает при компоновке с более новыми версиями libmcrypt >= 2.4 (ref), но

$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, 'ecb'); // 16

правильно возвращает размер блока. По сути, заполнение не применяется, и заполнение возвращается к нулевому заполнению а-ля mcrypt. Кредит принадлежит zaph для демонстрации

Ясно, что тестовые данные не были дополнены PKCS#7, потому что они содержат пустые байты заполнения. PKCS#7 проходит с байтами, равными длине заполнения. fullData будет: 74686520 71756963 6b206272 6f776e20 666f7820 6a756d70 6564206f 76657220 74686520 66656e63 65070707 07070707

Теперь было упомянуто, что iOS 7 обрабатывает «плохое заполнение» иначе, чем iOS 8. Мне абсолютно нужна ссылка на это, но эти две ошибки вместе объясняют поведение, наблюдаемое в OP.

person Drakes    schedule 06.08.2015
comment
Ранние версии CCCrypt возвращали ошибку, если заполнение было явно неверным, это была ошибка безопасности, которая позже была исправлена. IIRC iOS7 была последней версией, которая сообщала о неправильном заполнении как об ошибке. Это несколько раз обсуждалось на форумах Apple, Куинн участвовал во многих дискуссиях. Но это недостаток безопасности, поэтому проверка четности была удалена, и несколько разработчиков были расстроены/враждебны. Теперь, если есть неправильная четность, об ошибке не сообщается. - person zaph; 06.08.2015
comment
@zaph Достаточно хорошо. Пожалуйста, укажите это в своем ответе, так как это решает проблему iOS 7, и я приму это. Большое СПАСИБО за то, что вы остались на этом, и за сброс NSData. Хорошая работа. - person Drakes; 06.08.2015