在PHP中encryptionstring并在Node.js中解密

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

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

在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: 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 

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

在处理这种对称encryption的时候,第一步就是意识到这可能是后面的一个巨大的痛苦 – 即使当我复制粘贴自己的代码时,我也从来没有马上使用它。 这主要是因为encryption和解密方法在devise上是完全不可原谅的,而且很less提供有用的错误消息。 单个空字符,回车符,换行符或dynamic转换types可以默默地吹响整个过程。

知道这一点,逐步进步。 我build议如下:

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

现在,再做一个比上一个更多或更less的1个字符的示例文本。 这debugging块大小/零填充问题 – 重要的。

如果这是行得通的 – 而且很less做,那么很难预测原因,继续Node.js。

在Node.js中,就像在PHP中做的一样,即使看起来像浪费了精力 – 因为额外的原因,在一瞬间就会显而易见。 encryption和解密,一起,在您的Node.js。 它是否与上面给出的所有相同条件一起工作?

一旦完成了,下面是“有趣”的部分:在Node.js和PHP中独立使用相同的encryption方法,让它们都输出给你两个都产生的“最终”准备发送的密文。

如果一切顺利的话,他们应该是完全一样的。 如果他们不是,那么你的encryption实现和方法在系统之间就不兼容了。 某些设置是错误的或者是冲突的(可能是零填充或者其他一些可能性,或者IV等),或者你需要尝试一个不同的实现。

如果我不得不盲目猜测,我会说base64编码和解码有问题(最常见的是什么错误)。 事情往往会得到两次,因为在Web应用程序(通过浏览器)debugging二进制数据types可能会非常棘手。 有时候事情会被编码两次,但是只能被解码一次,或者一个实现会自动地对某些东西进行编码/解码,而不会清楚它正在做什么等等。

这也可能是Node和PHP之间的零填充实现问题,如下所示: 在Node.js中进行AESencryption在PHP中解密。 失败。

最后两个问题强烈build议您的错误代码。 encryption方法可以预测精确长度的块大小,如果它们是closures的,则表明数据正在传递给函数的损坏 – 如果单个额外字符滑入,或者如果编码处理不同,则会发生这种情况。

如果你一次一步地完成上述任何一个步骤,保证自己不能急于求成,并且必须检查每个过程中的每一个艰难的小步骤,应该更清楚地知道哪里出错了,然后可以troubleshooted。

本周我也遇到了同样的问题,但是反过来(PHPencryption – > NodeJS解密),并设法得到这个代码片段:

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

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

以下是一些示例运行:

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

注意:

我使用“ timestamp | message ”格式来避免中间人的攻击 。 例如,如果encryption的消息包含要authentication的ID,则MitM可以捕获该消息并在每次他想重新authentication时重新发送该消息。

因此,我可以检查encryption消息的时间戳在一段时间内。 这样,由于时间戳,相同的消息每秒都以不同的方式进行encryption,并且不能在固定的时间间隔内使用。

编辑:

这里我滥用了初始化向量(IV)。 作为@ArtjomB。 解释说,IV应该是encryption消息的第一部分,也应该是一个随机值。 还build议在HTTP标头( x-hmac: *value* )中使用hmac值以validation消息是否源自有效源(但这不解决先前描述的“重新发送”消息问题)。

以下是改进后的版本,包括php和node的hmac以及作为encryption消息的一部分的IV:

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

注意crypto.createHmac(...).digest('hex')是用hex消解的。 这是hmac在PHP中的默认值。

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"; ?> 

以下是一些示例运行:

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

最后但并非最不重要的一点,如果你没有在php中安装openssl mod,你可以使用mcrypt代替rijndael128pkcs7填充( 源代码 ),如下所示:

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"; ?> 

当然,接下来的一些testing:

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