Вопрос

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

Я что-то упускаю, потому что, несмотря на то, что ключ и вектор инициализации (iv) могут быть одинаковыми как в PHP, так и в Cocoa, декодирование никогда не срабатывает, когда одно приложение отправляет свои закодированные данные другому. Оба работают просто отлично, кодируя / декодируя свои собственные данные (проверено, чтобы убедиться, что не было какой-либо проблемы 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, но теперь все вместе, и у меня нет идей, чтобы попробовать.

Это было полезно?

Решение

Я думаю, что ваша проблема в том, что метод получения необработанного ключа шифрования из строки ключей различен с двух сторон. Функция 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 .

Другие советы

Я понял свою проблему. Короткий ответ: используемый ключ был разной длины в Какао и 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 будет работать для того, что мне нужно.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top