用 CryptoJS 加密,用 PHP 解密

问题描述 投票:0回答:4

在客户端(移动设备)我用 CryptoJS 加密用户密码:

var lib_crypt = require('aes');

$.loginButton.addEventListener('click', function(e){

var key = lib_crypt.CryptoJS.enc.Hex.parse('bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3');
var iv  = lib_crypt.CryptoJS.enc.Hex.parse('101112131415161718191a1b1c1d1e1f');

var encrypted = lib_crypt.CryptoJS.AES.encrypt($.passwordInput.value, key, { iv: iv });

var password_base64 = encrypted.ciphertext.toString(lib_crypt.CryptoJS.enc.Base64); 
return password_base64; 
});

在服务器端我想用 mcrypt_decrypt 解密它:

function decryptPassword($password)
{
    $key = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");
    $ciphertext_dec = base64_decode($password);
    $iv_dec = "101112131415161718191a1b1c1d1e1f";

    $ciphertext_dec = substr($ciphertext_dec, 16);
    $decryptedPassword = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $ciphertext_dec, MCRYPT_MODE_CBC, $iv_dec);

    return trim($decryptedPassword);
}

我使用相同的密钥和 IV,我做错了什么?

javascript php encryption mcrypt cryptojs
4个回答
19
投票

你好,

为了实现这一点,应该考虑使用 key 和 iv,每个都有 32 个十六进制数字,我必须在做我的事情时解决这个问题,这就是它的方式

<!--
This reach.your.crypto.js is just a ficticious placeholder, 
that was used replaced by http://crypto-js.googlecode.com/svn/tags/3.1.2/build/,
which does not exist anymore,
which is the path to your CryptoJS library,
that can be downloaded through 
https://code.google.com/archive/p/crypto-js/downloads?page=1
-->
<script src="reach.your.crypto.js/rollups/aes.js">
</script>

<script type="text/javascript">
//The key and iv should be 32 hex digits each, any hex digits you want, 
//but it needs to be 32 on length each
var key = CryptoJS.enc.Hex.parse("0123456789abcdef0123456789abcdef");
var iv =  CryptoJS.enc.Hex.parse("abcdef9876543210abcdef9876543210");

/*
if you wish to have a more friendly key, you can convert letters to Hex this way:
var a = "D";
var hex_D = a.charCodeAt(0).toString(16);
just to mention,
if it were to binary, it would be:
var binary_D = a.charCodeAt(0).toString(2);
*/

var secret = "Hi, this will be seen uncrypted later on";

//crypted
var encrypted = CryptoJS.AES.encrypt(secret, key, {iv:iv});
//and the ciphertext put to base64
encrypted = encrypted.ciphertext.toString(CryptoJS.enc.Base64);    
//Assuming you have control on the server side, and know the key and iv hexes(we do),
//the encrypted var is all you need to pass through ajax,
//Let's follow with welcomed pure JS style, to reinforce one and other concept if needed
var xh = new XMLHttpRequest();
xh.open("POST", "decrypt_in_php.php", true);
xh.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xh.send("encrypted="+encodeURIComponent(encrypted));
</script>

现在用 PHP 接收和解密

<?php
//Here we have the key and iv which we know, because we have just chosen them on the JS,
//the pack acts just like the parse Hex from JS

$key = pack("H*", "0123456789abcdef0123456789abcdef");
$iv =  pack("H*", "abcdef9876543210abcdef9876543210");

//Now we receive the encrypted from the post, we should decode it from base64,
$encrypted = base64_decode($_POST["encrypted"]);
$shown = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $encrypted, MCRYPT_MODE_CBC, $iv);

echo $shown;
//Although the decrypted is shown, there may be needed to trim and str_replace some \r \n \x06 \x05, if there is not a better "trim" way to do it though
?>

有了这个,我们将返回“嗨,稍后将看到未加密的” :)


13
投票

这是一个基于this评论的解决方案,使用来自PHP的

openssl_decrypt

JavaScript 部分(使用 NodeJS 为浏览器开发)——首先,使用 npm install crypto-js 安装

CryptoJS
,然后是您的 JS 代码:

import aes from 'crypto-js/aes'
import encHex from 'crypto-js/enc-hex'
import padZeroPadding from 'crypto-js/pad-zeropadding'

// message to encrypt
let msg = "Hello world";

// the key and iv should be 32 hex digits each, any hex digits you want, but it needs to be 32 on length each
let key = encHex.parse("0123456789abcdef0123456789abcdef");
let iv =  encHex.parse("abcdef9876543210abcdef9876543210");

// encrypt the message
let encrypted = aes.encrypt(msg, key, {iv:iv, padding:padZeroPadding}).toString();

// and finally, send this "encrypted" string to your server

PHP 方面,您的代码将如下所示:

// we use the same key and IV
$key = hex2bin("0123456789abcdef0123456789abcdef");
$iv =  hex2bin("abcdef9876543210abcdef9876543210");

// we receive the encrypted string from the post
$encrypted = $_POST['decrypt'];
$decrypted = openssl_decrypt($encrypted, 'AES-128-CBC', $key, OPENSSL_ZERO_PADDING, $iv);
// finally we trim to get our original string
$decrypted = trim($decrypted);

3
投票

你没有在两边做同样的事情。

你确实在 CryptoJS 中解析了 IV,但忘了在 PHP 中解析:

$iv_dec = pack('H*', "101112131415161718191a1b1c1d1e1f");

为了修复您的 IV 错误,您可能注意到前 16 个字节是乱码。当 IV 错误时会发生这种情况。请注意,CryptoJS 默认使用 CBC 模式,因此 IV 仅在解密期间影响第一个块。 删除这个:

$ciphertext_dec = substr($ciphertext_dec, 16);

填充

您可能注意到大多数明文都无法正确显示。它们最后以一些奇怪的重复字符结尾。这是 CryptoJS 中默认应用的 PKCS#7 填充。您必须自己在 PHP 中删除填充。好的是 Maarten Bodewes 为这个here.

提供了一个合适的复制粘贴解决方案

trim()
可能适用于 ZeroPadding,但当使用 PKCS#7 中定义的适当填充方案时则不适用。您可以完全删除
trim()
调用,因为它没有用,并且可能会导致意外的明文,因为零字节和空格从头到尾都被修剪了。


0
投票

如果没有发送JS部分的

iv
设置,设置为默认设置,PHP函数如下。这个方法对我有用

Js部分

let value = "Hello ehsan";
let encrypted = CryptoJS.AES.encrypt(value, key);

PHP 部分

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

function cryptoJs_aes_encrypt($data, $key)
{

    $salted = "Salted__";
    $salt = openssl_random_pseudo_bytes(8);

    $keyAndIV = aes_evpKDF($key, $salt);
    $encrypt  = openssl_encrypt(
        $data,
        "aes-256-cbc",
        $keyAndIV["key"],
        OPENSSL_RAW_DATA, // base64 was already decoded
        $keyAndIV["iv"]
    );
    return  base64_encode($salted . $salt . $encrypt);
}

function aes_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)
    );
}
© www.soinside.com 2019 - 2024. All rights reserved.