python实现AES加密解密

AES是一种对称加密,所谓对称加密就是加密与解密使用的秘钥是一个。

为了解决一个问题临时查到一个代码套用进去,或许可以迅速解决问题,但是遇到新的问题还需要再次查询,这种我认为还是比较浪费时间的。

环境安装

1
2
3
pip uninstall crypto
pip uninstall pycryptodome
pip install pycryptodome

前面两个卸载命令是为了防止一些安装环境问题,具体请看文章

加密模式

AES 加密最常用的模式就是 ECB模式 和 CBC 模式,当然还有很多其它模式,他们都属于AES加密。ECB模式和CBC 模式俩者区别就是 ECB 不需要 iv偏移量,而CBC需要。

AES加密使用参数

以下参数都是在python中使用的。

参数作用及数据类型
秘钥加密的时候用秘钥,解密的时候需要同样的秘钥才能解出来; 数据类型为bytes
明文需要加密的参数; 数据类型为bytes
模式aes 加密常用的有 ECB 和 CBC 模式(我只用了这两个模式,还有其他模式);数据类型为aes类内部的枚举量
iv 偏移量这个参数在 ECB 模式下不需要,在 CBC 模式下需要;数据类型为bytes

下面简单的一个例子ECB模式加密解密 :

1
2
3
4
5
6
7
8
9
10
from Crypto.Cipher import AES

password = b'1234567812345678' #秘钥,b就是表示为bytes类型
text = b'abcdefghijklmnhi' #需要加密的内容,bytes类型
aes = AES.new(password,AES.MODE_ECB) #创建一个aes对象
# AES.MODE_ECB 表示模式是ECB模式
en_text = aes.encrypt(text) #加密明文
print("密文:",en_text) #加密明文,bytes类型
den_text = aes.decrypt(en_text) # 解密密文
print("明文:",den_text)

输出:

1
2
密文: b'WU\xe0\x0e\xa3\x87\x12\x95\\]O\xd7\xe3\xd4 )'
明文: b'abcdefghijklmnhi'

以上是针对ECB模式的加密解密,从这个例子中可以看出参数中有几个限制。

  1. 秘钥必须为16字节或者16字节的倍数的字节型数据。
  2. 明文必须为16字节或者16字节的倍数的字节型数据,如果不够16字节需要进行补全,关于补全规则,后面会在补全模式中具体介绍。

通过CBC模式例子:

1
2
3
4
5
6
7
8
9
10
11
from Crypto.Cipher import AES
password = b'1234567812345678' #秘钥,b就是表示为bytes类型
iv = b'1234567812345678' # iv偏移量,bytes类型
text = b'abcdefghijklmnhi' #需要加密的内容,bytes类型
aes = AES.new(password,AES.MODE_CBC,iv) #创建一个aes对象
# AES.MODE_CBC 表示模式是CBC模式
en_text = aes.encrypt(text)
print("密文:",en_text) #加密明文,bytes类型
aes = AES.new(password,AES.MODE_CBC,iv) #CBC模式下解密需要重新创建一个aes对象
den_text = aes.decrypt(en_text)
print("明文:",den_text)

输出:

1
2
密文: b'\x93\x8bN!\xe7~>\xb0M\xba\x91\xab74;0'
明文: b'abcdefghijklmnhi'

通过上面CBC模式的例子,可以简单看出CBC模式与ECB模式的区别:AES.new() 解密和加密重新生成了aes对象,加密和解密不能调用同一个aes对象,否则会报错TypeError: decrypt() cannot be called after encrypt()。

总结:

  1. 在Python中进行AES加密解密时,所传入的密文、明文、秘钥、iv偏移量、都需要是bytes(字节型)数据。python 在构建aes对象时也只能接受bytes类型数据。

  2. 当秘钥,iv偏移量,待加密的明文,字节长度不够16字节或者16字节倍数的时候需要进行补全。

  3. CBC模式需要重新生成AES对象,为了防止这类错误,我写代码无论是什么模式都重新生成AES对象。

编码模式

前面说了,python中的 AES 加密解密,只能接受字节型(bytes)数据。而我们常见的 待加密的明文可能是中文,或者待解密的密文经过base64编码的,这种都需要先进行编码或者解码,然后才能用AES进行加密或解密。反正无论是什么情况,在python使用AES进行加密或者解密时,都需要先转换成bytes型数据。

我们以ECB模式针对中文明文进行加密解密举例:

1
2
3
4
5
6
7
8
9
10
11
from Crypto.Cipher import AES

password = b'1234567812345678' #秘钥,b就是表示为bytes类型
text = "好好学习天天向上".encode('gbk') #gbk编码,是1个中文字符对应2个字节,8个中文正好16字节
aes = AES.new(password,AES.MODE_ECB) #创建一个aes对象
# AES.MODE_ECB 表示模式是ECB模式
print(len(text))
en_text = aes.encrypt(text) #加密明文
print("密文:",en_text) #加密明文,bytes类型
den_text = aes.decrypt(en_text) # 解密密文
print("明文:",den_text.decode("gbk")) # 解密后同样需要进行解码

输出:

1
2
3
16
密文: b'=\xdd8k\x86\xed\xec\x17\x1f\xf7\xb2\x84~\x02\xc6C'
明文: 好好学习天天向上

对于中文明文,我们可以使用encode()函数进行编码,将字符串转换成bytes类型数据,而这里我选择gbk编码,是为了正好能满足16字节,utf8编码是一个中文字符对应3个字节。这里为了举例所以才选择使用gbk编码。

在解密后,同样是需要decode()函数进行解码的,将字节型数据转换回中文字符(字符串类型)。

现在我们来看另外一种情况,密文是经过base64编码的(这种也是非常常见的,很多网站也是这样使用的),我们用 http://tool.chacuo.net/cryptaes/ 这个网站举例子:

模式:ECB

密码: 1234567812345678

字符集:gbk编码

输出: base64

我们来写一个python 进行aes解密:

1
2
3
4
5
6
7
8
9
10
from Crypto.Cipher import AES
import base64

password = b'1234567812345678'
aes = AES.new(password,AES.MODE_ECB)
en_text = b"Pd04a4bt7Bcf97KEfgLGQw=="
en_text = base64.decodebytes(en_text) #将进行base64解码,返回值依然是bytes
den_text = aes.decrypt(en_text)
print("明文:",den_text.decode("gbk"))

输出:

1
明文: 好好学习天天向上

这里的 b"Pd04a4bt7Bcf97KEfgLGQw==" 是一个bytes数据, 如果你传递的是一个字符串,你可以直接使用 encode()函数 将其转换为 bytes类型数据。

1
2
3
4
5
6
7
8
9
10
from Crypto.Cipher import AES
import base64

password = b'1234567812345678'
aes = AES.new(password,AES.MODE_ECB)
en_text = "Pd04a4bt7Bcf97KEfgLGQw==".encode() #将字符串转换成bytes数据
en_text = base64.decodebytes(en_text) #将进行base64解码,参数为bytes数据,返回值依然是bytes
den_text = aes.decrypt(en_text)
print("明文:",den_text.decode("gbk"))

因为无论是 utf8gbk 编码,针对英文字符编码都是一个字符对应一个字节,所以这里**encode()**函数主要作用就是转换成bytes数据,然后使用base64进行解码。

hexstr,base64编码解码例子:

1
2
3
4
5
6
7
8
9
10
11
import base64
import binascii
data = "hello".encode()
data = base64.b64encode(data)
print("base64编码:",data)
data = base64.b64decode(data)
print("base64解码:",data)
data = binascii.b2a_hex(data)
print("hexstr编码:",data)
data = binascii.a2b_hex(data)
print("hexstr解码:",data)

输出:

1
2
3
4
base64编码: b'aGVsbG8='
base64解码: b'hello'
hexstr编码: b'68656c6c6f'
hexstr解码: b'hello'

这里要说明一下,有一些AES加密,所用的秘钥,或者IV向量是通过 base64编码或者 hexstr编码后的。针对这种,首先要进行的就是进行解码,都转换回 bytes数据,再次强调,python实现 AES加密解密传递的参数都是 bytes(字节型) 数据。

另外,我记得之前的 pycryptodome库,传递IV向量时,和明文时可以直接使用字符串类型数据,不过现在新的版本都必须为 字节型数据了,可能是为了统一好记。

填充模式

前面我使用秘钥,还有明文,包括IV向量,都是固定16字节,也就是数据块对齐了。而填充模式就是为了解决数据块不对齐的问题,使用什么字符进行填充就对应着不同的填充模式

AES补全模式常见有以下几种:

模式意义
ZeroPadding用b’\x00’进行填充,这里的0可不是字符串0,而是字节型数据的b’\x00’
PKCS7Padding当需要N个数据才能对齐时,填充字节型数据为N、并且填充N个
PKCS5Padding与PKCS7Padding相同,在AES加密解密填充方面我没感到什么区别
no padding当为16字节数据时候,可以不进行填充,而不够16字节数据时同ZeroPadding一样

这里有一个细节问题,我发现很多文章说的也是不对的。

ZeroPadding填充模式的意义:很多文章解释是当为16字节倍数时就不填充,然后当不够16字节倍数时再用字节数据0填充,这个解释是不对的,这解释应该是no padding的,而ZeroPadding是不管数据是否对其,都进行填充,直到填充到下一次对齐为止,也就是说即使你够了16字节数据,它会继续填充16字节的0,然后一共数据就是32字节。

这里可能会有一个疑问,为什么是16字节 ,其实这个是 数据块的大小,网站上也有对应设置,网站上对应的叫128位,也就是16字节对齐,当然也有192位(24字节),256位(32字节)。

本文在这个解释之后,后面就说数据块对齐问题了,而不会再说16字节倍数了。

除了no padding 填充模式,剩下的填充模式都会填充到下一次数据块对齐为止,而不会出现不填充的问题。

PKCS7Padding和 PKCS5Padding需要填充字节对应表:

明文长度值(mod 16)添加的填充字节数每个填充字节的值
0160x10
1150x0F
2140x0E
3130x0D
4120x0C
5110x0B
6100x0A
790x09
880x08
970x07
1060x06
1150x05
1240x04
1330x03
1420x02
1510x01

这里可以看到,当明文长度值已经对齐时(mod 16 = 0),还是需要进行填充,并且填充16个字节值为0x10。ZeroPadding填充逻辑也是类似的,只不过填充的字节值都为0x00,在python表示成 b’\x00’。

填充完毕后,就可以使用 AES进行加密解密了,当然解密后,也需要剔除填充的数据,无奈Python这些步骤需要自己实现(如果有这样的库还请评论指出)。

python的完整实现

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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
from Crypto.Cipher import AES
import base64
import binascii

# 数据类
class MData():
def __init__(self, data = b"",characterSet='utf-8'):
# data肯定为bytes
self.data = data
self.characterSet = characterSet

def saveData(self,FileName):
with open(FileName,'wb') as f:
f.write(self.data)

def fromString(self,data):
self.data = data.encode(self.characterSet)
return self.data

def fromBase64(self,data):
self.data = base64.b64decode(data.encode(self.characterSet))
return self.data

def fromHexStr(self,data):
self.data = binascii.a2b_hex(data)
return self.data

def toString(self):
return self.data.decode(self.characterSet)

def toBase64(self):
return base64.b64encode(self.data).decode()

def toHexStr(self):
return binascii.b2a_hex(self.data).decode()

def toBytes(self):
return self.data

def __str__(self):
try:
return self.toString()
except Exception:
return self.toBase64()


### 封装类
class AEScryptor():
def __init__(self,key,mode,iv = '',paddingMode= "NoPadding",characterSet ="utf-8"):
'''
构建一个AES对象
key: 秘钥,字节型数据
mode: 使用模式,只提供两种,AES.MODE_CBC, AES.MODE_ECB
iv: iv偏移量,字节型数据
paddingMode: 填充模式,默认为NoPadding, 可选NoPadding,ZeroPadding,PKCS5Padding,PKCS7Padding
characterSet: 字符集编码
'''
self.key = key
self.mode = mode
self.iv = iv
self.characterSet = characterSet
self.paddingMode = paddingMode
self.data = ""

def __ZeroPadding(self,data):
data += b'\x00'
while len(data) % 16 != 0:
data += b'\x00'
return data

def __StripZeroPadding(self,data):
data = data[:-1]
while len(data) % 16 != 0:
data = data.rstrip(b'\x00')
if data[-1] != b"\x00":
break
return data

def __PKCS5_7Padding(self,data):
needSize = 16-len(data) % 16
if needSize == 0:
needSize = 16
return data + needSize.to_bytes(1,'little')*needSize

def __StripPKCS5_7Padding(self,data):
paddingSize = data[-1]
return data.rstrip(paddingSize.to_bytes(1,'little'))

def __paddingData(self,data):
if self.paddingMode == "NoPadding":
if len(data) % 16 == 0:
return data
else:
return self.__ZeroPadding(data)
elif self.paddingMode == "ZeroPadding":
return self.__ZeroPadding(data)
elif self.paddingMode == "PKCS5Padding" or self.paddingMode == "PKCS7Padding":
return self.__PKCS5_7Padding(data)
else:
print("不支持Padding")

def __stripPaddingData(self,data):
if self.paddingMode == "NoPadding":
return self.__StripZeroPadding(data)
elif self.paddingMode == "ZeroPadding":
return self.__StripZeroPadding(data)

elif self.paddingMode == "PKCS5Padding" or self.paddingMode == "PKCS7Padding":
return self.__StripPKCS5_7Padding(data)
else:
print("不支持Padding")

def setCharacterSet(self,characterSet):
'''
设置字符集编码
characterSet: 字符集编码
'''
self.characterSet = characterSet

def setPaddingMode(self,mode):
'''
设置填充模式
mode: 可选NoPadding,ZeroPadding,PKCS5Padding,PKCS7Padding
'''
self.paddingMode = mode

def decryptFromBase64(self,entext):
'''
从base64编码字符串编码进行AES解密
entext: 数据类型str
'''
mData = MData(characterSet=self.characterSet)
self.data = mData.fromBase64(entext)
return self.__decrypt()

def decryptFromHexStr(self,entext):
'''
从hexstr编码字符串编码进行AES解密
entext: 数据类型str
'''
mData = MData(characterSet=self.characterSet)
self.data = mData.fromHexStr(entext)
return self.__decrypt()

def decryptFromString(self,entext):
'''
从字符串进行AES解密
entext: 数据类型str
'''
mData = MData(characterSet=self.characterSet)
self.data = mData.fromString(entext)
return self.__decrypt()

def decryptFromBytes(self,entext):
'''
从二进制进行AES解密
entext: 数据类型bytes
'''
self.data = entext
return self.__decrypt()

def encryptFromString(self,data):
'''
对字符串进行AES加密
data: 待加密字符串,数据类型为str
'''
self.data = data.encode(self.characterSet)
return self.__encrypt()

def __encrypt(self):
if self.mode == AES.MODE_CBC:
aes = AES.new(self.key,self.mode,self.iv)
elif self.mode == AES.MODE_ECB:
aes = AES.new(self.key,self.mode)
else:
print("不支持这种模式")
return

data = self.__paddingData(self.data)
enData = aes.encrypt(data)
return MData(enData)

def __decrypt(self):
if self.mode == AES.MODE_CBC:
aes = AES.new(self.key,self.mode,self.iv)
elif self.mode == AES.MODE_ECB:
aes = AES.new(self.key,self.mode)
else:
print("不支持这种模式")
return
data = aes.decrypt(self.data)
mData = MData(self.__stripPaddingData(data),characterSet=self.characterSet)
return mData


if __name__ == '__main__':
key = b"1234567812345678"
iv = b"0000000000000000"
aes = AEScryptor(key,AES.MODE_CBC,iv,paddingMode= "ZeroPadding",characterSet='utf-8')

data = "好好学习"
rData = aes.encryptFromString(data)
print("密文:",rData.toBase64())
rData = aes.decryptFromBase64(rData.toBase64())
print("明文:",rData)


对其进行了封装,加密和解密返回的数据类型可以使用toBase64(),toHexStr() 进行编码。另外我没有对key和iv进行补全,可以使用MData类自己实现,更多详细使用可以通过源码中注释了解。

目前以上常用的ECB,CBC这种常用的加密模式会是在漏洞扫描时报安全提醒,并推荐使用GCM模式。

常规示例

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : Uyynot
# @Email : uyynot@qq.com
# @Time : 2023/11/2 9:17
# @File : aes_gcm_helper.py
# @Project : project
# @Desc :
import base64

from Cryptodome.Cipher import AES
from django.conf import settings


class AESUtils:
def __init__(self, key: str, nonce: str):
self.key = key.encode(encoding='utf-8')
self.nonce = nonce.encode(encoding='utf-8')

def encrypt(self, plaintext: str):
"""
加密函数
@param plaintext: 加密明文
@return: 返回加密密文和tag
"""
# 创建AES-GCM加密器
cipher = AES.new(self.key, AES.MODE_GCM, nonce=self.nonce)
# 加密明文
ciphertext, tag = cipher.encrypt_and_digest(plaintext.encode(encoding='utf-8'))
# 返回加密后的数据和Nonce值
return base64.b64encode(ciphertext), base64.b64encode(tag)

def decrypt(self, ciphertext: str, tag: str) -> str:
"""
解密函数
@param ciphertext: 加密密文
@param tag: tag
@return: 加密明文
"""
# 创建AES-GCM解密器
cipher = AES.new(self.key, AES.MODE_GCM, nonce=self.nonce)
# 解密密文
plaintext = cipher.decrypt_and_verify(base64.b64decode(ciphertext), base64.b64decode(tag))
# 返回解密后的明文
return plaintext.decode()


clientAES = AESUtils(key=settings.AES_HELPER_DEFAULT_KEY, nonce=settings.AES_HELPER_DEFAULT_IV)
if __name__ == '__main__':
key = 'oVPSRgw8o1IWmkab'
nonce = 'KLyWDN7IivzcIgcd'
client = AESUtils(key=key, nonce=nonce)

ciphertext, tag = client.encrypt('hello中国')
res = client.decrypt(ciphertext, tag)
print(res)

由于以上方式在与第三方对接时诸多不变,以下为在此基础上的简化升级版本

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import base64
import typing

# from Crypto.Cipher import AES
# pip install pycryptodomex
from Cryptodome.Cipher import AES


class AESGCMClient(object):
"""
可参考在线gcm加密对照
- https://try8.cn/tool/cipher/aes
"""

def __init__(
self,
_key: typing.Union[str, bytes],
_associated: typing.Union[str, bytes],
_nonce: typing.Union[str, bytes],
):
self.key = _key.encode() if isinstance(_key, str) else _key
self.associated = _associated.encode() if isinstance(_associated, str) else _associated
self.nonce = _nonce.encode() if isinstance(_nonce, str) else _nonce

def encrypt(self, content: str) -> str:
"""
GCM encrypt

:params content: 待加密内容

> ciphertext: 加密后的bytes,可用base64.b64encode(ciphertext).decode('utf-8')解码
> tag: 加密后的bytes,可用base64.b64encode(tag).decode('utf-8')解码
:return : 将密文与tag内容一起返回,后续通过位数切割还原密文与tag,进行解密操作
"""
if not content:
return ''
cipher = AES.new(key=self.key, mode=AES.MODE_GCM, nonce=self.nonce)
cipher.update(assoc_data=self.associated)
cipher_text, tag = cipher.encrypt_and_digest(plaintext=content.encode('utf-8'))
return base64.b64encode(cipher_text + tag).decode('utf-8')

def decrypt(self, content: str) -> str:
"""
GCM decrypt
"""
if not content:
return ''
try:
cipher = AES.new(key=self.key, mode=AES.MODE_GCM, nonce=self.nonce)
cipher.update(assoc_data=self.associated)
en_data = base64.b64decode(content.encode('utf-8'))
cipher_text, received_mac_tag = en_data[:-16], en_data[-16:]
plaintext = cipher.decrypt_and_verify(ciphertext=cipher_text, received_mac_tag=received_mac_tag)
return plaintext.decode()
except Exception as e:
return ''


if __name__ == '__main__':
key = 'pRVIc4bvJ97bFaEL6s2n211brjgw6c9f'
nonce = 'qNvaCUrnxYNouXPxjDh4WY4gooky2HhN'
client = AESGCMClient(_key=key, _associated="123456", _nonce=nonce)

ciphertext = client.encrypt('hello中国')
print(f"ciphertext={ciphertext}")
res = client.decrypt(ciphertext)
print(res)

再次进阶,上述版本key和nonce都是固定的,官方其实建议nonce最好不要固定不变,由此我们可以尝试动态生成nonce,包含在加密数据中,后续对方解密的时候根据位数先取出nonce,再进行解密,这样双方对接的就只有一个key了。

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : Uyynot
# @Email : uyynot@qq.com
# @Time : 2023/11/2 9:17
# @File : aes_gcm_helper.py
# @Project : iep
# @Desc :
import base64

from Cryptodome.Cipher import AES
from Cryptodome.Random import get_random_bytes


class AESUtils:
def __init__(self, key: str):
self.key = key.encode(encoding='utf-8')

def encrypt(self, plaintext: str):
"""
加密函数
@param plaintext: 加密明文
@return: 返回加密密文和tag
"""
# 每次加密随机一个nonce
_nonce = get_random_bytes(32)
# 创建AES-GCM加密器
cipher = AES.new(self.key, AES.MODE_GCM, nonce=_nonce)
# 加密明文
_ciphertext, _tag = cipher.encrypt_and_digest(plaintext.encode(encoding='utf-8'))

# 返回加密后的数据和Nonce值
return base64.b64encode(_ciphertext + _tag + _nonce).decode('utf-8')

def decrypt(self, content: str) -> str:
"""
解密函数
@param content: 加密密文
@return: 加密明文
"""
if not content:
return ''
try:
content = base64.b64decode(content.encode())
_ciphertext, _tag, _nonce = content[:-48], content[-48:-32], content[-32:]
# 创建AES-GCM解密器
cipher = AES.new(self.key, AES.MODE_GCM, nonce=_nonce)
# 解密密文
plaintext = cipher.decrypt_and_verify(_ciphertext, _tag)
# 返回解密后的明文
return plaintext.decode()
except Exception as e:
print(e)
return ''


if __name__ == '__main__':
key = 'pRVIc4bvJ97bFaEL6s2n211brjgw6c2x'
client = AESUtils(key=key)
ciphertext = client.encrypt('26617053184')

print('加密结果:')
print("ciphertext:", ciphertext, type(ciphertext))


res = client.decrypt(ciphertext)
print('解密结果:', res)


golang的完整实现

最近在看golang,想着按照python项目去封装一套脚手架,日常项目中aes也是少不了的,这里就封装了下,代码中都含有注释,所以这里直接上代码:

golang中这几个包都是内置的,不用另外安装哟

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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/*
@Author : Uyynot
@Email : uyynot@qq.com
@Time : 2023/8/2 13:51
@File : main.py
@Project : test
@Desc :
*/
package main

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"errors"
"fmt"
)

func main() {
// 定义一个结构体,将key、iv和加解密全部封装到了一个结构体中
aes := Aes{
Key: []byte("Ay4j79B6XlKyOmzVVOsWCh5fUkHNUhav"),
Iv: []byte("M033mWWXiCvdg2tq"),
}
// 加密内容(要转成字节哟)
data := []byte("01234567891234567")

// 解密
enData, err := aes.encrypt(data)
fmt.Println("enData:", enData, string(enData))
fmt.Println("err:", err)
/**
* 输出如下
* enData: [77 65 103 86 51 99 101 84 119 68 81 50 116 69 105 117 68 66 51 103 82 72 108 90 71 50 86 86 51 113 76 65 98 117 79 76 79 82 55 76 49 72 107 61] MAgV3ceTwDQ2tEiuDB3gRHlZG2VV3qLAbuOLOR7L1Hk=
* err: <nil>
*/

// 解密
deData, err1 := aes.decrypt(string(enData))
fmt.Println("deData:", deData, string(deData))
fmt.Println("err1:", err1)
/**
* 输出如下:
* deData: [48 49 50 51 52 53 54 55 56 57 49 50 51 52 53 54 55] 01234567891234567
* err1: <nil>
*/
}

// aes结构体及加解密方法
type Aes struct {
Key []byte
Iv []byte
}

func (a *Aes) encrypt(data []byte) ([]byte, error) {
block, err := aes.NewCipher(a.Key)
if err != nil {
return nil, err
}
//判断加密块的大小
blockSize := block.BlockSize()
//补充长度,最终补充的长度为blockSize的倍数
encryptBytes := a.pkcs7Padding(data, blockSize)
//初始化加密数据接收切片
crypted := make([]byte, len(encryptBytes))
//使用CBC加密模式
mode := cipher.NewCBCEncrypter(block, a.Iv)
//执行加密
mode.CryptBlocks(crypted, encryptBytes)
//进行base64编码
encryptData := base64.StdEncoding.EncodeToString(crypted)
return []byte(encryptData), nil
}

func (a *Aes) decrypt(encryData string) ([]byte, error) {
block, err := aes.NewCipher(a.Key)
if err != nil {
return nil, err
}
//先使用base进行解码
baseDeData, err := base64.StdEncoding.DecodeString(encryData)
if err != nil {
return nil, err
}
//初始化解密数据接收切片
decrypted := make([]byte, len(baseDeData))
//使用CBC解密模式
mode := cipher.NewCBCDecrypter(block, a.Iv)

//执行解密
mode.CryptBlocks(decrypted, baseDeData)
//去除填充
decrypted, err = a.pkcs7UnPadding(decrypted)
if err != nil {
return nil, err
}
return decrypted, nil
}

// pkcs7Padding 填充
func (a *Aes) pkcs7Padding(data []byte, blockSize int) []byte {
//判断缺少几位长度。最少1,最多 blockSize
padding := blockSize - len(data)%blockSize
//补足位数。把切片[]byte{byte(padding)}复制padding个
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padText...)
}

// pkcs7UnPadding 移除
func (a *Aes) pkcs7UnPadding(data []byte) ([]byte, error) {
length := len(data)
if length == 0 {
return nil, errors.New("加密字符串错误!")
}
//获取填充的个数
unPadding := int(data[length-1])
return data[:(length - unPadding)], nil
}

版权声明:转自CSDN博主「Hello_wshuo」的原创文章。
原文链接:https://blog.csdn.net/chouzhou9701/article/details/122019967