RNCryptor AES256 для соответствия PHP MCRYPT_RIJNDAEL_256

PHP API, который я вызываю из своего приложения для iOS, требует, чтобы полезная нагрузка была зашифрована определенным образом. У меня проблемы с воспроизведением этого подхода в Objective-C с помощью RNCryptor.

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

function encrypt($string) {
    $key = 'some-random-key';
    return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($key), $string, MCRYPT_MODE_CBC, md5(md5($key))));
}

И вот как я пытаюсь добиться того же результата шифрования в Objective-C:

+ (NSData*)encryptData:(NSData*)sourceData {

    NSString *keyString = @"some-random-key";
    NSData *key = [[keyString MD5String] dataUsingEncoding:NSUTF8StringEncoding];
    NSData *iv = [[[keyString MD5String] MD5String] dataUsingEncoding:NSUTF8StringEncoding];

    NSMutableData *encryptedData = [NSMutableData data];

    RNCryptorEngine *cryptor = [[RNCryptorEngine alloc] initWithOperation:kCCEncrypt settings:kRNCryptorAES256Settings key:key IV:iv error:nil];

    [encryptedData appendData:[cryptor addData:sourceData error:nil]];
    [encryptedData appendData:[cryptor finishWithError:nil]];

    return encryptedData;

}

Но результаты двух функций никогда не совпадают. Например, для той же строки из одного слова PHP-код возвращает J39gRcuBEaqMIPP1VlizdA8tRjmyAB6za4zG5wcOB/8=, а в Objective-C (после выполнения base64EncodedStringWithOptions: для полученного NSData) я получаю 1FGpZpVm2p4z3BBY6KW2fw==.

Есть ли что-то, что мне нужно изменить в настройках RNCryptor, чтобы заставить его работать?

ОБНОВЛЕНИЕ

Я экспериментировал с нативным фреймворком CommonCrypto для iOS напрямую, не используя стороннюю библиотеку RNCryptor. Однако я постоянно получаю тот же результат, что и с RNCryptor. Я даже пытался реализовать AES128 как в своих фрагментах кода Objective-C, так и в PHP, но даже это никогда не приводило к совпадению результатов в двух средах…

ОБНОВЛЕНИЕ 2

Метод MD5String, который я использую, является категорией в NSString и определяется следующим образом:

- (NSString *)MD5String {
    const char *cstr = [self UTF8String];
    unsigned char result[16];
    CC_MD5(cstr, strlen(cstr), result);

    return [[NSString stringWithFormat:
            @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
            result[0], result[1], result[2], result[3],
            result[4], result[5], result[6], result[7],
            result[8], result[9], result[10], result[11],
            result[12], result[13], result[14], result[15]
            ] lowercaseString];
}

person Arnold    schedule 04.11.2013    source источник
comment
Что такое метод MD5String? Какого типа аргумент и тип возвращаемого значения?   -  person zaph    schedule 05.11.2013


Ответы (4)


Хотя большинство ответов сосредоточены на хэше MD5, проблемы, скорее всего, вызовет тот факт, что MCRYPT_RIJNDAEL_256 не AES. Он не указывает размер ключа 256, а размер блока 256, в то время как AES всегда имеет размер блока 128 бит. Что касается других параметров, распечатайте их значения в шестнадцатеричном формате прямо перед процедурой шифрования с обеих сторон, чтобы узнать их значения.

person Maarten Bodewes    schedule 05.11.2013

Я понимаю, что опаздываю на вечеринку, а ты, наверное, уже ушел. Но я хочу отметить, что теперь есть полный PHP-порт RNCryptor, совместимый с потоком байтов Реализация Objective-C. Я сам внес его прошлым летом, и в настоящее время я поддерживаю его. :-)

Таким образом, теперь должно быть довольно легко шифровать с помощью RNCryptor PHP и расшифровывать с помощью RNCryptor Objective-C или наоборот. Ознакомьтесь с основным проектом RNCryptor, чтобы узнать подробности и список подпроектов.

person curtisdf    schedule 24.01.2014

Необычно возвращать md5 в виде шестнадцатеричного ASCII в языках, отличных от языков сценариев, даже php предоставляет возможность двоичного вывода.

В php mcrypt_encrypt есть несколько нестандартных вещей:

  1. Дополнение ключа \0, если он меньше требуемого размера ключа.
  2. Данные будут дополнены символами \0 до кратного размера блока, обычно используется дополнение, такое как pkcs7.
  3. Не указано, как обрабатывается iv, который не является размером блока, можно предположить, что он также дополняется конечными символами \0.

Из них iv будет правильной длины из-за шестнадцатеричного ASCII вывода метода md5. Но это оставляет длину данных и нестандартное заполнение.

Ключ будет иметь размер 32 байта, поэтому в php он будет дополнен 32 символами \0. Напрашивается вопрос об использовании AES256 со 128-битным ключом.

Из базового CommonCrypto «CommonCryptor.h»:

keyLength: длина материала ключа. Должен соответствовать выбранной операции и алгоритму.

Длина ключа неправильная.

Что нужно сделать:
1. Обработать длину данных/заполнение
2. Обработать длину ключа

Для получения дополнительной помощи предоставьте образцы данных, которые вы используете, и вывод mcrypt_encrypt в шестнадцатеричном формате ASCII до кодирования base64.

Для справки см.:
mcrypt-encrypt.php и md5.php

person zaph    schedule 05.11.2013

Примечание Зафа, что

Данные будут дополнены символами \0 до кратного размера блока.

поставил меня на правильный путь и помог мне понять часть проблемы.

По сути, mcrypt в PHP использует только заполнение \0 для данных, в то время как CommonCryptor от Apple позволяет выбирать между дополнением PKCS7 (kCCOptionPKCS7Padding) или вообще без заполнения. Это было одной из причин, по которой я никогда не мог добиться совпадения данных: они всегда дополнялись по-разному, прежде чем их собирались зашифровать.

Решение для этого состоит в том, чтобы либо заставить PHP выполнять заполнение данных PKCS7 перед запуском mcrypt (пример решения) или заставить Objective-C выполнить заполнение \0 в стиле PHP и обязательно удалить kCCOptionPKCS7Padding (передайте NULL в параметры CCCrypt):

NSMutableData *dataToEncrypt = [sourceData mutableCopy];
NSUInteger dataLength = [dataToEncrypt length];

// See how much padding is required
NSUInteger padding = kCCBlockSizeAES128 - (dataLength % kCCBlockSizeAES128);

// Add that many \0’s (there could be a more efficient way to do this)
for (int i=0; i<padding; i++)
    [dataToEncrypt appendData:[@"\0" dataUsingEncoding:NSASCIIStringEncoding]];

// Recalculate the data length
dataLength = dataToEncrypt.length;

В итоге я отказался от RNCryptor и вместо этого работал с собственным API CommonCryptor напрямую, поэтому конечный результат выглядел примерно так (метод encryptedBase64String здесь является категорией NSString в моем приложении, потому что мне нужно только шифровать строки таким образом; также обратите внимание константа kHSEncryptionKey, представляющая строку ключа произвольной формы):

- (NSString*)encryptedBase64String {

    // Prepare the data

    NSMutableData *sourceData = [[self dataUsingEncoding:NSUTF8StringEncoding] mutableCopy];

    // Process the key

    NSString *key = [[kHSEncryptionKey MD5String] substringWithRange:NSMakeRange(0, 16)];

    char keyPtr[kCCKeySizeAES128 + 1];
    bzero(keyPtr, sizeof(keyPtr));
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    // Process the iv

    NSString *iv = [[[kHSEncryptionKey MD5String] MD5String] substringWithRange:NSMakeRange(0, 16)];

    char ivPtr[kCCKeySizeAES128 + 1];
    bzero(ivPtr, sizeof(ivPtr));
    [iv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding];

    // Pad the data, PHP style

    NSUInteger dataLength = [sourceData length];
    NSUInteger padding = kCCBlockSizeAES128 - (dataLength % kCCBlockSizeAES128);

    for (int i=0; i<padding; i++)
        [sourceData appendData:[@"\0" dataUsingEncoding:NSASCIIStringEncoding]];

    dataLength = sourceData.length;

    // Buffer for the resulting data

    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void* buffer = malloc(bufferSize);

    // Run the encryption

    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, NULL,
                                          keyPtr, kCCKeySizeAES128,
                                          ivPtr,
                                          sourceData.bytes, dataLength, /* input */
                                          buffer, bufferSize, /* output */
                                          &numBytesEncrypted);

    if (cryptStatus == kCCSuccess) {
        NSData *encyptedData = [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
        return [encyptedData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
    }

    free(buffer);
    return nil;
}
person Arnold    schedule 06.11.2013