AES加密原理以及实现

AES加密

加密模式

ECB

ECB(电子密码本模式:Electronic codebook)是最简单的块密码加密模式,加密前根据加密块大小(如AES为128位)分成若干块,之后将每块使用相同的密钥单独加密,解密同理。

####CBC
CBC(密码分组链接:Cipher-block chaining)模式对于每个待加密的密码块在加密前会先与前一个密码块的密文异或然后再用加密器加密。第一个明文块与一个叫初始化向量的数据块异或。

CFB

CBC(密文反馈:Cipher feedback)模式对于每个待加密的密码块在加密前会先与前一个密码块的密文异或然后再用加密器加密。第一个明文块与一个叫初始化向量的数据块异或。

OFB

OFB(输出反馈:Output feedback)是先用块加密器生成密钥流(Keystream),然后再将密钥流与明文流异或得到密文流,解密是先用块加密器生成密钥流,再将密钥流与密文流异或得到明文,由于异或操作的对称性所以加密和解密的流程是完全一样的。

数据填充方式

PKCS5Padding

如果块长度是16,数据长度9,那么还差7个字节,就在后面补充7个0x07,如下所示

1
2
数据: FF FF FF FF FF FF FF FF FF
填充后:FF FF FF FF FF FF FF FF FF 07 07 07 07 07 07 07

如果数据长度为16,仍然需要补充16个0x16

PKCS7Padding

在AES加密的时候,同PKCS5Padding

####ZeroPadding
如字面意思,在数据后面填充0即可

1
2
数据: FF FF FF FF FF FF FF FF FF
填充后:FF FF FF FF FF FF FF FF FF 00 00 00 00 00 00 00

Java 实现

Java 并不支持ZeroPadding填充模式,用NoPadding代替,然后手动填充,未避免麻烦,本例中 iv和key都是16位,不需要填充,实际生产中可能需要填充,注意加密解密端保持一致。

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package utils;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Decoder;
/**
* 编码工具类
* 实现aes加密、解密
*/
public class AESEncryptUtils {
private static final String KEY_ALGORITHM = "AES";
private static final String ECB_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
private static final String CBC_CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String ECB_NOPADDING_CIPHER_ALGORITHM = "AES/ECB/NoPadding";
private static final String CBC_NOPADDING_CIPHER_ALGORITHM = "AES/CBC/NoPadding";
public static String AES_ECB_ENCRYPT(String data, String key) throws Exception {
Cipher cipher = Cipher.getInstance(ECB_CIPHER_ALGORITHM);
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), KEY_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, keyspec);
byte[] encrypted = cipher.doFinal(data.getBytes());
return new sun.misc.BASE64Encoder().encode(encrypted);
}
public static String AES_ECB_ZEROPadding_ENCRYPT(String data, String key) throws Exception {
Cipher cipher = Cipher.getInstance(ECB_NOPADDING_CIPHER_ALGORITHM);
//padding 0
int blockSize = cipher.getBlockSize();
byte[] dataBytes = data.getBytes();
int plaintextLength = dataBytes.length;
if (plaintextLength % blockSize != 0) {
plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
}
byte[] plaintext = new byte[plaintextLength];
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), KEY_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, keyspec);
byte[] encrypted = cipher.doFinal(plaintext);
return new sun.misc.BASE64Encoder().encode(encrypted);
}
public static String AES_CBC_ENCRYPT(String data, String key, String iv) throws Exception {
Cipher cipher = Cipher.getInstance(CBC_CIPHER_ALGORITHM);
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), KEY_ALGORITHM);
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
byte[] encrypted = cipher.doFinal(data.getBytes());
return new sun.misc.BASE64Encoder().encode(encrypted);
}
public static String AES_CBC_ZEROPadding_ENCRYPT(String data, String key, String iv) throws Exception {
Cipher cipher = Cipher.getInstance(CBC_NOPADDING_CIPHER_ALGORITHM);
//padding 0
int blockSize = cipher.getBlockSize();
byte[] dataBytes = data.getBytes();
int plaintextLength = dataBytes.length;
if (plaintextLength % blockSize != 0) {
plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
}
byte[] plaintext = new byte[plaintextLength];
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), KEY_ALGORITHM);
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
byte[] encrypted = cipher.doFinal(plaintext);
return new sun.misc.BASE64Encoder().encode(encrypted);
}
public static String AES_DECRYPT(String data, String key, String iv, String padding_mode) throws Exception {
String algorithm = null;
IvParameterSpec ivspec = null;
if (!iv.equals("")) {
ivspec = new IvParameterSpec(iv.getBytes());
if (padding_mode != null && padding_mode.equals("NoPadding")) {
algorithm = CBC_NOPADDING_CIPHER_ALGORITHM;
} else
algorithm = CBC_CIPHER_ALGORITHM;
} else {
if (padding_mode != null && padding_mode.equals("NoPadding")) {
algorithm = ECB_NOPADDING_CIPHER_ALGORITHM;
} else
algorithm = ECB_CIPHER_ALGORITHM;
}
byte[] encrypted = new BASE64Decoder().decodeBuffer(data);
Cipher cipher = Cipher.getInstance(algorithm);
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), KEY_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
byte[] plain = cipher.doFinal(encrypted);
return new String(plain);
}
public static void main(String[] args) throws Exception {
System.out.println(AES_CBC_ZEROPadding_ENCRYPT("admin", "1234567812345678", "1234567812345678"));
System.out.println(AES_DECRYPT("tgexo5TO+DJGzMrpCYsPUw==", "1234567812345678", "", "NoPadding"));
}
}

Python 实现

Python 实现采用Crpyto库,以下代码用lambda自己实现的pkcs5填充,仅供参考

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
# -*- coding:utf-8 -*-
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
import base64
class aescrypt():
def __init__(self, key, iv='1234567812345678'):
self.key = key
self.iv = iv
self.mode = AES.MODE_CBC
self.BS = AES.block_size
# 补位
self.pad = lambda s: s + (self.BS - len(s) % self.BS) * chr(self.BS - len(s) % self.BS)
self.unpad = lambda s: s[0:-ord(s[-1])]
def encrypt(self, text):
text = self.pad(text)
cryptor = AES.new(self.key, self.mode, self.iv)
# 目前AES-128 足够目前使用
ciphertext = cryptor.encrypt(text)
# 把加密后的字符串转化为16进制字符串
#return b2a_hex(ciphertext)
# 返回加密字符串的base64值
return base64.b64encode(ciphertext)
# 解密后,去掉补足的空格用strip() 去掉
def decrypt(self, text):
cryptor = AES.new(self.key, self.mode, self.iv)
plain_text = cryptor.decrypt(base64.b64decode(text))
return self.unpad(plain_text.rstrip('\0'))
if __name__ == '__main__':
pc = aescrypt('1234567812345678') # 初始化密钥 和 iv
e = pc.encrypt("123456") # 加密
d = pc.decrypt(e) # 解密
print "加密:", e
print "解密:", d
print "长度:", len(d)

Javascript 实现

JS的加密采用了CryptoJS库,示例代码如下

ECB 模式默认的是ZeroPadding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script src="js/cryptojslib/rollups/aes.js"></script>
<script src="js/cryptojslib/components/pad-zeropadding-min.js"></script>
<script>
var data = "123456";
var key = CryptoJS.enc.Latin1.parse('1234567812345678');
var iv = CryptoJS.enc.Latin1.parse('1234567812345678');
//加密
//如果不是和Java交互,而是和python或者其他交互,那么padding方式可以采用pkcs7(和pkcs5一样)
var encrypted = CryptoJS.AES.encrypt(
data,
key,
{iv:iv,mode:CryptoJS.mode.CBC,padding:CryptoJS.pad.ZeroPadding
});
console.log('encrypted: ' + encrypted) ;