1.基本概念
- 对称加密:
对称加密是,采用单密钥密码系统的加密方法,同一个密钥同时用作信息的加密和解密。由于速度快,常用于加密大量数据的传输。 - DES(Data Encryption Standard),数据加密标准:
DES的密钥长度是56比特,算法的理论安全强度是\( 2^{56} \)。随着计算机处理能力的增强,DES已经不能够提供足够的安全性。 - AES(Advanced Encryption Standard),高级加密标准:
1997年1月,美国国家标准技术研究所,开始征集高级加密标准,得到众多学者响应。经过层层筛选、性能测试,最后Rijndael算法被选中。该算法为比利时密码学家Joan Daemen和Vincent Rijmen所设计。
2.基本原理
下面是加密过程的动态图。
AES加密算法涉及4种操作。
- 1.字节替代(SubBytes):
字节代替的主要功能是完成一个字节到另外一个字节的映射 - 2.行移位(ShiftRows):
行移位是一个4x4的矩阵内部字节之间的置换,用于提供算法的扩散性。 - 3.列混淆(MixColumns):
列混淆,利用GF(\( 2^{8} \))域上算术特性的一个代替,同样用于提供算法的扩散性。 - 4.轮密钥加(AddRoundKey):
矩阵中的每一个字节都与该次轮秘钥(round key)做XOR运算,每个子密钥由密钥生成方案产生。
下面是AES加密算法图解:
3.工作模式
分组加密算法是按分组大小来进行加解密操作的,如DES算法的分组是64位,而AES是128位,但实际明文的长度一般要远大于分组大小,这样的情况如何处理呢?
工作模式约定的就是明文数据流按照多大分组切分、数据对不齐时如何处理等问题。
主要的几种工作模式:
- 电子密码本(Electronic Code Book Mode ,ECB):
ECB模式只是将明文按分组大小切分,然后用同样的密钥正常加密切分好的明文分组。ECB的理想应用场景是短数据(如加密密钥)的加密。此模式的问题是无法隐藏原明文数据的模式,因为同样的明文分组加密得到的密文也是一样的。 - 密码分组链接(Cipher Block Chaining Mode ,CBC):引入了IV(初始化向量:Initialization Vector)的概念。CBC模式相比ECB实现了更好的模式隐藏,但因为其将密文引入运算,加解密操作无法并行操作。同时引入的IV向量,还需要加、解密双方共同知晓方可。
- 密文反馈(Cipher Feedback Mode ,CFB):与CBC模式类似,但不同的地方在于,CFB模式先生成密码流字典,然后用密码字典与明文进行异或操作并最终生成密文。后一分组的密码字典的生成需要前一分组的密文参与运算。
- 输出反馈(Output Feedback Mode ,OFB):OFB模式与CFB模式不同的地方是:生成字典的时候会采用明文参与运算,CFB采用的是密文。
- 计数器模式(Counter Mode ,CTR):CTR模式同样会产生流密码字典,但同是会引入一个计数,以保证任意长时间均不会产生重复输出。
4. 填充
填充的作用是,在加密前将普通文本的长度扩展到需要的长度。ECB 和 CBC 需要填充,即加密后长度可能会不一样,CFB
、OFB、CTR 不需要填充,密文长度与明文长度一样。主要的填充模式有,PKCS7 、ANSIX923、ISO10126 、NoPadding、ZeroPadding。
- PKCS7 : 填充字符串由一个字节序列组成,每个字节填充该字节序列的长度
- ANSIX923:填充字符串包含的一个填充了零长度的字节序列
- ISO10126 :填充字符串包含的长度的随机数据
- NoPadding:不填充是完成的
- ZeroPadding:填充字符串由设置为零的字节组成
示例:
数据︰ FF FF FF FF FF FF FF FF FF
PKCS7 填充︰ FF FF FF FF FF FF FF FF FF 07 07 07 07-07-07 07
ANSIX923填充︰ FF FF FF FF FF FF FF FF FF 00 00 00 00 00 00 07
ISO10126 填充︰ FF FF FF FF FF FF FF FF FF 7 D 2A 75 EF F8 EF 07
ZeroPadding 填充︰ FF FF FF FF FF FF FF FF FF 00 00 00 00 00 00 00
5. 前端AES加密 - JavaScript
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
| <script src="cryptojs/3.1.2/rollups/aes.js"></script>
<script src="cryptojs/3.1.2/components/pad-zeropadding-min.js"></script>
<script type="text/javascript">
var str = 'my_text';
// 密钥key长度必须为16(AES-128)、24(AES-192)、或32(AES-256)Bytes 长度
var key = 'mDSR16qzlugnyK0wBJZOAhTHoI4sP7df';
// 初始向量 initial vector 16 位,这里为了简便,直接截取key前16位
var iv = key.substring(0,16) ;
// key 和 iv 可以一致
key = CryptoJS.enc.Utf8.parse(key);
iv = CryptoJS.enc.Utf8.parse(iv);
//加密过程
var encrypted = CryptoJS.AES.encrypt(str, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding
});
// mode 支持 CBC、CFB、CTR、ECB、OFB, 默认 CBC
// padding 支持 Pkcs7、AnsiX923、Iso10126、NoPadding、ZeroPadding, 默认 Pkcs7
// 转换为字符串,"7sLMd96Msn24voJuuLDllw=="
encrypted = encrypted.toString();
//解密过程
var decrypted = CryptoJS.AES.decrypt(encrypted, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding
});
// 转换为 utf8 字符串,"my_text"
decrypted = CryptoJS.enc.Utf8.stringify(decrypted);
</script>
|
6. 后端AES加密- Python
在Python中使用AES加密,只需要安装一个PyCrypto库即可。
AES的Key可选长度必须是,16个字节,24个字节,32个字节。
1
2
| import random, string
key = ''.join(random.sample(string.ascii_letters + string.digits, 16))
|
1
2
| from Crypto.Cipher import AES
cipher = AES.new(key, mode, iv)
|
- key:初始密钥。根据 AES 规范,可以是 16 字节、24 字节和32 字节长,分别对应 128 位、192 位和 256 位
- mode:加密模式。可以寻找相关的文档了解。接下来的示例中会用到 CBC 模式,因此在此作一个简单的阐述:CBC 模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密
- iv:初始化向量。在部分加密模式中需要使用到,如对于 CBC 模式来说,它必须是随机选取并且需要保密的;而且它的长度和密码分组相同(AES 的分组长度固定为 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
| # encoding:utf-8
import string
import base64
import random
from Crypto.Cipher import AES
class AESecrypt():
def __init__(self, key, padding='\0'):
# 这里密钥key 长度必须为16(AES-128)、24(AES-192)、或32(AES-256)Bytes 长度
self.key = key
self.iv = key[:AES.block_size]
self.mode = AES.MODE_CBC
self.padding = padding
# self.is_unicode = False
# 加密函数,如果text不是16的倍数【加密文本text必须为16的倍数!】,那就补足为16的倍数
def encrypt(self, text):
# if isinstance(text, unicode):
# text = text.encode('utf-8')
# self.is_unicode = True
cryptor = AES.new(self.key, self.mode, IV=self.iv)
length = AES.block_size
count = len(text)
add = count % length
if add:
text = text + (self.padding * (length - add))
self.ciphertext = cryptor.encrypt(text)
# 因为AES加密时候得到的字符串不一定是ascii字符集的,输出到终端或者保存时候可能存在问题
# 所以这里统一把加密后的字符串用base64转化
return base64.b64encode(self.ciphertext)
# 解密后,去掉补足的'\0'用strip() 去掉
def decrypt(self, text):
cryptor = AES.new(self.key, self.mode, IV=self.iv)
plain_text = cryptor.decrypt(base64.b64decode(text)).rstrip(self.padding)
# return plain_text.decode('utf-8') if self.is_unicode else plain_text
return plain_text
if __name__ == '__main__':
key = ''.join(random.sample(string.ascii_letters + string.digits, 32))
print 'key:{key}, length:{length}'.format(key=key, length=len(key))
tool = AESecrypt(key)
e_text_en = tool.encrypt('my_text')
print u'encrypt_text_en:{text}'.format(text=e_text_en)
d_text_en = tool.decrypt(e_text_en)
print 'de_text_en:{text}'.format(text=d_text_en)
# e_text_cn = tool.encrypt(u'中文文本')
# print 'encrypt_text_cn:{text}'.format(text=e_text_cn)
# d_text_cn = tool.decrypt(e_text_cn)
# print u'de_text_en:{text}'.format(text=d_text_cn)
|
输出:
1
2
3
| key:8YkSbojRzx3AJfuX2QdD6s5HWFcL1iE4, length:32
encrypt_text_en:YZ5PUFJIqYeSDbD8ORq5Qg==
de_text_en:my_text
|
7. 参考