Ситуация, которую я пытаюсь решить: в моем приложении Cocoa мне нужно зашифровать строку с симметричным шифрованием, POST его на PHP, и этот сценарий декодирует данные. Процесс должен работать обратным образом для возврата ответа (PHP кодирует, декодирует Cocoa).
Я пропускаю что-то, потому что, хотя я могу получить как ключ, так и вектор инициализации (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 и данные Cocoa распечатываются одинаково, они по-прежнему различаются на уровне байтов. Строка php имеет шестнадцатеричное кодирование (т. Е. Состоит только из символов 0-9 и af), тогда как данные какао являются необработанными байтами (хотя NSData успешно печатает шестнадцатеричную строку своего содержимого при NSLogged). Вам все равно нужно добавить второй параметр TRUE в функцию mp5 () php, чтобы получить исходную строку байта.
Edit 2: OpenSSL 1.1.0c изменил алгоритм дайджеста] ( Шифрование / дешифрование не работает хорошо между двумя разными версиями openssl ), используемыми в некоторых внутренних компонентах. Раньше использовался 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()
возвращает наименьший ключ, возможный для шифрования, поскольку он не принимает контекст, с помощью которого можно EVP_BytesToKey()
размер ключа. Таким образом, размер по умолчанию – это все, что вы получаете, для Blowfish – 16 байт. mcrypt_get_key_size()
другой стороны, 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 будет работать для того, что мне нужно.