在 PHP 中加密字符串并在 Node.js 中解密 [英] Encrypt string in PHP and decrypt in Node.js

查看:30
本文介绍了在 PHP 中加密字符串并在 Node.js 中解密的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我在 PHP 中尝试过:

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

在 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');

除了错误的解密数据外,我还收到如下错误:

TypeError:错误:0606508A:数字信封例程:EVP_DecryptFinal_ex:数据不是块长度的倍数类型错误:错误:0606506D:数字信封例程:EVP_DecryptFinal_ex:错误的最终块长度

我需要一个简单的加密,因为数据不太敏感(没有密码或用户数据),但是数据只能由接收者读取.密钥长度可以任意,但加密/解密的过程必须尽可能简单,请不要使用IV.

解决方案

我这周遇到了同样的问题,但以相反的方式(PHP 加密 -> NodeJS 解密)并设法使此代码段正常工作:

aes256cbc.js

var crypto = require('crypto');var encrypt = 函数(纯文本、加密方法、秘密、iv){var encryptor = crypto.createCipheriv(encryptionMethod, secret, iv);返回 encryptor.update(plain_text, 'utf8', 'base64') + encryptor.final('base64');};var 解密 = 函数(encryptedMessage、encryptionMethod、secret、iv){var 解密器 = crypto.createDecipheriv(encryptionMethod, secret, iv);返回解密器.更新(加密消息,'base64','utf8')+decryptor.final('utf8');};var textToEncrypt = new Date().toISOString().substr(0,19) + '|我的超级机密信息.';var encryptionMethod = 'AES-256-CBC';var secret = "My32charPasswordAndInitVectorStr";//必须是32个字符长度var iv = secret.substr(0,16);var encryptedMessage = encrypt(textToEncrypt, encryptionMethod, secret, iv);变量解密消息 = 解密(加密消息,加密方法,秘密,iv);控制台日志(加密消息);控制台日志(解密消息);

aes256cbc.php

这里避免陷入密钥/iv 大小/解密问题的秘诀是拥有恰好 32 个字符长度和 16 个字符长度的秘诀用于 IV.此外,在 NodeJS 中使用base64"和utf8"非常很重要,因为这些是 PHP 中的默认值.

以下是一些示例运行:

$ node aes256cbc.js &&php aes256cbc.phpzra3FX4iyCc7qPc1dZs+G3ZQ40f5bSw8P9n5OtWl1t86nV5Qfh4zNRPFbsciyyHyU3Qi4Ga1oTiTwzrPIZQXLw==2015-01-27T18:29:12|我的超级机密信息.zra3FX4iyCc7qPc1dZs+G3ZQ40f5bSw8P9n5OtWl1t86nV5Qfh4zNRPFbsciyyHyU3Qi4Ga1oTiTwzrPIZQXLw==2015-01-27T18:29:12|我的超级机密信息.$ 节点 aes256cbc.js &&php aes256cbc.phpzra3FX4iyCc7qPc1dZs+G6B6+8aavHNc/Ymv9L6Omod8Di3tMbvOa2B7O2Yiyoutm9fy9l0G+P5VJT9z2qNESA==2015-01-27T18:29:15|我的超级机密信息.zra3FX4iyCc7qPc1dZs+G6B6+8aavHNc/Ymv9L6Omod8Di3tMbvOa2B7O2Yiyoutm9fy9l0G+P5VJT9z2qNESA==2015-01-27T18:29:15|我的超级机密信息.$ 节点 aes256cbc.js &&php aes256cbc.phpzra3FX4iyCc7qPc1dZs+G4oD1Fr5yLByON6QDE56UOqP6kkfGJzpyH6TbwZYX2oGlh2JGv+aHYUMh0qQnAj/uw==2015-01-27T18:29:29|我的超级机密信息.zra3FX4iyCc7qPc1dZs+G4oD1Fr5yLByON6QDE56UOqP6kkfGJzpyH6TbwZYX2oGlh2JGv+aHYUMh0qQnAj/uw==2015-01-27T18:29:29|我的超级机密信息.$ 节点 aes256cbc.js &&php aes256cbc.phpzra3FX4iyCc7qPc1dZs+G5OVCbCaUy8a0LLF+Bn8UT4X3nYbtynO0Zt2mvXnnli9dRxrxMw43uWnkh8MIwVHXA==2015-01-27T18:29:31|我的超级机密信息.zra3FX4iyCc7qPc1dZs+G5OVCbCaUy8a0LLF+Bn8UT4X3nYbtynO0Zt2mvXnnli9dRxrxMw43uWnkh8MIwVHXA==2015-01-27T18:29:31|我的超级机密信息.$ 节点 aes256cbc.js &&php aes256cbc.phpfdsqSyHBJjlwD0jYfOUZM2FrONG6Fk5d7FOItYEdbnaZIhhmg/apa8/jPwKFkDXD9eNqWC3w0JzY5wjtZADiBA==2015-01-27T18:30:08|我的超级机密信息.fdsqSyHBJjlwD0jYfOUZM2FrONG6Fk5d7FOItYEdbnaZIhhmg/apa8/jPwKFkDXD9eNqWC3w0JzY5wjtZADiBA==2015-01-27T18:30:08|我的超级机密信息.$ 节点 aes256cbc.js &&php aes256cbc.phpfdsqSyHBJjlwD0jYfOUZM4SRfi6jG5EoDFEF6d9xCIyluXSiMaKlhd89ovpeOz/YyEIlPbYR4ly00gf6hWfKHw==2015-01-27T18:30:45|我的超级机密信息.fdsqSyHBJjlwD0jYfOUZM4SRfi6jG5EoDFEF6d9xCIyluXSiMaKlhd89ovpeOz/YyEIlPbYR4ly00gf6hWfKHw==2015-01-27T18:30:45|我的超级机密信息.

注意:

我使用时间戳|消息"格式来避免中间人攻击.例如,如果加密的消息包含要验证的 ID,则 MitM 可以捕获该消息并在他每次想要重新验证时重新发送.

因此,我可以检查加密消息上的时间戳是否在一个很小的时间间隔内.这样一来,由于时间戳的原因,同一条消息每秒都会以不同的方式加密,并且无法在此固定时间间隔之外使用.

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

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

aes256cbc.js (v2)

var crypto = require('crypto');var encrypt = 函数(消息、方法、秘密、hmac){//var iv = crypto.randomBytes(16).toString('hex').substr(0,16);//在生产中使用它var iv = secret.substr(0,16);//将此用于测试目的(在 PHP 和 Node 加密器中具有相同的加密 IV)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');返回加密;};var 解密 = 函数(加密、方法、秘密、hmac){if (crypto.createHmac('md5', secret).update(encrypted).digest('hex') == hmac.value) {var iv = new Buffer(encrypted.substr(0, 24), 'base64').toString();var 解密器 = crypto.createDecipheriv(method, secret, iv);返回decryptor.update(encrypted.substr(24), 'base64', 'utf8') +decryptor.final('utf8');}};var encryptWithTSValidation = 函数(消息、方法、秘密、hmac){var messageTS = new Date().toISOString().substr(0,19) + message;返回加密(messageTS,方法,秘密,hmac);}vardecryptWithTSValidation = 函数(加密,方法,秘密,hmac,intervalThreshold){var 解密 = 解密(加密,方法,秘密,hmac);var now = new Date();var year = parseInt(decrypted.substr(0,4)), month = parseInt(decrypted.substr(5,2)) - 1,天 = parseInt(decrypted.substr(8,2)), 小时 = parseInt(decrypted.substr(11,2)),分钟 = parseInt(decrypted.substr(14,2)), second = parseInt(decrypted.substr(17,2));var msgDate = new Date(Date.UTC(年、月、日、时、分、秒))if (Math.round((now - msgDate)/1000) <= intervalThreshold) {返回解密.substr(19);}}var message = '我的超级机密信息.';var 方法 = 'AES-256-CBC';var secret = "My32charPasswordAndInitVectorStr";//必须是32个字符长度var hmac = {};//var encrypted = encrypt(message, method, secret, hmac);//var 解密 = 解密(加密,方法,秘密,hmac);var encrypted = encryptWithTSValidation(message, method, secret, hmac);var解密=decryptWithTSValidation(加密,方法,秘密,hmac,60*60*12);//60*60m*12=12hconsole.log("使用 HTTP 标头 'x-hmac:" + hmac.value + "' 来验证中间人攻击.");console.log("加密:" + 加密);console.log("解密:" + 解密);

请注意,crypto.createHmac(...).digest('hex') 是用 hex 消化的.这是 hmac 在 PHP 中的默认设置.

aes256cbc.php (v2)

getTimestamp() - $msgDate->getTimestamp()) <= $intervalThreshold) {返回 substr($decrypted,19);}}$message = "我的超级机密信息.";$method = "AES-256-CBC";$secret = "My32charPasswordAndInitVectorStr";//必须是32个字符长度//$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=12hecho "使用 HTTP 标头 'x-hmac: $hmac' 来验证对抗中间人攻击.
";echo "加密: $encrypted
";echo "解密: $decrypted
";?>

以下是一些示例运行:

$ node aes256cbc.js &&php aes256cbc.php使用 HTTP 标头 'x-hmac: 6862972ef0f463bf48523fc9e334bb42' 来验证对抗中间件攻击.加密:YjE0ZzNyMHNwVm50MGswbQ==I6cAKeoxeSP5TGgtK59PotB/iG2BUSU8Y6NhAhVabN9UB+ZCTn7q2in4JyLwQiGN解密:我的超级秘密信息.使用 HTTP 标头 'x-hmac: 6862972ef0f463bf48523fc9e334bb42' 来验证对抗中间件攻击.加密:YjE0ZzNyMHNwVm50MGswbQ==I6cAKeoxeSP5TGgtK59PotB/iG2BUSU8Y6NhAhVabN9UB+ZCTn7q2in4JyLwQiGN解密:我的超级秘密信息.$ 节点 aes256cbc.js &&php aes256cbc.php使用 HTTP 标头 'x-hmac: b2e63f216acde938a82142220652cf59' 来验证中间人攻击.加密:YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCluCk7Yg+U+S1CSgYBBR8dkZytORm8xwEDmD9WB1mpqC3XnSrB+wR3/KW解密:我的超级秘密信息.使用 HTTP 标头 'x-hmac: b2e63f216acde938a82142220652cf59' 来验证中间人攻击.加密:YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCluCk7Yg+U+S1CSgYBBR8dkZytORm8xwEDmD9WB1mpqC3XnSrB+wR3/KW解密:我的超级秘密信息.$ 节点 aes256cbc.js &&php aes256cbc.php使用 HTTP 标头 'x-hmac: 73181744453d55eb6f81896ffd284cd8' 来验证对抗中间件攻击.加密:YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CTGik4Lv9PnWuEg5SiADJcdKX1to0LrNKmuCiYIweBAZ解密:我的超级秘密信息.使用 HTTP 标头 'x-hmac: 73181744453d55eb6f81896ffd284cd8' 来验证对抗中间件攻击.加密:YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CTGik4Lv9PnWuEg5SiADJcdKX1to0LrNKmuCiYIweBAZ解密:我的超级秘密信息.$ 节点 aes256cbc.js &&php aes256cbc.php使用 HTTP 标头 'x-hmac: 5372ecca442d65f582866cf3b24cb2b6' 来验证对抗中间件攻击.加密:YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CYEITF6aozBNp7bA54qY0Ugg9v6ktwoH6nqRyatkFqy8解密:我的超级秘密信息.使用 HTTP 标头 'x-hmac: 5372ecca442d65f582866cf3b24cb2b6' 来验证对抗中间件攻击.加密:YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CYEITF6aozBNp7bA54qY0Ugg9v6ktwoH6nqRyatkFqy8解密:我的超级秘密信息.

最后但并非最不重要的是,如果您没有在 php 中安装 openssl mod,您可以使用 mcrypt 代替 rijndael128pkcs7像这样填充(source):

aes256cbc-mcrypt.php (v2)

getTimestamp() - $msgDate->getTimestamp()) <= $intervalThreshold) {返回 substr($decrypted,19);}}$message = "我的超级机密信息.";$method = MCRYPT_RIJNDAEL_128;$secret = "My32charPasswordAndInitVectorStr";//必须是32个字符长度//$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=12hecho "使用 HTTP 标头 'x-hmac: $hmac' 来验证对抗中间人攻击.
";echo "加密: $encrypted
";echo "解密: $decrypted
";?>

当然,接下来进行一些测试:

$ php aes256cbc-mcrypt.php &&节点 aes256cbc.js使用 HTTP 标头 'x-hmac: 801282a9ed6b2d5bd2254140d7a17582' 来验证中间人攻击.加密:YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v9IyatyGeNT2yebrpJZ5xH73H5fFcV1zhqhRGwM0ToGU解密:我的超级秘密信息.使用 HTTP 标头 'x-hmac: 801282a9ed6b2d5bd2254140d7a17582' 来验证中间人攻击.加密:YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v9IyatyGeNT2yebrpJZ5xH73H5fFcV1zhqhRGwM0ToGU解密:我的超级秘密信息.$ php aes256cbc-mcrypt.php &&节点 aes256cbc.js使用 HTTP 标头 'x-hmac: 0ab2bc83108e1e250f6ecd483cd65329' 来验证中间人攻击.加密:YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v79P+j4YUl8ln8eu​​7FDqEdbxMe1Z7BvW8iVUN1qFCiHM解密:我的超级秘密信息.使用 HTTP 标头 'x-hmac: 0ab2bc83108e1e250f6ecd483cd65329' 来验证中间人攻击.加密:YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v79P+j4YUl8ln8eu​​7FDqEdbxMe1Z7BvW8iVUN1qFCiHM解密:我的超级秘密信息.

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..

I tried this in 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:

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.

解决方案

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:

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

<?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
";
echo "$decryptedMessage
";
?>

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.

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.

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).

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

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.

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.
";
echo "Encrypted: $encrypted
";
echo "Decrypted: $decrypted
";
?>

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:

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
";
    $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.
";
echo "Encrypted: $encrypted
";
echo "Decrypted: $decrypted
";
?>

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.

这篇关于在 PHP 中加密字符串并在 Node.js 中解密的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆