Python Tech

Python,Javascript(NodeJS),PHP之间的AES加密解密

场景是现有的几个项目,微信企业号是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
0 0 投票数
文章评分
订阅评论
提醒
guest

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

4 评论
最新
最旧 最多投票
内联反馈
查看所有评论
bajian
bajian
5 年 前

解决了,楼主写的不对。。加解密都要将update和final 连起来才对。比如
Buffer.concat([
cipher.update(data, ‘utf-8’),
cipher.final()
]).toString(“hex”)

bajian
bajian
5 年 前

楼主贴的nodejs的代码,当加密文本长度大于15就解密失败了,能帮忙看下为啥么,不是很懂加密,谢谢

恋羽
6 年 前

这段代码看了很多遍,才发现走进了一个大坑。php7.1 如何解决兼容问题