Шифрование данных в Какао, декодирование в PHP - PullRequest
2 голосов
/ 26 ноября 2008

Ситуация, которую я пытаюсь решить: в моем приложении Какао мне нужно зашифровать строку с помощью симметричного шифра, отправить ее в PHP и сделать так, чтобы этот скрипт декодировал данные. Процесс должен работать в обратном порядке для возврата ответа (PHP кодирует, Какао декодирует).

Я что-то упускаю, потому что хотя я могу получить одинаковые ключ и вектор инициализации (iv) как в PHP, так и в Какао, декодирование никогда не работает, когда одно приложение отправляет свои закодированные данные другому. Оба работают просто отлично, кодируя / декодируя свои собственные данные (проверено, чтобы убедиться, что не было какой-либо проблемы PEBKAC под рукой). У меня есть подозрение, что где-то есть проблема с заполнением, я просто не вижу этого.

Мое приложение для какао кодирует с помощью SSCrypto (который является просто удобной оберткой для функций OpenSSL). Шифр Blowfish, режим CBC. (простите за утечки памяти, код был убран до самого необходимого)

NSData *secretText = [@"secretTextToEncode" dataUsingEncoding:NSUTF8StringEncoding];
NSData *symmetricKey = [@"ThisIsMyKey" dataUsingEncoding:NSUTF8StringEncoding];

unsigned char *input = (unsigned char *)[secretText bytes];
unsigned char *outbuf;
int outlen, templen, inlen;
inlen = [secretText length];

unsigned char evp_key[EVP_MAX_KEY_LENGTH] = {"\0"};
int cipherMaxIVLength = EVP_MAX_IV_LENGTH;
EVP_CIPHER_CTX cCtx;
const EVP_CIPHER *cipher = EVP_bf_cbc();

cipherMaxIVLength = EVP_CIPHER_iv_length( cipher );
unsigned char iv[cipherMaxIVLength];

EVP_BytesToKey(cipher, EVP_md5(), NULL, [symmetricKey bytes], [symmetricKey length], 1, evp_key, iv);

NSData *initVector = [NSData dataWithBytes:iv length:cipherMaxIVLength];

EVP_CIPHER_CTX_init(&cCtx);

if (!EVP_EncryptInit_ex(&cCtx, cipher, NULL, evp_key, iv)) {
    EVP_CIPHER_CTX_cleanup(&cCtx);
    return nil;
}
int ctx_CipherKeyLength = EVP_CIPHER_CTX_key_length( &cCtx );
EVP_CIPHER_CTX_set_key_length(&cCtx, ctx_CipherKeyLength);

outbuf = (unsigned char *)calloc(inlen + EVP_CIPHER_CTX_block_size(&cCtx), sizeof(unsigned char));

if (!EVP_EncryptUpdate(&cCtx, outbuf, &outlen, input, inlen)){
    EVP_CIPHER_CTX_cleanup(&cCtx);
    return nil;
}
if (!EVP_EncryptFinal(&cCtx, outbuf + outlen, &templen)){
    EVP_CIPHER_CTX_cleanup(&cCtx);
    return nil;
}
outlen += templen;
EVP_CIPHER_CTX_cleanup(&cCtx);

NSData *cipherText = [NSData dataWithBytes:outbuf length:outlen];

NSString *base64String = [cipherText encodeBase64WithNewlines:NO];
NSString *iv = [initVector encodeBase64WithNewlines:NO];

base64String и iv затем отправляются в PHP, который пытается его декодировать:

<?php

import_request_variables( "p", "p_" );

if( $p_data != "" && $p_iv != "" )
{
    $encodedData = base64_decode( $p_data, true );
    $iv = base64_decode( $p_iv, true );

    $td = mcrypt_module_open( MCRYPT_BLOWFISH, '', MCRYPT_MODE_CBC, '' );
    $keySize = mcrypt_enc_get_key_size( $td );
    $key = substr( md5( "ThisIsMyKey" ), 0, $keySize );

    $decodedData = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $encodedData, MCRYPT_MODE_CBC, $iv );
    mcrypt_module_close( $td );

    echo "decoded: " . $decodedData;
}
?>

decodedData всегда бессмысленна.

Я попытался обратить процесс вспять, отправив закодированный вывод из PHP в Cocoa, но EVP_DecryptFinal () завершился ошибкой, и это заставляет меня поверить, что где-то есть проблема с заполнением NULL. Я прочитал и перечитал документы по PHP и OpenSSL, но теперь все вместе, и у меня нет идей, чтобы попробовать.

Ответы [ 2 ]

3 голосов
/ 26 ноября 2008

Я думаю, что ваша проблема в том, что метод получения необработанного ключа шифрования из строки ключей отличается с двух сторон. Функция php md5 () возвращает шестнадцатеричную строку, т.е. «a476c3 ...», которую вы уменьшаете до размера ключа, тогда как EVP_BytesToKey () - довольно сложная подпрограмма хеширования, которая возвращает необработанную строку байтов. Возможно, с предоставленными параметрами упростить до необработанного хеша MD5, но я не могу точно сказать. В любом случае, это будет отличаться от хэша php.

Если вы измените php на md5 («ThisIsMyKey», TRUE), это даст вам необработанный хэш md5. Что касается какао, то метод SSCrypto + getMD5ForData: должен генерировать один и тот же для той же строки (за исключением проблем с кодировкой текста).

Редактировать 1: Если строка php и данные Какао распечатываются одинаково, они все равно различаются на уровне байтов. Строка php закодирована в шестнадцатеричном формате (т.е. состоит только из символов 0-9 и a-f), в то время как данные какао являются необработанными байтами (хотя NSData полезно выводит строку своего содержимого в шестнадцатеричном формате, когда NSLogged). Вам все еще нужно добавить второй параметр TRUE в функцию php md5 (), чтобы получить необработанную строку байтов.

Редактировать 2: OpenSSL 1.1.0c изменил алгоритм дайджеста , используемый в некоторых внутренних компонентах. Раньше использовался MD5, а 1.1.0 переключался на SHA256. Будьте осторожны, изменения не влияют на вас как в EVP_BytesToKey, так и в таких командах, как openssl enc.

1 голос
/ 01 декабря 2008

Я разобрался в своей проблеме. Короткий ответ: используемый ключ был разной длины в Какао и PHP. Длинный ответ ...

В моем первоначальном запросе использовался Blowfish / CBC, представляющий собой шифр с переменной длиной ключа от 16 байт до 56. Исходя из идеи Боаза, что ключ каким-то образом виноват, я переключился на TripleDES для шифра, поскольку он использует фиксированный ключ. длина 24 байта. Тогда я заметил проблему: ключ, возвращаемый Cocoa / EVP_BytesToKey (), имел длину 24 байта, но значение, возвращаемое md5 (), хэшировавшим мой ключ, было только 16.

Решение проблемы состояло в том, чтобы PHP создавал ключ точно так же, как EVP_BytesToKey, пока длина вывода не будет хотя бы минимальной (cipherKeyLength + cipherIVLength). Следующий PHP делает именно это (игнорируя любые итерации или подсчет)

$cipher = MCRYPT_TRIPLEDES;
$cipherMode = MCRYPT_MODE_CBC;

$keySize   = mcrypt_get_key_size( $cipher, $cipherMode );
$ivSize    = mcrypt_get_iv_size( $cipher, $cipherMode );

$rawKey = "ThisIsMyKey";
$genKeyData = '';
do
{
    $genKeyData = $genKeyData.md5( $genKeyData.$rawKey, true );
} while( strlen( $genKeyData ) < ($keySize + $ivSize) );

$generatedKey = substr( $genKeyData, 0, $keySize );
$generatedIV  = substr( $genKeyData, $keySize, $ivSize );

$output = mcrypt_decrypt( $cipher, $generatedKey, $encodedData, $cipherMode, $generatedIV );

echo "output (hex)" . bin2hex($output);

Обратите внимание, что, скорее всего, в конце этого вывода будет заполнение PKCS # 5. Ознакомьтесь с комментариями здесь http://us3.php.net/manual/en/ref.mcrypt.php для pkcs5_pad и pkcs5_unpad для добавления и удаления указанных отступов.

В основном, возьмите необработанное значение md5 ключа, и, если этого недостаточно, добавьте ключ к результату md5 и снова md5 эту строку. Вымойте, промойте, повторите. Страница man для EVP_BytesToKey () объясняет, что он на самом деле делает, и показывает, где можно поместить значения соли, если это необходимо. Этот метод восстановления ключа также правильно восстанавливает вектор инициализации (iv), поэтому нет необходимости передавать его.

А как насчет Blowfish?

EVP_BytesToKey() возвращает наименьший возможный ключ для шифра, так как он не принимает контекст, на котором основывается размер ключа. Таким образом, размер по умолчанию - это все, что вы получаете, что для Blowfish составляет 16 байт. mcrypt_get_key_size(), с другой стороны, возвращает максимально возможный размер ключа. Итак, следующие строки в моем исходном коде:

$keySize = mcrypt_enc_get_key_size( $td );
$key = substr( md5( "ThisIsMyKey" ), 0, $keySize );

всегда будет возвращать 32-символьный ключ, потому что $ keySize установлен на 56. Изменив код выше на:

$cipher = MCRYPT_BLOWFISH;
$cipherMode = MCRYPT_MODE_CBC;

$keySize   = 16;

позволяет Blowfish правильно декодировать, но в значительной степени разрушает преимущество ключа переменной длины. Подводя итог, EVP_BytesToKey() не работает, когда дело доходит до шифров с переменной длиной ключа. Вам необходимо создать ключ / iv по-разному при использовании шифра с переменным ключом. Я не стал вдаваться в подробности, потому что 3DES будет работать для того, что мне нужно.

...