Please enable Javascript to view the contents

前后端对称加密传输 - AES

 ·  ☕ 6 分钟

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加密算法图解:

Alt text

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

  • 安装PyCrypto库

在Python中使用AES加密,只需要安装一个PyCrypto库即可。

1
pip install pycrypto
  • 生成Key

AES的Key可选长度必须是,16个字节,24个字节,32个字节。

1
2
import random, string
key = ''.join(random.sample(string.ascii_letters + string.digits, 16))
  • AES加密函数原型
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. 参考


微信公众号
作者
微信公众号