Шифрование в JavaScript и дешифрование в PHP

Я шифрую свой пароль пользователя в JavaScript следующим образом:

 var encryptedPassword = CryptoJS.AES.encrypt(password, "Secret Passphrase");

Он отлично работает, но теперь я пытаюсь расшифровать PHP на стороне сервера следующим образом:

 $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
 $decryptPassword = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, "Secret Passphrase", base64_decode($password), MCRYPT_MODE_CBC, $iv);

вообще не работает, расшифрованная строка пароля выглядит очень странно:

 string(64) ">�OX2MS��댗v�<$�ʕ��i�̄��_��P���\�կ=�_6(�m����,4WT7��a"

Вот текущее состояние моего кода в JavaScript после полезных комментариев:

    var encryptedPassword = CryptoJS.AES.encrypt(password, "Secret Passphrase");
    var ivHex = encryptedPassword.iv.toString();
    var ivSize = encryptedPassword.algorithm.ivSize; // same as blockSize
    var keySize = encryptedPassword.algorithm.keySize;
    var keyHex = encryptedPassword.key.toString();
    var saltHex = encryptedPassword.salt.toString(); // must be sent
    var openSslFormattedCipherTextString = encryptedPassword.toString(); // not used
    var cipherTextHex = encryptedPassword.ciphertext.toString(); // must be sent

Я отправляю saltHex и CipherTextHex на сервер PHP и использую mcrypt_decrypt() следующим образом:

 $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), $saltHex);
 $decryptPassword = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, "Secret Passphrase", base64_decode($cipherTextHex), MCRYPT_MODE_CBC, $iv);

Он по-прежнему не работает с этим обновленным кодом.

Может ли кто-нибудь помочь мне правильно расшифровать с помощью PHP-функции mcrypt_decrypt() для простого метода шифрования AES? Я уверен, что делаю что-то не так с шифром, режимом mcrypt и параметрами IV внутри моего метода mcrypt_decrypt(). Спасибо, если знаете.


person Nizar B.    schedule 28.12.2014    source источник
comment
@ArtjomB. Не могли бы вы дать больше точности?   -  person Nizar B.    schedule 28.12.2014
comment
В чем преимущество шифрования в JavaScript и расшифровки в PHP, потому что вы можете шифровать и расшифровывать в PHP   -  person    schedule 28.12.2014
comment
@lawrenceoverflow Шифрование на стороне клиента с помощью JavaScript используется, чтобы не отправлять форму входа в виде открытого текста на мой PHP-сервер.   -  person Nizar B.    schedule 28.12.2014
comment
@lawrenceoverflow javascript не java   -  person Francis.TM    schedule 28.12.2014
comment
@Francis.TM Правдивая история...   -  person Nizar B.    schedule 28.12.2014
comment
См. здесь: stackoverflow.com/a/27250883/1816580 и преобразуйте код в php, если хотите   -  person Artjom B.    schedule 28.12.2014
comment
@ArtjomB. Было бы неплохо, если бы у меня были только правильные CIPHER, MCRYPT_MODE и IV в PHP для расшифровки этой строки JavaScript: var зашифрованныйPassword = CryptoJS.AES.encrypt(пароль, секретная фраза-пароль);   -  person Nizar B.    schedule 28.12.2014
comment
Если у вас нет капельницы и соли, вам не повезло. Вы не показали, как передается encryptedPassword, потому что он может быть закодирован в нем. CryptoJS использует OpenSSLFormatter. Вы должны изучить код CryptoJS.   -  person Artjom B.    schedule 28.12.2014
comment
@ArtjomB. Ведьма означает, что cipherTextHex в JavaScript является CIPHER для функции PHP mcrypt_decrypt(), но что такое переменная saltText?   -  person Nizar B.    schedule 28.12.2014
comment
нет, $cipher по-прежнему MCRYPT_RIJNDAEL_128.   -  person Artjom B.    schedule 28.12.2014
comment
Давайте продолжим обсуждение в чате.   -  person Nizar B.    schedule 28.12.2014
comment
Я сделал кое-что, что вы можете использовать: расшифровать с помощью javascript cryptojs"> stackoverflow.com/questions/24337317/   -  person Brain Foo Long    schedule 28.12.2014


Ответы (3)


Проблема в том, что в коде CryptoJS пароль используется для получения ключа и IV, которые будут использоваться для шифрования AES, но mcrypt использует ключ только для шифрования/дешифрования. Эту информацию нужно передать в php. Поскольку вы не хотите передавать пароль, вы должны точно так же получить ключ и IV в php.

Следующий код извлекает ключ и IV из пароля и соли. Он создан по образцу кода в моем ответе здесь (для получения дополнительной информации).

function evpKDF($password, $salt, $keySize = 8, $ivSize = 4, $iterations = 1, $hashAlgorithm = "md5") {
    $targetKeySize = $keySize + $ivSize;
    $derivedBytes = "";
    $numberOfDerivedWords = 0;
    $block = NULL;
    $hasher = hash_init($hashAlgorithm);
    while ($numberOfDerivedWords < $targetKeySize) {
        if ($block != NULL) {
            hash_update($hasher, $block);
        }
        hash_update($hasher, $password);
        hash_update($hasher, $salt);
        $block = hash_final($hasher, TRUE);
        $hasher = hash_init($hashAlgorithm);

        // Iterations
        for ($i = 1; $i < $iterations; $i++) {
            hash_update($hasher, $block);
            $block = hash_final($hasher, TRUE);
            $hasher = hash_init($hashAlgorithm);
        }

        $derivedBytes .= substr($block, 0, min(strlen($block), ($targetKeySize - $numberOfDerivedWords) * 4));

        $numberOfDerivedWords += strlen($block)/4;
    }

    return array(
        "key" => substr($derivedBytes, 0, $keySize * 4),
        "iv"  => substr($derivedBytes, $keySize * 4, $ivSize * 4)
    );
}

Соль генерируется во время шифрования в CryptoJS и должна быть отправлена ​​на php вместе с зашифрованным текстом. Перед вызовом evpKDF соль должна быть преобразована в двоичную строку из шестнадцатеричной.

$keyAndIV = evpKDF("Secret Passphrase", hex2bin($saltHex));
$decryptPassword = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, 
        $keyAndIV["key"], 
        hex2bin($cipherTextHex), 
        MCRYPT_MODE_CBC, 
        $keyAndIV["iv"]);

Если на сервер было отправлено только encryptedPassword.toString(), то перед использованием необходимо разделить соль и фактический зашифрованный текст. Это проприетарный формат, совместимый с OpenSSL, в котором первые 8 байтов являются «Salted__», следующие 8 байтов являются случайной солью, а остальные — фактическим зашифрованным текстом. Все вместе закодировано в Base64.

function decrypt($ciphertext, $password) {
    $ciphertext = base64_decode($ciphertext);
    if (substr($ciphertext, 0, 8) != "Salted__") {
        return false;
    }
    $salt = substr($ciphertext, 8, 8);
    $keyAndIV = evpKDF($password, $salt);
    $decryptPassword = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, 
            $keyAndIV["key"], 
            substr($ciphertext, 16), 
            MCRYPT_MODE_CBC, 
            $keyAndIV["iv"]);

    // unpad (PKCS#7)
    return substr($decryptPassword, 0, strlen($decryptPassword) - ord($decryptPassword[strlen($decryptPassword)-1]));
}

То же самое можно сделать с расширением OpenSSL вместо Mcrypt:

function decrypt($ciphertext, $password) {
    $ciphertext = base64_decode($ciphertext);
    if (substr($ciphertext, 0, 8) != "Salted__") {
        return false;
    }
    $salt = substr($ciphertext, 8, 8);
    $keyAndIV = evpKDF($password, $salt);
    $decryptPassword = openssl_decrypt(
            substr($ciphertext, 16), 
            "aes-256-cbc",
            $keyAndIV["key"], 
            OPENSSL_RAW_DATA, // base64 was already decoded
            $keyAndIV["iv"]);

    return $decryptPassword;
}
person Artjom B.    schedule 28.12.2014
comment
Подобный код можно найти в моих ответах для Java и Питон. - person Artjom B.; 11.08.2017
comment
Уведомление о безопасности. Этот код предназначен для обеспечения совместимости между различными языками программирования. Это не обязательно полностью безопасно. Его безопасность зависит от сложности и длины пароля из-за всего одной итерации и использования MD5. Я бы рекомендовал использовать как минимум 20-символьный пароль с буквенно-цифровыми символами, который в идеале генерируется случайным образом. - person Artjom B.; 29.01.2018
comment
СПАСИБО ОГРОМНОЕ @Artjom B.! Хотел бы я поставить вам +100. Это было так полезно. - person Ryan; 11.08.2018

Вы не можете расшифровать с помощью случайного вектора инициализации — вам нужно использовать тот же IV, с помощью которого данные были зашифрованы. Кроме того, IIRC, AES по умолчанию использует 8-битное представление зашифрованных данных, которые необходимо тщательно обрабатывать при передаче по HTTP.

person symcbean    schedule 28.12.2014
comment
Одна вещь, у меня проблема с md5. Когда я передаю чистый пароль для проверки входа пользователя, хэш md5 равен хешу md5 из базы данных MySQL. Но когда я использую расшифрованный пароль, md5 отличается. Это странно, потому что чистая строка пароля совпадает с расшифрованной строкой, а хэш md5 отличается. - person Nizar B.; 29.12.2014

person    schedule
comment
Хотя этот код может дать ответ на вопрос, предоставление дополнительного контекста относительно того, как и/или почему он решает проблему, улучшит долгосрочную ценность ответа. - person Tomer Shetah; 13.10.2020