3. AES 加密算法详解:密钥长度、模式、填充方式及Python实现
在接口测试中,数据加密是家常便饭。AES 这词你肯定不陌生,但真正落地时,很多人容易栽跟头。我刚开始做加密测试时也踩过不少坑,今天就跟你聊聊 AES 的核心要点和实战代码。
3.1 AES 是什么?
AES,全称 Advanced Encryption Standard,是目前最主流的对称加密算法。所谓对称,就是加密和解密用同一个密钥。
它的优势很明显:速度快、安全性高、支持多种硬件加速。我们做接口测试时,后端接口常要求用 AES 对请求体或响应体进行加密。说白了,不懂 AES,这活就没法干。
3.2 密钥长度:128、192、256
AES 支持三种密钥长度:128位、192位、256位。长度越长,加密轮数越多,安全性也越高。
我这里简单对比下:
| 密钥长度 | 加密轮数 | 应用场景 |
|---|---|---|
| 128位 | 10轮 | 大部分互联网应用,性能优先 |
| 192位 | 12轮 | 企业级系统,安全与性能平衡 |
| 256位 | 14轮 | 金融、政府等高安全场景 |
我在项目中遇到过个案例:客户要求用 256 位密钥,但服务端用的是 Java 默认的密钥长度限制。跑起来后一直报错,最后发现是因为未安装 JCE 无限制权限策略文件。嗯,这个坑你得留意。
3.3 工作模式:ECB、CBC、CFB、OFB、CTR
密钥长度定了,还得选工作模式。这部分很多人容易混淆,我按自己的理解给你捋一遍。
ECB(电子密码本模式)
最简单的模式,每个数据块独立加密。缺点也很明显:相同明文块会得到相同密文块,容易暴露数据规律。说白了,不安全。
我不建议在生产环境中用 ECB,除非你只是做简单的数据脱敏。
CBC(密码分组链接模式)
这是最常用的模式。每个明文块先与前一个密文块 XOR,再加密。这样相同明文块会得到不同密文。
CBC 模式需要一个初始向量 IV。IV 每次加密都应随机生成,且长度等于块大小(16字节)。如果你固定 IV,那安全性会大打折扣。
CFB(密码反馈模式)
将 AES 当作一个密钥流生成器,逐段加密。适合流式数据传输,比如网络协议加密。
OFB(输出反馈模式)
生成的密钥流与前一块无关,只与 AES 输出有关。优点是可以预先计算密钥流,提高吞吐。
但要注意,密钥流不能重用。我曾经见过有人复用 OFB 的密钥流,结果被攻击者轻松破解。
CTR(计数器模式)
用递增的计数器值作为输入,生成密钥流。CTR 模式同样支持并行计算,性能很好。我个人习惯用 CTR,因为它无需填充,且非常适合多线程环境。
下面这个图可以帮你理解几种模式的核心逻辑:
3.4 填充方式:为什么要用 PKCS7?
AES 是一个块加密算法,每次加密固定 16 字节。如果明文长度不是 16 的倍数,就得填充。
最常见的填充方式是 PKCS7。它根据缺少的字节数填充相同数值。比如缺 5 个字节,就填 5 个 0x05。这样解密时可以根据最后一个字节的值判断填充了多少。
如果明文正好是 16 的倍数呢?有些模式会额外填充一整个 16 字节块。这就是为啥你有时会发现解密后的数据多了 16 个字节 —— 记得去掉填充。
3.5 Python 实现:手把手写代码
理论说完了,来点实际的。下面是我写的一个 AES 工具类,支持 ECB、CBC 和 CTR 模式。
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
class AESUtil:
def __init__(self, key, mode='CBC', iv=None):
self.key = key.encode('utf-8') # 密钥,需为16/24/32字节
self.bs = 16 # 块大小
self.mode = mode.upper()
# 初始化向量
if iv:
self.iv = iv.encode('utf-8')
else:
self.iv = None
def encrypt(self, data):
data = data.encode('utf-8')
if self.mode == 'ECB':
cipher = AES.new(self.key, AES.MODE_ECB)
elif self.mode == 'CBC':
# 如果没有提供IV,使用密钥前16字节作为IV(仅做演示,不安全)
iv = self.iv if self.iv else self.key[:self.bs]
cipher = AES.new(self.key, AES.MODE_CBC, iv=iv)
elif self.mode == 'CTR':
# CTR模式需用counter
from Crypto.Util import Counter
ctr = Counter.new(128)
cipher = AES.new(self.key, AES.MODE_CTR, counter=ctr)
else:
raise ValueError(f"不支持的AES模式: {self.mode}")
# ECB/CBC 需要填充,CTR不需要
if self.mode in ('ECB', 'CBC'):
padded_data = pad(data, self.bs)
else:
padded_data = data
encrypted = cipher.encrypt(padded_data)
return base64.b64encode(encrypted).decode()
def decrypt(self, ciphertext):
raw = base64.b64decode(ciphertext.encode())
if self.mode == 'ECB':
cipher = AES.new(self.key, AES.MODE_ECB)
elif self.mode == 'CBC':
iv = self.iv if self.iv else self.key[:self.bs]
cipher = AES.new(self.key, AES.MODE_CBC, iv=iv)
elif self.mode == 'CTR':
from Crypto.Util import Counter
ctr = Counter.new(128)
cipher = AES.new(self.key, AES.MODE_CTR, counter=ctr)
else:
raise ValueError(f"不支持的AES模式: {self.mode}")
decrypted = cipher.decrypt(raw)
if self.mode in ('ECB', 'CBC'):
try:
decrypted = unpad(decrypted, self.bs)
except ValueError:
# 如果填充不正确,说明密钥或密文有问题
raise ValueError("解密失败:填充数据无效")
return decrypted.decode('utf-8')
# 使用示例
if __name__ == '__main__':
key = 'thisisademokey!' # 16字节密钥
data = 'Hello, AES!'
# ECB模式
aes_ecb = AESUtil(key, 'ECB')
encrypted = aes_ecb.encrypt(data)
decrypted = aes_ecb.decrypt(encrypted)
print(f'ECB: {encrypted} -> {decrypted}')
# CBC模式
aes_cbc = AESUtil(key, 'CBC', iv='initvector123456')
encrypted = aes_cbc.encrypt(data)
decrypted = aes_cbc.decrypt(encrypted)
print(f'CBC: {encrypted} -> {decrypted}')
# CTR模式
aes_ctr = AESUtil(key, 'CTR')
encrypted = aes_ctr.encrypt(data)
decrypted = aes_ctr.decrypt(encrypted)
print(f'CTR: {encrypted} -> {decrypted}')
3.6 常见问题与避坑指南
- 密钥长度不对:Python 的 pycryptodome 要求密钥必须是 16、24 或 32 字节。如果你传入的密钥长度不对,会直接报错。我习惯在创建对象时就校验长度。
- IV 不固定:CBC 模式每次加密都应生成随机 IV,不要硬编码。固定 IV 会让攻击者发现数据中的规律模式。
- 编码问题:加密后的结果是字节串,不要直接打印,最好用 base64 编码后再传输。解密时记得先解 base64。
- CTR 模式不用填充:如果你的模式是 CTR,别调用 pad 和 unpad,否则会多出字符。
- 密钥长度:128 位够用,256 位更安全
- 工作模式:首选 CBC(需 IV)或 CTR(无需填充)
- 填充方式:PKCS7,CTR/OFB/CFB 无需填充
- Python 实现:用 pycryptodome,注意编码与填充
你想想看,AES 其实并不复杂,关键是搞清楚每个参数的含义。我当初花了整整一个下午才跑通第一个加密测试,现在把这些经验整理出来,就是为了让你少走弯路。如果后续在集成测试中遇到加密问题,相信你能轻松应对了。