在PHP中加密字符串并在Node.js中解密。

时间:2022-04-12 14:32:11

I am sending data through insecure connection between Apache and Node.js servers. I need to encrypt data in PHP and decrypt in Node.js. I've spent 2 days trying to get it to work, however I only managed to get message signing to work, no encryption. I tried passing AES128-CBC, AES256-CBC, DES, AES128, AES256 as algorithms, however nothing worked well..

我通过Apache和Node之间不安全的连接发送数据。js服务器。我需要在PHP中加密数据并在Node.js中解密。我花了2天时间试图让它工作,但我只是设法让消息签署工作,没有加密。我试过通过AES128- cbc, AES256- cbc, DES, AES128, AES256作为算法,但是没有什么效果。

I tried this in PHP:

我在PHP中尝试过:

$data = json_encode(Array('mk' => $_SESSION['key'], 'algorithm' => 'SHA1', 'username' => $_SESSION['userid'], 'expires' => $expires));
$payload = openssl_encrypt($data, 'des', '716c26ef');
return base64_encode($payload);

And in Node.js:

在node . js:

var enc_json = new Buffer(response[1], 'base64');
var decipher = crypto.createDecipher('des', '716c26ef');
var json = decipher.update(enc_json).toString('ascii');
json += decipher.final('ascii');

And besides wrong decrypted data I get error such as these:

除了错误的解密数据,我还会犯这样的错误:

TypeError: error:0606508A:digital envelope routines:EVP_DecryptFinal_ex:data not multiple of block length

TypeError: error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length

I need a simple encryption as data is not too sensitive (no password or user data), however data should only be read by the recipient. Key length can be anything, but procedure to encrypt/decrypt has to be as simple as possible, please no IVs.

我需要一个简单的加密,因为数据不太敏感(没有密码或用户数据),但是数据只能由收件人读取。密钥长度可以是任何东西,但是加密/解密程序必须尽可能简单,请不要使用IVs。

2 个解决方案

#1


8  

When dealing with symmetric encryption like this, the first step is realize that it's probably going to be a huge pain in the rear - I've never, ever had it work right away, even when I was copy pasting my own code. This is mainly because encryption and decryption methods are, by design, utterly unforgiving and rarely give useful error messages. A single null character, carriage return, line feed, or dynamically converted type can silently blow the whole process up.

当处理像这样的对称加密时,第一步是意识到它可能会是一个巨大的痛苦在后面-我从来没有,曾经有它工作,即使当我拷贝粘贴我自己的代码。这主要是因为加密和解密方法是完全不可原谅的,很少给出有用的错误消息。一个空字符、回车、换行或动态转换类型可以悄无声息地将整个过程放大。

Knowing this, progress stepwise. I suggest the following:

知道了这一点,逐步进步。我建议以下几点:

First, get PHP alone working. Pass in sample text, encrypt it, immediately decrypt it, and compare it with strict equality to the original clear text variable. Are they perfectly the same? Output both, as well - are they the same type and appear perfectly unmolested? Watch out for non-printed characters - check the length and character encoding too!

首先,让PHP单独工作。传入示例文本,对其进行加密,立即对其解密,并将其与原始明文变量的严格相等进行比较。它们完全一样吗?输出都是相同的类型,并且看起来完全不受干扰吗?注意非打印字符-检查长度和字符编码!

Now, do the above with one more sample text that is 1 character more or less than the previous one. This debugs block size/zero-padding issues - it matters.

现在,用一个比前一个字符多或少一个字符的示例文本来完成上述操作。这个调试器块大小/零填充问题—它很重要。

If that's working - and it rarely does right away, for hard to predict reasons, continue to Node.js.

如果这是有效的,而且它很少会立即发生,因为很难预测原因,继续进行。

In Node.js, do the same thing as you did in PHP, even if it seems like wasted effort - for extra reasons that will be obvious in a moment. Encrypt and decrypt, all together, in your Node.js. Does it work with all the same provisos given above?

在节点。js和你在PHP中做的事情一样,即使这看起来像是白费功夫——因为额外的原因会在一瞬间变得明显。在你的Node.js中加密和解密。它是否与上面给出的所有条件相同?

Once that is done, here comes the 'fun' part: using the same encryption methods independently in Node.js and PHP, have them both output to you the 'final' ready-to-transmit cryptext that both produced.

一旦完成,这里就有了“乐趣”部分:在节点中独立使用相同的加密方法。js和PHP,将它们都输出到“最终”的“最终”即传输的加密文本中。

If all is well, they should be perfectly, exactly the same. If they aren't, you have a problem with your encryption implementations and methods not being compatible between systems. Some setting is wrong or conflicting (perhaps with zero padding or a host of other possibilities, or IV, etc), or you need to try a different implementation.

如果一切顺利,它们应该是完美的,完全一样。如果它们不是,那么您的加密实现和方法在系统之间不兼容就有问题。有些设置是错误的或冲突的(可能是零填充或其他可能的情况,或者IV等),或者您需要尝试不同的实现。

If I had to guess blindly, I'd say there is an issue with the base64 encoding and decoding (it's most commonly what goes wrong). Things tend to get done twice, because it can be tricky to debug binary data types in web applications (through a browser). Sometimes things are being encoded twice but only decoded once, or one implementation will 'helpfully' encode/decode something automatically without being clear that's what it's doing, etc.

如果我不得不盲目地猜测,我认为base64编码和解码是有问题的(最常见的是出错)。因为在web应用程序中调试二进制数据类型(通过浏览器)可能很困难,所以事情往往会重复两次。有时候,有些东西被编码了两次,但只有一次被解码,或者一个实现会“帮助”自动编码/解码某些东西,而不清楚这是它在做什么,等等。

It's also possible it's a zero-padding implementation issue between Node and PHP, as suggested here: AES encrypt in Node.js Decrypt in PHP. Fail.

它也可能是节点和PHP之间的零填充实现问题,正如这里所建议的:AES在节点中加密。js解密在PHP。失败。

These last two issues are strongly suggested by your error codes. The encryption methods predict block sizes of precise length, and if they are off then that signals corruption of the data being passed to the functions - which happens if a single extra character slipped in, or if encoding is handled differently, etc.

最后两个问题是由错误代码强烈建议的。加密方法可以预测精确长度的块大小,如果它们断开,就会显示传递给函数的数据的损坏——如果一个额外的字符出现了,或者编码处理的方式不同,就会发生这种情况。

If you step through each of the above one at a time, assuring yourself you can't rush and must check every painstaking tiny little step of the process, it should be much more clear where exactly things are going wrong, and then that can be troubleshooted.

如果你一步一步地完成上面的每一个步骤,确保自己不能匆忙,并且必须检查过程中的每一个细微的步骤,那么就应该清楚地知道哪里出了问题,然后就可以解决问题了。

#2


16  

I was struggling with the same problem this week but in the opposite way (PHP encrypts -> NodeJS decrypts) and had managed to get this snippet working:

我在这个星期遇到了同样的问题,但与此相反的是(PHP加密-> NodeJS解密),并设法让这段代码工作:

aes256cbc.js

aes256cbc.js

var crypto = require('crypto');

var encrypt = function (plain_text, encryptionMethod, secret, iv) {
    var encryptor = crypto.createCipheriv(encryptionMethod, secret, iv);
    return encryptor.update(plain_text, 'utf8', 'base64') + encryptor.final('base64');
};

var decrypt = function (encryptedMessage, encryptionMethod, secret, iv) {
    var decryptor = crypto.createDecipheriv(encryptionMethod, secret, iv);
    return decryptor.update(encryptedMessage, 'base64', 'utf8') + decryptor.final('utf8');
};

var textToEncrypt = new Date().toISOString().substr(0,19) + '|My super secret information.';
var encryptionMethod = 'AES-256-CBC';
var secret = "My32charPasswordAndInitVectorStr"; //must be 32 char length
var iv = secret.substr(0,16);

var encryptedMessage = encrypt(textToEncrypt, encryptionMethod, secret, iv);
var decryptedMessage = decrypt(encryptedMessage, encryptionMethod, secret, iv);

console.log(encryptedMessage);
console.log(decryptedMessage);

aes256cbc.php

aes256cbc.php

<?php
date_default_timezone_set('UTC');
$textToEncrypt = substr(date('c'),0,19) . "|My super secret information.";
$encryptionMethod = "AES-256-CBC";
$secret = "My32charPasswordAndInitVectorStr";  //must be 32 char length
$iv = substr($secret, 0, 16);

$encryptedMessage = openssl_encrypt($textToEncrypt, $encryptionMethod, $secret,0,$iv);
$decryptedMessage = openssl_decrypt($encryptedMessage, $encryptionMethod, $secret,0,$iv);

echo "$encryptedMessage\n";
echo "$decryptedMessage\n";
?>

The secret here to avoid falling in key/iv size/decryption problems is to have the secret of exactly 32 characters length and 16 for the IV. Also, it is VERY important to use 'base64' and 'utf8' in NodeJS since these are the defaults in PHP.

为了避免在密钥/iv的大小/解密问题上出现问题,关键是要有32个字符长度和16个字符的秘密。另外,在NodeJS中使用“base64”和“utf8”是非常重要的,因为这些都是PHP的默认值。

Here are some sample runs:

下面是一些示例运行:

$ node aes256cbc.js && php aes256cbc.php
zra3FX4iyCc7qPc1dZs+G3ZQ40f5bSw8P9n5OtWl1t86nV5Qfh4zNRPFbsciyyHyU3Qi4Ga1oTiTwzrPIZQXLw==
2015-01-27T18:29:12|My super secret information.
zra3FX4iyCc7qPc1dZs+G3ZQ40f5bSw8P9n5OtWl1t86nV5Qfh4zNRPFbsciyyHyU3Qi4Ga1oTiTwzrPIZQXLw==
2015-01-27T18:29:12|My super secret information.

$ node aes256cbc.js && php aes256cbc.php
zra3FX4iyCc7qPc1dZs+G6B6+8aavHNc/Ymv9L6Omod8Di3tMbvOa2B7O2Yiyoutm9fy9l0G+P5VJT9z2qNESA==
2015-01-27T18:29:15|My super secret information.
zra3FX4iyCc7qPc1dZs+G6B6+8aavHNc/Ymv9L6Omod8Di3tMbvOa2B7O2Yiyoutm9fy9l0G+P5VJT9z2qNESA==
2015-01-27T18:29:15|My super secret information.

$ node aes256cbc.js && php aes256cbc.php
zra3FX4iyCc7qPc1dZs+G4oD1Fr5yLByON6QDE56UOqP6kkfGJzpyH6TbwZYX2oGlh2JGv+aHYUMh0qQnAj/uw==
2015-01-27T18:29:29|My super secret information.
zra3FX4iyCc7qPc1dZs+G4oD1Fr5yLByON6QDE56UOqP6kkfGJzpyH6TbwZYX2oGlh2JGv+aHYUMh0qQnAj/uw==
2015-01-27T18:29:29|My super secret information.

$ node aes256cbc.js && php aes256cbc.php
zra3FX4iyCc7qPc1dZs+G5OVCbCaUy8a0LLF+Bn8UT4X3nYbtynO0Zt2mvXnnli9dRxrxMw43uWnkh8MIwVHXA==
2015-01-27T18:29:31|My super secret information.
zra3FX4iyCc7qPc1dZs+G5OVCbCaUy8a0LLF+Bn8UT4X3nYbtynO0Zt2mvXnnli9dRxrxMw43uWnkh8MIwVHXA==
2015-01-27T18:29:31|My super secret information.

$ node aes256cbc.js && php aes256cbc.php
fdsqSyHBJjlwD0jYfOUZM2FrONG6Fk5d7FOItYEdbnaZIhhmg/apa8/jPwKFkDXD9eNqWC3w0JzY5wjtZADiBA==
2015-01-27T18:30:08|My super secret information.
fdsqSyHBJjlwD0jYfOUZM2FrONG6Fk5d7FOItYEdbnaZIhhmg/apa8/jPwKFkDXD9eNqWC3w0JzY5wjtZADiBA==
2015-01-27T18:30:08|My super secret information.

$ node aes256cbc.js && php aes256cbc.php
fdsqSyHBJjlwD0jYfOUZM4SRfi6jG5EoDFEF6d9xCIyluXSiMaKlhd89ovpeOz/YyEIlPbYR4ly00gf6hWfKHw==
2015-01-27T18:30:45|My super secret information.
fdsqSyHBJjlwD0jYfOUZM4SRfi6jG5EoDFEF6d9xCIyluXSiMaKlhd89ovpeOz/YyEIlPbYR4ly00gf6hWfKHw==
2015-01-27T18:30:45|My super secret information.

NOTE:

注意:

I use a "timestamp|message" format to avoid man in the middle attacks. For example, if the encrypted message contains an ID to be authenticated, the MitM could capture the message and re-send it every time he wants to re-authenticate.

我使用“时间戳|消息”格式来避免中间人攻击。例如,如果加密消息包含一个要经过身份验证的ID,那么MitM可以捕获消息并在每次重新进行身份验证时重新发送它。

Therefore, I could check the timestamp on the encrypted message to be within a little time interval. This way, the same message is encrypted differently each second because of the timestamp, and could not be used out of this fixed time interval.

因此,我可以在短时间间隔内检查加密消息的时间戳。这样,由于时间戳,相同的消息会以不同的方式进行加密,并且不能在这个固定的时间间隔内使用。

EDIT:

编辑:

Here I was misusing the Initialization Vector (IV). As @ArtjomB. explained, the IV should be the first part of the encrypted message, and also it should be a random value. It's also recommended to use a hmac value in a HTTP Header (x-hmac: *value*) in order to validate that the message was originated from a valid source (but this does not address the "re-send" message issue previously described).

在这里,我使用了初始化向量(IV),如@ArtjomB。解释,IV应该是加密消息的第一部分,也应该是一个随机值。它还建议在HTTP头(x-hmac: *value*)中使用hmac值,以验证消息是否来自有效的源(但这并没有解决前面描述的“重新发送”消息问题)。

Here's the improved version, including the hmac for php and node and the IV as a part of the encrypted message:

这是改进后的版本,包括用于php和节点的hmac,以及作为加密消息的一部分的IV:

aes256cbc.js (v2)

aes256cbc。js(v2)

var crypto = require('crypto');

var encrypt = function (message, method, secret, hmac) {
    //var iv = crypto.randomBytes(16).toString('hex').substr(0,16);    //use this in production
    var iv = secret.substr(0,16);    //using this for testing purposes (to have the same encryption IV in PHP and Node encryptors)
    var encryptor = crypto.createCipheriv(method, secret, iv);
    var encrypted = new Buffer(iv).toString('base64') + encryptor.update(message, 'utf8', 'base64') + encryptor.final('base64');
    hmac.value = crypto.createHmac('md5', secret).update(encrypted).digest('hex');
    return encrypted;
};

var decrypt = function (encrypted, method, secret, hmac) {
    if (crypto.createHmac('md5', secret).update(encrypted).digest('hex') == hmac.value) {
        var iv = new Buffer(encrypted.substr(0, 24), 'base64').toString();
        var decryptor = crypto.createDecipheriv(method, secret, iv);
        return decryptor.update(encrypted.substr(24), 'base64', 'utf8') + decryptor.final('utf8');
    }
};

var encryptWithTSValidation = function (message, method, secret, hmac) {
    var messageTS = new Date().toISOString().substr(0,19) + message;
    return encrypt(messageTS, method, secret, hmac);
}

var decryptWithTSValidation = function (encrypted, method, secret, hmac, intervalThreshold) {
    var decrypted = decrypt(encrypted, method, secret, hmac);
    var now = new Date();
    var year = parseInt(decrypted.substr(0,4)), month = parseInt(decrypted.substr(5,2)) - 1,
    day = parseInt(decrypted.substr(8,2)), hour = parseInt(decrypted.substr(11,2)), 
    minute = parseInt(decrypted.substr(14,2)), second = parseInt(decrypted.substr(17,2));
    var msgDate = new Date(Date.UTC(year, month, day, hour, minute, second))
    if (Math.round((now - msgDate) / 1000) <= intervalThreshold) {
        return decrypted.substr(19);
    }
}

var message = 'My super secret information.';
var method = 'AES-256-CBC';
var secret = "My32charPasswordAndInitVectorStr"; //must be 32 char length
var hmac = {};

//var encrypted = encrypt(message, method, secret, hmac);
//var decrypted = decrypt(encrypted, method, secret, hmac);
var encrypted = encryptWithTSValidation(message, method, secret, hmac);
var decrypted = decryptWithTSValidation(encrypted, method, secret, hmac, 60*60*12); //60*60m*12=12h

console.log("Use HTTP header 'x-hmac: " + hmac.value + "' for validating against MitM-attacks.");
console.log("Encrypted: " + encrypted);
console.log("Decrypted: " + decrypted);

Note that crypto.createHmac(...).digest('hex') is digested with hex. This is the default in PHP for hmac.

请注意,密码。createhmac(…).digest('hex')被hex消化。这是hmac的PHP默认值。

aes256cbc.php (v2)

aes256cbc。php(v2)

<?php

function encrypt ($message, $method, $secret, &$hmac) {
    //$iv = substr(bin2hex(openssl_random_pseudo_bytes(16)),0,16);    //use this in production
    $iv = substr($secret, 0, 16);        //using this for testing purposes (to have the same encryption IV in PHP and Node encryptors)
    $encrypted = base64_encode($iv) . openssl_encrypt($message, $method, $secret, 0, $iv);
    $hmac = hash_hmac('md5', $encrypted, $secret);
    return $encrypted;
}

function decrypt ($encrypted, $method, $secret, $hmac) {
    if (hash_hmac('md5', $encrypted, $secret) == $hmac) {
        $iv = base64_decode(substr($encrypted, 0, 24));
        return openssl_decrypt(substr($encrypted, 24), $method, $secret, 0, $iv);
    }
}

function encryptWithTSValidation ($message, $method, $secret, &$hmac) {
    date_default_timezone_set('UTC');
    $message = substr(date('c'),0,19) . "$message";
    return encrypt($message, $method, $secret, $hmac);
}

function decryptWithTSValidation ($encrypted, $method, $secret, $hmac, $intervalThreshold) {
    $decrypted = decrypt($encrypted, $method, $secret, $hmac);
    $now = new DateTime();
    $msgDate = new DateTime(str_replace("T"," ",substr($decrypted,0,19)));
    if (($now->getTimestamp() - $msgDate->getTimestamp()) <= $intervalThreshold) {
        return substr($decrypted,19);
    }
}

$message = "My super secret information.";
$method = "AES-256-CBC";
$secret = "My32charPasswordAndInitVectorStr";  //must be 32 char length

//$encrypted = encrypt($message, $method, $secret, $hmac);
//$decrypted = decrypt($encrypted, $method, $secret, $hmac);

$encrypted = encryptWithTSValidation($message, $method, $secret, $hmac);
$decrypted = decryptWithTSValidation($encrypted, $method, $secret, $hmac, 60*60*12); //60*60m*12=12h

echo "Use HTTP header 'x-hmac: $hmac' for validating against MitM-attacks.\n";
echo "Encrypted: $encrypted\n";
echo "Decrypted: $decrypted\n";
?>

Here are some sample runs:

下面是一些示例运行:

$ node aes256cbc.js && php aes256cbc.php
Use HTTP header 'x-hmac: 6862972ef0f463bf48523fc9e334bb42' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==I6cAKeoxeSP5TGgtK59PotB/iG2BUSU8Y6NhAhVabN9UB+ZCTn7q2in4JyLwQiGN
Decrypted: My super secret information.
Use HTTP header 'x-hmac: 6862972ef0f463bf48523fc9e334bb42' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==I6cAKeoxeSP5TGgtK59PotB/iG2BUSU8Y6NhAhVabN9UB+ZCTn7q2in4JyLwQiGN
Decrypted: My super secret information.

$ node aes256cbc.js && php aes256cbc.php
Use HTTP header 'x-hmac: b2e63f216acde938a82142220652cf59' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CSgYBBR8dkZytORm8xwEDmD9WB1mpqC3XnSrB+wR3/KW
Decrypted: My super secret information.
Use HTTP header 'x-hmac: b2e63f216acde938a82142220652cf59' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CSgYBBR8dkZytORm8xwEDmD9WB1mpqC3XnSrB+wR3/KW
Decrypted: My super secret information.

$ node aes256cbc.js && php aes256cbc.php
Use HTTP header 'x-hmac: 73181744453d55eb6f81896ffd284cd8' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CTGik4Lv9PnWuEg5SiADJcdKX1to0LrNKmuCiYIweBAZ
Decrypted: My super secret information.
Use HTTP header 'x-hmac: 73181744453d55eb6f81896ffd284cd8' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CTGik4Lv9PnWuEg5SiADJcdKX1to0LrNKmuCiYIweBAZ
Decrypted: My super secret information.

$ node aes256cbc.js && php aes256cbc.php
Use HTTP header 'x-hmac: 5372ecca442d65f582866cf3b24cb2b6' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CYEITF6aozBNp7bA54qY0Ugg9v6ktwoH6nqRyatkFqy8
Decrypted: My super secret information.
Use HTTP header 'x-hmac: 5372ecca442d65f582866cf3b24cb2b6' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CYEITF6aozBNp7bA54qY0Ugg9v6ktwoH6nqRyatkFqy8
Decrypted: My super secret information.

Last but not least, if you don't have openssl mod installed in php, you can use mcrypt instead with rijndael128 and pkcs7 padding (source) like this:

最后,如果您没有在php中安装openssl mod,您可以使用mcrypt,而使用rijndael128和pkcs7填充(源):

aes256cbc-mcrypt.php (v2)

aes256cbc-mcrypt。php(v2)

<?php

function pkcs7pad($message) {
    $padding = 16 - (strlen($message) % 16);
    return $message . str_repeat(chr($padding), $padding);
}

function pkcs7unpad($message) {
    $padding = ord(substr($message, -1));  //get last char and transform it to Int
    return substr($message, 0, -$padding); //remove the last 'padding' string
}

function encrypt ($message, $method, $secret, &$hmac) {
    //$iv = substr(bin2hex(mcrypt_create_iv(mcrypt_get_iv_size($method, MCRYPT_MODE_CBC), MCRYPT_DEV_URANDOM)),0,16);    //use this in production
    $iv = substr($secret, 0, 16);    //using this for testing purposes (to have the same encryption IV in PHP and Node encryptors)
    $message = pkcs7pad($message);
    $encrypted = base64_encode($iv) . base64_encode(mcrypt_encrypt($method, $secret, $message, MCRYPT_MODE_CBC, $iv));
    $hmac = hash_hmac('md5', $encrypted, $secret);
    return $encrypted;
}

function decrypt ($encrypted, $method, $secret, $hmac) {
    if (hash_hmac('md5', $encrypted, $secret) == $hmac) {
        $iv = base64_decode(substr($encrypted, 0, 24));
        return pkcs7unpad(mcrypt_decrypt($method, $secret , base64_decode(substr($encrypted, 24)) , MCRYPT_MODE_CBC, $iv));
    }
}

function encryptWithTSValidation ($message, $method, $secret, &$hmac) {
    date_default_timezone_set('UTC');
    $message = substr(date('c'),0,19) . "$message";
    return encrypt($message, $method, $secret, $hmac);
}

function decryptWithTSValidation ($encrypted, $method, $secret, $hmac, $intervalThreshold) {
    $decrypted = decrypt($encrypted, $method, $secret, $hmac);
    $now = new DateTime();
    //echo "Decrypted: $decrypted\n";
    $msgDate = new DateTime(str_replace("T"," ",substr($decrypted,0,19)));
    if (($now->getTimestamp() - $msgDate->getTimestamp()) <= $intervalThreshold) {
        return substr($decrypted,19);
    }
}

$message = "My super secret information.";
$method = MCRYPT_RIJNDAEL_128;
$secret = "My32charPasswordAndInitVectorStr";  //must be 32 char length

//$encrypted = encrypt($message, $method, $secret, $hmac);
//$decrypted = decrypt($encrypted, $method, $secret, $hmac);

$encrypted = encryptWithTSValidation($message, $method, $secret, $hmac);
$decrypted = decryptWithTSValidation($encrypted, $method, $secret, $hmac, 60*60*12); //60*60m*12=12h

echo "Use HTTP header 'x-hmac: $hmac' for validating against MitM-attacks.\n";
echo "Encrypted: $encrypted\n";
echo "Decrypted: $decrypted\n";
?>

Ofcourse, some tests next:

当然,一些测试:

$ php aes256cbc-mcrypt.php && node aes256cbc.js 
Use HTTP header 'x-hmac: 801282a9ed6b2d5bd2254140d7a17582' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v9IyatyGeNT2yebrpJZ5xH73H5fFcV1zhqhRGwM0ToGU
Decrypted: My super secret information.
Use HTTP header 'x-hmac: 801282a9ed6b2d5bd2254140d7a17582' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v9IyatyGeNT2yebrpJZ5xH73H5fFcV1zhqhRGwM0ToGU
Decrypted: My super secret information.
$ php aes256cbc-mcrypt.php && node aes256cbc.js 
Use HTTP header 'x-hmac: 0ab2bc83108e1e250f6ecd483cd65329' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v79P+j4YUl8ln8eu7FDqEdbxMe1Z7BvW8iVUN1qFCiHM
Decrypted: My super secret information.
Use HTTP header 'x-hmac: 0ab2bc83108e1e250f6ecd483cd65329' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v79P+j4YUl8ln8eu7FDqEdbxMe1Z7BvW8iVUN1qFCiHM
Decrypted: My super secret information.

#1


8  

When dealing with symmetric encryption like this, the first step is realize that it's probably going to be a huge pain in the rear - I've never, ever had it work right away, even when I was copy pasting my own code. This is mainly because encryption and decryption methods are, by design, utterly unforgiving and rarely give useful error messages. A single null character, carriage return, line feed, or dynamically converted type can silently blow the whole process up.

当处理像这样的对称加密时,第一步是意识到它可能会是一个巨大的痛苦在后面-我从来没有,曾经有它工作,即使当我拷贝粘贴我自己的代码。这主要是因为加密和解密方法是完全不可原谅的,很少给出有用的错误消息。一个空字符、回车、换行或动态转换类型可以悄无声息地将整个过程放大。

Knowing this, progress stepwise. I suggest the following:

知道了这一点,逐步进步。我建议以下几点:

First, get PHP alone working. Pass in sample text, encrypt it, immediately decrypt it, and compare it with strict equality to the original clear text variable. Are they perfectly the same? Output both, as well - are they the same type and appear perfectly unmolested? Watch out for non-printed characters - check the length and character encoding too!

首先,让PHP单独工作。传入示例文本,对其进行加密,立即对其解密,并将其与原始明文变量的严格相等进行比较。它们完全一样吗?输出都是相同的类型,并且看起来完全不受干扰吗?注意非打印字符-检查长度和字符编码!

Now, do the above with one more sample text that is 1 character more or less than the previous one. This debugs block size/zero-padding issues - it matters.

现在,用一个比前一个字符多或少一个字符的示例文本来完成上述操作。这个调试器块大小/零填充问题—它很重要。

If that's working - and it rarely does right away, for hard to predict reasons, continue to Node.js.

如果这是有效的,而且它很少会立即发生,因为很难预测原因,继续进行。

In Node.js, do the same thing as you did in PHP, even if it seems like wasted effort - for extra reasons that will be obvious in a moment. Encrypt and decrypt, all together, in your Node.js. Does it work with all the same provisos given above?

在节点。js和你在PHP中做的事情一样,即使这看起来像是白费功夫——因为额外的原因会在一瞬间变得明显。在你的Node.js中加密和解密。它是否与上面给出的所有条件相同?

Once that is done, here comes the 'fun' part: using the same encryption methods independently in Node.js and PHP, have them both output to you the 'final' ready-to-transmit cryptext that both produced.

一旦完成,这里就有了“乐趣”部分:在节点中独立使用相同的加密方法。js和PHP,将它们都输出到“最终”的“最终”即传输的加密文本中。

If all is well, they should be perfectly, exactly the same. If they aren't, you have a problem with your encryption implementations and methods not being compatible between systems. Some setting is wrong or conflicting (perhaps with zero padding or a host of other possibilities, or IV, etc), or you need to try a different implementation.

如果一切顺利,它们应该是完美的,完全一样。如果它们不是,那么您的加密实现和方法在系统之间不兼容就有问题。有些设置是错误的或冲突的(可能是零填充或其他可能的情况,或者IV等),或者您需要尝试不同的实现。

If I had to guess blindly, I'd say there is an issue with the base64 encoding and decoding (it's most commonly what goes wrong). Things tend to get done twice, because it can be tricky to debug binary data types in web applications (through a browser). Sometimes things are being encoded twice but only decoded once, or one implementation will 'helpfully' encode/decode something automatically without being clear that's what it's doing, etc.

如果我不得不盲目地猜测,我认为base64编码和解码是有问题的(最常见的是出错)。因为在web应用程序中调试二进制数据类型(通过浏览器)可能很困难,所以事情往往会重复两次。有时候,有些东西被编码了两次,但只有一次被解码,或者一个实现会“帮助”自动编码/解码某些东西,而不清楚这是它在做什么,等等。

It's also possible it's a zero-padding implementation issue between Node and PHP, as suggested here: AES encrypt in Node.js Decrypt in PHP. Fail.

它也可能是节点和PHP之间的零填充实现问题,正如这里所建议的:AES在节点中加密。js解密在PHP。失败。

These last two issues are strongly suggested by your error codes. The encryption methods predict block sizes of precise length, and if they are off then that signals corruption of the data being passed to the functions - which happens if a single extra character slipped in, or if encoding is handled differently, etc.

最后两个问题是由错误代码强烈建议的。加密方法可以预测精确长度的块大小,如果它们断开,就会显示传递给函数的数据的损坏——如果一个额外的字符出现了,或者编码处理的方式不同,就会发生这种情况。

If you step through each of the above one at a time, assuring yourself you can't rush and must check every painstaking tiny little step of the process, it should be much more clear where exactly things are going wrong, and then that can be troubleshooted.

如果你一步一步地完成上面的每一个步骤,确保自己不能匆忙,并且必须检查过程中的每一个细微的步骤,那么就应该清楚地知道哪里出了问题,然后就可以解决问题了。

#2


16  

I was struggling with the same problem this week but in the opposite way (PHP encrypts -> NodeJS decrypts) and had managed to get this snippet working:

我在这个星期遇到了同样的问题,但与此相反的是(PHP加密-> NodeJS解密),并设法让这段代码工作:

aes256cbc.js

aes256cbc.js

var crypto = require('crypto');

var encrypt = function (plain_text, encryptionMethod, secret, iv) {
    var encryptor = crypto.createCipheriv(encryptionMethod, secret, iv);
    return encryptor.update(plain_text, 'utf8', 'base64') + encryptor.final('base64');
};

var decrypt = function (encryptedMessage, encryptionMethod, secret, iv) {
    var decryptor = crypto.createDecipheriv(encryptionMethod, secret, iv);
    return decryptor.update(encryptedMessage, 'base64', 'utf8') + decryptor.final('utf8');
};

var textToEncrypt = new Date().toISOString().substr(0,19) + '|My super secret information.';
var encryptionMethod = 'AES-256-CBC';
var secret = "My32charPasswordAndInitVectorStr"; //must be 32 char length
var iv = secret.substr(0,16);

var encryptedMessage = encrypt(textToEncrypt, encryptionMethod, secret, iv);
var decryptedMessage = decrypt(encryptedMessage, encryptionMethod, secret, iv);

console.log(encryptedMessage);
console.log(decryptedMessage);

aes256cbc.php

aes256cbc.php

<?php
date_default_timezone_set('UTC');
$textToEncrypt = substr(date('c'),0,19) . "|My super secret information.";
$encryptionMethod = "AES-256-CBC";
$secret = "My32charPasswordAndInitVectorStr";  //must be 32 char length
$iv = substr($secret, 0, 16);

$encryptedMessage = openssl_encrypt($textToEncrypt, $encryptionMethod, $secret,0,$iv);
$decryptedMessage = openssl_decrypt($encryptedMessage, $encryptionMethod, $secret,0,$iv);

echo "$encryptedMessage\n";
echo "$decryptedMessage\n";
?>

The secret here to avoid falling in key/iv size/decryption problems is to have the secret of exactly 32 characters length and 16 for the IV. Also, it is VERY important to use 'base64' and 'utf8' in NodeJS since these are the defaults in PHP.

为了避免在密钥/iv的大小/解密问题上出现问题,关键是要有32个字符长度和16个字符的秘密。另外,在NodeJS中使用“base64”和“utf8”是非常重要的,因为这些都是PHP的默认值。

Here are some sample runs:

下面是一些示例运行:

$ node aes256cbc.js && php aes256cbc.php
zra3FX4iyCc7qPc1dZs+G3ZQ40f5bSw8P9n5OtWl1t86nV5Qfh4zNRPFbsciyyHyU3Qi4Ga1oTiTwzrPIZQXLw==
2015-01-27T18:29:12|My super secret information.
zra3FX4iyCc7qPc1dZs+G3ZQ40f5bSw8P9n5OtWl1t86nV5Qfh4zNRPFbsciyyHyU3Qi4Ga1oTiTwzrPIZQXLw==
2015-01-27T18:29:12|My super secret information.

$ node aes256cbc.js && php aes256cbc.php
zra3FX4iyCc7qPc1dZs+G6B6+8aavHNc/Ymv9L6Omod8Di3tMbvOa2B7O2Yiyoutm9fy9l0G+P5VJT9z2qNESA==
2015-01-27T18:29:15|My super secret information.
zra3FX4iyCc7qPc1dZs+G6B6+8aavHNc/Ymv9L6Omod8Di3tMbvOa2B7O2Yiyoutm9fy9l0G+P5VJT9z2qNESA==
2015-01-27T18:29:15|My super secret information.

$ node aes256cbc.js && php aes256cbc.php
zra3FX4iyCc7qPc1dZs+G4oD1Fr5yLByON6QDE56UOqP6kkfGJzpyH6TbwZYX2oGlh2JGv+aHYUMh0qQnAj/uw==
2015-01-27T18:29:29|My super secret information.
zra3FX4iyCc7qPc1dZs+G4oD1Fr5yLByON6QDE56UOqP6kkfGJzpyH6TbwZYX2oGlh2JGv+aHYUMh0qQnAj/uw==
2015-01-27T18:29:29|My super secret information.

$ node aes256cbc.js && php aes256cbc.php
zra3FX4iyCc7qPc1dZs+G5OVCbCaUy8a0LLF+Bn8UT4X3nYbtynO0Zt2mvXnnli9dRxrxMw43uWnkh8MIwVHXA==
2015-01-27T18:29:31|My super secret information.
zra3FX4iyCc7qPc1dZs+G5OVCbCaUy8a0LLF+Bn8UT4X3nYbtynO0Zt2mvXnnli9dRxrxMw43uWnkh8MIwVHXA==
2015-01-27T18:29:31|My super secret information.

$ node aes256cbc.js && php aes256cbc.php
fdsqSyHBJjlwD0jYfOUZM2FrONG6Fk5d7FOItYEdbnaZIhhmg/apa8/jPwKFkDXD9eNqWC3w0JzY5wjtZADiBA==
2015-01-27T18:30:08|My super secret information.
fdsqSyHBJjlwD0jYfOUZM2FrONG6Fk5d7FOItYEdbnaZIhhmg/apa8/jPwKFkDXD9eNqWC3w0JzY5wjtZADiBA==
2015-01-27T18:30:08|My super secret information.

$ node aes256cbc.js && php aes256cbc.php
fdsqSyHBJjlwD0jYfOUZM4SRfi6jG5EoDFEF6d9xCIyluXSiMaKlhd89ovpeOz/YyEIlPbYR4ly00gf6hWfKHw==
2015-01-27T18:30:45|My super secret information.
fdsqSyHBJjlwD0jYfOUZM4SRfi6jG5EoDFEF6d9xCIyluXSiMaKlhd89ovpeOz/YyEIlPbYR4ly00gf6hWfKHw==
2015-01-27T18:30:45|My super secret information.

NOTE:

注意:

I use a "timestamp|message" format to avoid man in the middle attacks. For example, if the encrypted message contains an ID to be authenticated, the MitM could capture the message and re-send it every time he wants to re-authenticate.

我使用“时间戳|消息”格式来避免中间人攻击。例如,如果加密消息包含一个要经过身份验证的ID,那么MitM可以捕获消息并在每次重新进行身份验证时重新发送它。

Therefore, I could check the timestamp on the encrypted message to be within a little time interval. This way, the same message is encrypted differently each second because of the timestamp, and could not be used out of this fixed time interval.

因此,我可以在短时间间隔内检查加密消息的时间戳。这样,由于时间戳,相同的消息会以不同的方式进行加密,并且不能在这个固定的时间间隔内使用。

EDIT:

编辑:

Here I was misusing the Initialization Vector (IV). As @ArtjomB. explained, the IV should be the first part of the encrypted message, and also it should be a random value. It's also recommended to use a hmac value in a HTTP Header (x-hmac: *value*) in order to validate that the message was originated from a valid source (but this does not address the "re-send" message issue previously described).

在这里,我使用了初始化向量(IV),如@ArtjomB。解释,IV应该是加密消息的第一部分,也应该是一个随机值。它还建议在HTTP头(x-hmac: *value*)中使用hmac值,以验证消息是否来自有效的源(但这并没有解决前面描述的“重新发送”消息问题)。

Here's the improved version, including the hmac for php and node and the IV as a part of the encrypted message:

这是改进后的版本,包括用于php和节点的hmac,以及作为加密消息的一部分的IV:

aes256cbc.js (v2)

aes256cbc。js(v2)

var crypto = require('crypto');

var encrypt = function (message, method, secret, hmac) {
    //var iv = crypto.randomBytes(16).toString('hex').substr(0,16);    //use this in production
    var iv = secret.substr(0,16);    //using this for testing purposes (to have the same encryption IV in PHP and Node encryptors)
    var encryptor = crypto.createCipheriv(method, secret, iv);
    var encrypted = new Buffer(iv).toString('base64') + encryptor.update(message, 'utf8', 'base64') + encryptor.final('base64');
    hmac.value = crypto.createHmac('md5', secret).update(encrypted).digest('hex');
    return encrypted;
};

var decrypt = function (encrypted, method, secret, hmac) {
    if (crypto.createHmac('md5', secret).update(encrypted).digest('hex') == hmac.value) {
        var iv = new Buffer(encrypted.substr(0, 24), 'base64').toString();
        var decryptor = crypto.createDecipheriv(method, secret, iv);
        return decryptor.update(encrypted.substr(24), 'base64', 'utf8') + decryptor.final('utf8');
    }
};

var encryptWithTSValidation = function (message, method, secret, hmac) {
    var messageTS = new Date().toISOString().substr(0,19) + message;
    return encrypt(messageTS, method, secret, hmac);
}

var decryptWithTSValidation = function (encrypted, method, secret, hmac, intervalThreshold) {
    var decrypted = decrypt(encrypted, method, secret, hmac);
    var now = new Date();
    var year = parseInt(decrypted.substr(0,4)), month = parseInt(decrypted.substr(5,2)) - 1,
    day = parseInt(decrypted.substr(8,2)), hour = parseInt(decrypted.substr(11,2)), 
    minute = parseInt(decrypted.substr(14,2)), second = parseInt(decrypted.substr(17,2));
    var msgDate = new Date(Date.UTC(year, month, day, hour, minute, second))
    if (Math.round((now - msgDate) / 1000) <= intervalThreshold) {
        return decrypted.substr(19);
    }
}

var message = 'My super secret information.';
var method = 'AES-256-CBC';
var secret = "My32charPasswordAndInitVectorStr"; //must be 32 char length
var hmac = {};

//var encrypted = encrypt(message, method, secret, hmac);
//var decrypted = decrypt(encrypted, method, secret, hmac);
var encrypted = encryptWithTSValidation(message, method, secret, hmac);
var decrypted = decryptWithTSValidation(encrypted, method, secret, hmac, 60*60*12); //60*60m*12=12h

console.log("Use HTTP header 'x-hmac: " + hmac.value + "' for validating against MitM-attacks.");
console.log("Encrypted: " + encrypted);
console.log("Decrypted: " + decrypted);

Note that crypto.createHmac(...).digest('hex') is digested with hex. This is the default in PHP for hmac.

请注意,密码。createhmac(…).digest('hex')被hex消化。这是hmac的PHP默认值。

aes256cbc.php (v2)

aes256cbc。php(v2)

<?php

function encrypt ($message, $method, $secret, &$hmac) {
    //$iv = substr(bin2hex(openssl_random_pseudo_bytes(16)),0,16);    //use this in production
    $iv = substr($secret, 0, 16);        //using this for testing purposes (to have the same encryption IV in PHP and Node encryptors)
    $encrypted = base64_encode($iv) . openssl_encrypt($message, $method, $secret, 0, $iv);
    $hmac = hash_hmac('md5', $encrypted, $secret);
    return $encrypted;
}

function decrypt ($encrypted, $method, $secret, $hmac) {
    if (hash_hmac('md5', $encrypted, $secret) == $hmac) {
        $iv = base64_decode(substr($encrypted, 0, 24));
        return openssl_decrypt(substr($encrypted, 24), $method, $secret, 0, $iv);
    }
}

function encryptWithTSValidation ($message, $method, $secret, &$hmac) {
    date_default_timezone_set('UTC');
    $message = substr(date('c'),0,19) . "$message";
    return encrypt($message, $method, $secret, $hmac);
}

function decryptWithTSValidation ($encrypted, $method, $secret, $hmac, $intervalThreshold) {
    $decrypted = decrypt($encrypted, $method, $secret, $hmac);
    $now = new DateTime();
    $msgDate = new DateTime(str_replace("T"," ",substr($decrypted,0,19)));
    if (($now->getTimestamp() - $msgDate->getTimestamp()) <= $intervalThreshold) {
        return substr($decrypted,19);
    }
}

$message = "My super secret information.";
$method = "AES-256-CBC";
$secret = "My32charPasswordAndInitVectorStr";  //must be 32 char length

//$encrypted = encrypt($message, $method, $secret, $hmac);
//$decrypted = decrypt($encrypted, $method, $secret, $hmac);

$encrypted = encryptWithTSValidation($message, $method, $secret, $hmac);
$decrypted = decryptWithTSValidation($encrypted, $method, $secret, $hmac, 60*60*12); //60*60m*12=12h

echo "Use HTTP header 'x-hmac: $hmac' for validating against MitM-attacks.\n";
echo "Encrypted: $encrypted\n";
echo "Decrypted: $decrypted\n";
?>

Here are some sample runs:

下面是一些示例运行:

$ node aes256cbc.js && php aes256cbc.php
Use HTTP header 'x-hmac: 6862972ef0f463bf48523fc9e334bb42' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==I6cAKeoxeSP5TGgtK59PotB/iG2BUSU8Y6NhAhVabN9UB+ZCTn7q2in4JyLwQiGN
Decrypted: My super secret information.
Use HTTP header 'x-hmac: 6862972ef0f463bf48523fc9e334bb42' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==I6cAKeoxeSP5TGgtK59PotB/iG2BUSU8Y6NhAhVabN9UB+ZCTn7q2in4JyLwQiGN
Decrypted: My super secret information.

$ node aes256cbc.js && php aes256cbc.php
Use HTTP header 'x-hmac: b2e63f216acde938a82142220652cf59' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CSgYBBR8dkZytORm8xwEDmD9WB1mpqC3XnSrB+wR3/KW
Decrypted: My super secret information.
Use HTTP header 'x-hmac: b2e63f216acde938a82142220652cf59' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CSgYBBR8dkZytORm8xwEDmD9WB1mpqC3XnSrB+wR3/KW
Decrypted: My super secret information.

$ node aes256cbc.js && php aes256cbc.php
Use HTTP header 'x-hmac: 73181744453d55eb6f81896ffd284cd8' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CTGik4Lv9PnWuEg5SiADJcdKX1to0LrNKmuCiYIweBAZ
Decrypted: My super secret information.
Use HTTP header 'x-hmac: 73181744453d55eb6f81896ffd284cd8' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CTGik4Lv9PnWuEg5SiADJcdKX1to0LrNKmuCiYIweBAZ
Decrypted: My super secret information.

$ node aes256cbc.js && php aes256cbc.php
Use HTTP header 'x-hmac: 5372ecca442d65f582866cf3b24cb2b6' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CYEITF6aozBNp7bA54qY0Ugg9v6ktwoH6nqRyatkFqy8
Decrypted: My super secret information.
Use HTTP header 'x-hmac: 5372ecca442d65f582866cf3b24cb2b6' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CYEITF6aozBNp7bA54qY0Ugg9v6ktwoH6nqRyatkFqy8
Decrypted: My super secret information.

Last but not least, if you don't have openssl mod installed in php, you can use mcrypt instead with rijndael128 and pkcs7 padding (source) like this:

最后,如果您没有在php中安装openssl mod,您可以使用mcrypt,而使用rijndael128和pkcs7填充(源):

aes256cbc-mcrypt.php (v2)

aes256cbc-mcrypt。php(v2)

<?php

function pkcs7pad($message) {
    $padding = 16 - (strlen($message) % 16);
    return $message . str_repeat(chr($padding), $padding);
}

function pkcs7unpad($message) {
    $padding = ord(substr($message, -1));  //get last char and transform it to Int
    return substr($message, 0, -$padding); //remove the last 'padding' string
}

function encrypt ($message, $method, $secret, &$hmac) {
    //$iv = substr(bin2hex(mcrypt_create_iv(mcrypt_get_iv_size($method, MCRYPT_MODE_CBC), MCRYPT_DEV_URANDOM)),0,16);    //use this in production
    $iv = substr($secret, 0, 16);    //using this for testing purposes (to have the same encryption IV in PHP and Node encryptors)
    $message = pkcs7pad($message);
    $encrypted = base64_encode($iv) . base64_encode(mcrypt_encrypt($method, $secret, $message, MCRYPT_MODE_CBC, $iv));
    $hmac = hash_hmac('md5', $encrypted, $secret);
    return $encrypted;
}

function decrypt ($encrypted, $method, $secret, $hmac) {
    if (hash_hmac('md5', $encrypted, $secret) == $hmac) {
        $iv = base64_decode(substr($encrypted, 0, 24));
        return pkcs7unpad(mcrypt_decrypt($method, $secret , base64_decode(substr($encrypted, 24)) , MCRYPT_MODE_CBC, $iv));
    }
}

function encryptWithTSValidation ($message, $method, $secret, &$hmac) {
    date_default_timezone_set('UTC');
    $message = substr(date('c'),0,19) . "$message";
    return encrypt($message, $method, $secret, $hmac);
}

function decryptWithTSValidation ($encrypted, $method, $secret, $hmac, $intervalThreshold) {
    $decrypted = decrypt($encrypted, $method, $secret, $hmac);
    $now = new DateTime();
    //echo "Decrypted: $decrypted\n";
    $msgDate = new DateTime(str_replace("T"," ",substr($decrypted,0,19)));
    if (($now->getTimestamp() - $msgDate->getTimestamp()) <= $intervalThreshold) {
        return substr($decrypted,19);
    }
}

$message = "My super secret information.";
$method = MCRYPT_RIJNDAEL_128;
$secret = "My32charPasswordAndInitVectorStr";  //must be 32 char length

//$encrypted = encrypt($message, $method, $secret, $hmac);
//$decrypted = decrypt($encrypted, $method, $secret, $hmac);

$encrypted = encryptWithTSValidation($message, $method, $secret, $hmac);
$decrypted = decryptWithTSValidation($encrypted, $method, $secret, $hmac, 60*60*12); //60*60m*12=12h

echo "Use HTTP header 'x-hmac: $hmac' for validating against MitM-attacks.\n";
echo "Encrypted: $encrypted\n";
echo "Decrypted: $decrypted\n";
?>

Ofcourse, some tests next:

当然,一些测试:

$ php aes256cbc-mcrypt.php && node aes256cbc.js 
Use HTTP header 'x-hmac: 801282a9ed6b2d5bd2254140d7a17582' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v9IyatyGeNT2yebrpJZ5xH73H5fFcV1zhqhRGwM0ToGU
Decrypted: My super secret information.
Use HTTP header 'x-hmac: 801282a9ed6b2d5bd2254140d7a17582' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v9IyatyGeNT2yebrpJZ5xH73H5fFcV1zhqhRGwM0ToGU
Decrypted: My super secret information.
$ php aes256cbc-mcrypt.php && node aes256cbc.js 
Use HTTP header 'x-hmac: 0ab2bc83108e1e250f6ecd483cd65329' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v79P+j4YUl8ln8eu7FDqEdbxMe1Z7BvW8iVUN1qFCiHM
Decrypted: My super secret information.
Use HTTP header 'x-hmac: 0ab2bc83108e1e250f6ecd483cd65329' for validating against MitM-attacks.
Encrypted: YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v79P+j4YUl8ln8eu7FDqEdbxMe1Z7BvW8iVUN1qFCiHM
Decrypted: My super secret information.