Шифрование данных в Какао, декодирование в PHP
-
11-07-2019 - |
Вопрос
Ситуацию, которую я пытаюсь разрешить: в моем приложении 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 будет работать для того, что мне нужно.