我正在通过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'); 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
我需要简单的加密,因为数据不太敏感(没有密码或用户数据),但是数据只能由收件人读取。密钥长度可以是任何长度,但是加密/解密的过程必须尽可能简单,请不要使用IV。
我本周在同一个问题上苦苦挣扎,但是却采用相反的方式(PHP加密-> 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个IV的长度。另外,在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.
注意:
我使用“ 时间戳 | 消息 ”格式来避免 中间人的攻击 。例如,如果加密的消息包含要验证的ID,则MitM可以捕获该消息,并在每次他想要重新验证时将其重新发送。
因此,我可以检查加密邮件上的时间戳是否在较短的时间间隔内。这样,同一条消息由于时间戳而每秒进行不同的加密,因此无法在此固定时间间隔内使用。
编辑:
在这里,我在滥用初始化向量(IV)。作为 @ArtjomB。 解释说,IV应该是加密消息的第一部分,也应该是随机值。还建议hmac在HTTP标头(x-hmac: *value*)中使用一个值,以验证消息是否源自有效来源(但这不能解决前面所述的“重新发送”消息问题)。
hmac
x-hmac: *value*
这是改进的版本,包括hmac用于php和node以及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。这是PHP中的默认设置hmac。
crypto.createHmac(...).digest('hex')
hex
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与rijndael128and pkcs7padding(source)结合使用,如下所示:
mcrypt
rijndael128
pkcs7
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"; ?>
当然,接下来要进行一些测试:
$ 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.