场景是现有的几个项目,微信企业号是PHP,广告数据处理平台是Python,其中的Web API部分是NodeJS。现在这几个应用之间要传递数据,基于安全考虑先用AES加密,接收后做解密处理。本来预想是一个很简单的工作,库都是现成的,但发现网上的代码要么是不全,要么是padding处理不一致,所以最后还是自己看文档来写的,分享其中的核心代码,有类似需求可以直接拿去用。
AES
AES的介绍可以参看Wikipedia: 高级加密标准。这种加密方式需要指定Key(密钥)和IV(初始化向量),解密时使用同样的Key和IV进行解密。其次需要padding(填充字符),网上一些代码是用空格或者大括号做padding,解密后再用rstrip/rtrim/replace清掉,但用标准的PKCS会更好。
- 使用256位的AES,Python会根据传入的Key长度自动选择,在PHP5在mcrypt里是MCRYPT_RIJNDAEL_128,Nodejs/PHP7.1是aes-256-cbc。
- 使用AES的CBC模式,因为ECB模式用不到IV。
- 使用PKCS的方式来padding,因为NodeJS的库在auto_padding的状态下使用的也是PKCS。Key用AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA(32个,256位),IV用AAAAAAAAAAAAAAAA(16个,128位)。
- Python使用pycrypto(pip install pycrypto),NodeJS使用crypto(npm install crypto),PHP需要mcrypt模块。
Python
from Crypto.Cipher import AES
import base64
def _pad(s): return s + (AES.block_size - len(s) % AES.block_size) * chr(AES.block_size - len(s) % AES.block_size)
def _cipher():
key = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
iv = 'AAAAAAAAAAAAAAAA'
return AES.new(key=key, mode=AES.MODE_CBC, IV=iv)
def encrypt_token(data):
return _cipher().encrypt(_pad(data))
def decrypt_token(data):
return _cipher().decrypt(data)
if __name__ == '__main__':
print('Python encrypt: ' + base64.b64encode(encrypt_token('dmyz.org')))
print('Python decrypt: ' + decrypt_token(base64.b64decode('FSfhJ/gk3iEJOPVLyFVc2Q==')))
Javascript(NodeJS)
var crypto = require('crypto'),
key = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
iv = 'AAAAAAAAAAAAAAAA';
function encrypt_token(data) {
var cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
cipher.update(data, 'binary', 'base64');
return cipher.final('base64');
}
function decrypt_token(data) {
var decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
decipher.update(data, 'base64', 'binary');
return decipher.final('binary');
}
console.log('NodeJS encrypt: ', encrypt_token('dmyz.org'));
console.log('NodeJS decrypt: ', decrypt_token('FSfhJ/gk3iEJOPVLyFVc2Q=='));
// NodeJS V0.8+ https://github.com/joyent/node/wiki/Api-changes-between-v0.8-and-v0.10
var crypto = require('crypto'),
key = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
iv = 'AAAAAAAAAAAAAAAA';
function encrypt_token(data) {
var encipher = crypto.createCipheriv('aes-256-cbc', key, iv),
buffer = Buffer.concat([
encipher.update(data),
encipher.final()
]);
return buffer.toString('base64');
}
function decrypt_token(data) {
var decipher = crypto.createDecipheriv('aes-256-cbc', key, iv),
buffer = Buffer.concat([
decipher.update(Buffer.from(data, 'base64')),
decipher.final()
]);
return buffer.toString();
}
console.log('NodeJS encrypt: ', encrypt_token('dmyz.org'));
console.log('NodeJS decrypt: ', decrypt_token('FSfhJ/gk3iEJOPVLyFVc2Q=='));
PHP
<?php
class AES
{
var $key = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
var $iv = 'AAAAAAAAAAAAAAAA';
function encryptToken($data)
{
$padding = 16 - (strlen($data) % 16);
$data .= str_repeat(chr($padding), $padding);
return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->key, $data, MCRYPT_MODE_CBC, $this->iv);
}
function decryptToken($data)
{
$data = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $this->key, base64_decode($data), MCRYPT_MODE_CBC, $this->iv);
$padding = ord($data[strlen($data) - 1]);
return substr($data, 0, -$padding);
}
}
if (php_sapi_name() === 'cli')
{
$aes = new AES();
echo ('PHP encrypt: '.base64_encode($aes->encryptToken('dmyz.org')))."\n";
echo ('PHP decrypt: '.$aes->decryptToken('FSfhJ/gk3iEJOPVLyFVc2Q=='))."\n";
}
Testing
环境: Ubuntu 14.04.2, Python 2.7.6, NodeJS v0.10.25, PHP 5.5.9
python test.py && nodejs test.js && php test.php
# Python encrypt: FSfhJ/gk3iEJOPVLyFVc2Q==
# Python decrypt: dmyz.org
# Nodejs encrypt: FSfhJ/gk3iEJOPVLyFVc2Q==
# Nodejs decrypt: dmyz.org
# PHP encrypt: FSfhJ/gk3iEJOPVLyFVc2Q==
# PHP decrypt: dmyz.org
解决了,楼主写的不对。。加解密都要将update和final 连起来才对。比如
Buffer.concat([
cipher.update(data, ‘utf-8’),
cipher.final()
]).toString(“hex”)
楼主贴的nodejs的代码,当加密文本长度大于15就解密失败了,能帮忙看下为啥么,不是很懂加密,谢谢
这段代码看了很多遍,才发现走进了一个大坑。php7.1 如何解决兼容问题
php文档上一直写有,之后会停止mcrypt_encrypt/decrypt。用openssl代替,比如解密: openssl_decrypt(base64_decode($data), ‘aes-256-cbc’, $this->key, OPENSSL_RAW_DATA, $this->iv);