python和js的AES-PKCS7对称加密解密

之前由于时间关系一直把前后端传输数据的加密解密搁在一旁,如今框架和功能模块的进度也远超前端,终于腾出了时间来做这一块东西。
基于安全考虑采用AES 256 CBC PKCS#7这种安全的对称加密算法,密钥256位,工业级、速度快。

AES的介绍可以参看Wikipedia: 高级加密标准。这种加密方式需要指定Key(密钥)和IV(初始化向量),解密时使用同样的Key和IV进行解密。其次需要padding(填充字符),网上一些代码是用空格或者大括号做padding,解密后再用rstrip/rtrim/replace清掉,但用标准的pkcs会更好。

Python使用pycrypto(pip install pycrypto),NodeJS使用crypto(npm install crypto)。

pkcs7源文件:

pkcs7.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from Crypto.Cipher import AES

import binascii
import StringIO

class PKCS7Encoder(object):
'''
RFC 2315: PKCS#7 page 21
Some content-encryption algorithms assume the
input length is a multiple of k octets, where k > 1, and
let the application define a method for handling inputs
whose lengths are not a multiple of k octets. For such
algorithms, the method shall be to pad the input at the
trailing end with k - (l mod k) octets all having value k -
(l mod k), where l is the length of the input. In other
words, the input is padded at the trailing end with one of
the following strings:

01 -- if l mod k = k-1
02 02 -- if l mod k = k-2
.
.
.
k k ... k k -- if l mod k = 0

The padding can be removed unambiguously since all input is
padded and no padding string is a suffix of another. This
padding method is well-defined if and only if k < 256;
methods for larger k are an open issue for further study.
'''

def __init__(self, k=16):
self.k = k

## @param text The padded text for which the padding is to be removed.
# @exception ValueError Raised when the input padding is missing or corrupt.
def decode(self, text):
'''
Remove the PKCS#7 padding from a text string
'''

nl = len(text)
val = int(binascii.hexlify(text[-1]), 16)

if val > self.k:
raise ValueError('Input is not padded or padding is corrupt')

l = nl - val

return text[:l]

## @param text The text to encode.
def encode(self, text):
'''
Pad an input string according to PKCS#7
'''

l = len(text)
output = StringIO.StringIO()
val = self.k - (l % self.k)

for _ in xrange(val):
output.write('%02x' % val)

return text + binascii.unhexlify(output.getvalue())

Python服务器端加密/解密:

encrypt.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import base64

from Crypto.Cipher import AES
from pkcs7 import PKCS7Encoder

# 使用256位的AES,Python会根据传入的Key长度自动选择,长度为16时使用128位的AES
key = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
mode = AES.MODE_CBC
iv = '1234567812345678' # AES的CBC模式使用IV

encoder = PKCS7Encoder()
text = "This is for test."

def encrypt(data):
encryptor = AES.new(key, AES.MODE_CBC, iv)
padded_text = encoder.encode(data)
encrypted_data = encryptor.encrypt(padded_text)

return base64.b64encode(encrypted_data)

def decrypt(data):
cipher = base64.b64decode(data)
decryptor = AES.new(key, AES.MODE_CBC, iv)
plain = decryptor.decrypt(cipher)

return encoder.decode(plain)

encrypted_text = encrypt(text)
clean_text = decrypt(encrypted_text)

print "encrypted_text:", encrypted_text
print "clean_text: ", clean_text

JS客户端端加密/解密:

crypto.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var crypto = require('crypto');

var AESCrypt = {};

AESCrypt.decrypt = function(cryptkey, iv, encryptdata) {
var decipher = crypto.createDecipheriv('aes-256-cbc', cryptkey, iv);
return Buffer.concat([
decipher.update(encryptdata),
decipher.final()
]);
}

AESCrypt.encrypt = function(cryptkey, iv, cleardata) {
var encipher = crypto.createCipheriv('aes-256-cbc', cryptkey, iv);
return Buffer.concat([
encipher.update(cleardata),
encipher.final()
]);
}

var cryptkey = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
iv = new Buffer('1234567812345678'),
buf = new Buffer("this is for test."),

enc = AESCrypt.encrypt(cryptkey, iv, buf);
var dec = AESCrypt.decrypt(cryptkey, iv, enc);

console.warn("encrypt cryptkey: ", cryptkey);
console.warn("encrypt length: ", enc.length);
console.warn("encrypt in Base64:", enc.toString('base64'));
console.warn("decrypt all: " + dec.toString('utf8'));

测试结果:
测试结果