Pytest 接口加密封装详解 – 新手完整教程
1. 什么是接口加密
1.1 接口加密的基本概念
接口加密是指在接口请求和响应过程中,对数据进行加密处理,以确保数据的安全性和完整性。在实际的接口测试中,我们经常会遇到需要加密或签名的接口。
1.2 为什么需要接口加密
想象一下,如果你在银行转账:
不加密的情况:
你发送:我要给张三转1000元
服务器收到:我要给张三转1000元
加密的情况:
你发送:a8f5f167f44f4964e6c998dee827110c(加密后的数据)
服务器收到后解密:我要给张三转1000元
接口加密的主要目的:
- 数据安全:防止数据在传输过程中被窃取或篡改
- 身份验证:确保请求来自合法的客户端
- 防止重放攻击:通过时间戳、随机数等机制防止请求被重复使用
- 数据完整性:确保数据在传输过程中没有被修改
1.3 接口加密的常见场景
1.3.1 登录接口加密
# 示例:登录接口需要加密密码
# 原始请求
{
"username": "admin",
"password": "123456" # 明文密码,不安全
}
# 加密后的请求
{
"username": "admin",
"password": "e10adc3949ba59abbe56e057f20f883e" # MD5加密后的密码
}
1.3.2 支付接口签名
# 示例:支付接口需要签名验证
# 请求参数
{
"order_id": "20240101001",
"amount": 100.00,
"timestamp": "1704067200"
}
# 需要生成签名
sign = md5(order_id + amount + timestamp + secret_key)
# 最终请求
{
"order_id": "20240101001",
"amount": 100.00,
"timestamp": "1704067200",
"sign": "a8f5f167f44f4964e6c998dee827110c"
}
1.3.3 敏感数据传输
# 示例:传输身份证号等敏感信息
# 原始数据
{
"id_card": "110101199001011234" # 明文身份证号
}
# AES加密后
{
"id_card": "U2FsdGVkX1+..." # 加密后的数据
}
1.4 接口加密的常见方式
接口加密主要有以下几种方式:
- MD5 加密:单向加密,常用于密码加密和签名
- SHA 加密:单向加密,比MD5更安全
- AES 加密:对称加密,速度快,适合大数据量
- RSA 加密:非对称加密,安全性高,适合小数据量
- Base64 编码:不是加密,只是编码,但常用于数据传输
- 签名算法:通过签名验证请求的合法性
2. 基础加密算法介绍
2.1 MD5 加密
2.1.1 什么是 MD5
MD5(Message Digest Algorithm 5) 是一种广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。
MD5 的特点:
- 单向性:只能加密,不能解密
- 固定长度:无论输入多长,输出都是32位16进制字符串
- 雪崩效应:输入稍微改变,输出完全不同
- 快速计算:计算速度快
2.1.2 MD5 的基本使用
import hashlib
# 方式一:使用 hashlib 模块
def md5_encrypt(text):
"""
对文本进行 MD5 加密
参数:
text: 要加密的文本(字符串)
返回:
加密后的32位16进制字符串
"""
# 创建 MD5 对象
md5_obj = hashlib.md5()
# 如果输入是字符串,需要先编码为字节
md5_obj.update(text.encode('utf-8'))
# 获取加密后的16进制字符串
return md5_obj.hexdigest()
# 使用示例
password = "123456"
encrypted = md5_encrypt(password)
print(f"原始密码: {password}")
print(f"MD5加密后: {encrypted}")
# 输出:
# 原始密码: 123456
# MD5加密后: e10adc3949ba59abbe56e057f20f883e
2.1.3 MD5 加密的详细示例
import hashlib
# 示例1:加密单个字符串
def test_md5_simple():
"""简单的 MD5 加密示例"""
text = "hello world"
md5_hash = hashlib.md5(text.encode('utf-8')).hexdigest()
print(f"'{text}' 的 MD5 值: {md5_hash}")
# 输出: 'hello world' 的 MD5 值: 5eb63bbbe01eeed093cb22bb8f5acdc3
# 示例2:加密多个字符串拼接
def test_md5_concatenate():
"""多个字符串拼接后加密"""
username = "admin"
password = "123456"
timestamp = "1704067200"
# 拼接字符串
combined = username + password + timestamp
md5_hash = hashlib.md5(combined.encode('utf-8')).hexdigest()
print(f"拼接后的 MD5 值: {md5_hash}")
# 示例3:加密字典数据
def test_md5_dict():
"""对字典数据进行 MD5 加密"""
data = {
"username": "admin",
"password": "123456",
"timestamp": "1704067200"
}
# 将字典转换为排序后的字符串
# 按键名排序,确保顺序一致
sorted_items = sorted(data.items())
data_str = "&".join([f"{k}={v}" for k, v in sorted_items])
md5_hash = hashlib.md5(data_str.encode('utf-8')).hexdigest()
print(f"字典数据的 MD5 值: {md5_hash}")
# 示例4:带密钥的 MD5 加密
def test_md5_with_secret():
"""带密钥的 MD5 加密(常用于签名)"""
data = "order_id=20240101001&amount=100.00"
secret_key = "my_secret_key_123"
# 在数据后面拼接密钥
sign_string = data + secret_key
md5_hash = hashlib.md5(sign_string.encode('utf-8')).hexdigest()
print(f"带密钥的 MD5 签名: {md5_hash}")
if __name__ == "__main__":
test_md5_simple()
test_md5_concatenate()
test_md5_dict()
test_md5_with_secret()
2.1.4 MD5 在接口测试中的应用
import hashlib
import requests
def test_login_with_md5():
"""使用 MD5 加密密码的登录接口测试"""
url = "https://api.example.com/login"
username = "admin"
password = "123456"
# 对密码进行 MD5 加密
encrypted_password = hashlib.md5(password.encode('utf-8')).hexdigest()
# 构造请求数据
data = {
"username": username,
"password": encrypted_password # 使用加密后的密码
}
# 发送请求
response = requests.post(url, json=data)
# 验证响应
assert response.status_code == 200
result = response.json()
assert result["code"] == 0
print("登录成功!")
2.2 SHA 加密
2.2.1 什么是 SHA
SHA(Secure Hash Algorithm) 是一系列密码散列函数,包括 SHA-1、SHA-256、SHA-512 等。SHA 比 MD5 更安全,但计算速度稍慢。
SHA 的常见类型:
- SHA-1:160位(40个16进制字符)
- SHA-256:256位(64个16进制字符),最常用
- SHA-512:512位(128个16进制字符)
2.2.2 SHA 的基本使用
import hashlib
# SHA-1 加密
def sha1_encrypt(text):
"""SHA-1 加密"""
return hashlib.sha1(text.encode('utf-8')).hexdigest()
# SHA-256 加密(最常用)
def sha256_encrypt(text):
"""SHA-256 加密"""
return hashlib.sha256(text.encode('utf-8')).hexdigest()
# SHA-512 加密
def sha512_encrypt(text):
"""SHA-512 加密"""
return hashlib.sha512(text.encode('utf-8')).hexdigest()
# 使用示例
text = "hello world"
print(f"SHA-1: {sha1_encrypt(text)}")
print(f"SHA-256: {sha256_encrypt(text)}")
print(f"SHA-512: {sha512_encrypt(text)}")
2.2.3 SHA 加密的详细示例
import hashlib
# 示例1:SHA-256 加密
def test_sha256():
"""SHA-256 加密示例"""
password = "123456"
sha256_hash = hashlib.sha256(password.encode('utf-8')).hexdigest()
print(f"密码 '{password}' 的 SHA-256 值: {sha256_hash}")
# 示例2:带盐值的 SHA 加密(更安全)
def test_sha256_with_salt():
"""带盐值的 SHA-256 加密"""
password = "123456"
salt = "random_salt_123" # 盐值,增加安全性
# 密码 + 盐值
salted_password = password + salt
sha256_hash = hashlib.sha256(salted_password.encode('utf-8')).hexdigest()
print(f"带盐值的 SHA-256: {sha256_hash}")
# 示例3:HMAC-SHA256(带密钥的 SHA)
def test_hmac_sha256():
"""HMAC-SHA256 加密(常用于签名)"""
import hmac
message = "order_id=20240101001&amount=100.00"
secret_key = "my_secret_key_123"
# HMAC-SHA256
signature = hmac.new(
secret_key.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).hexdigest()
print(f"HMAC-SHA256 签名: {signature}")
if __name__ == "__main__":
test_sha256()
test_sha256_with_salt()
test_hmac_sha256()
2.3 Base64 编码
2.3.1 什么是 Base64
Base64 不是加密算法,而是一种编码方式。它将二进制数据转换为可打印的 ASCII 字符,常用于在 HTTP 协议中传输二进制数据。
Base64 的特点:
- 可逆:可以编码也可以解码
- 安全性低:不是加密,任何人都可以解码
- 用途:主要用于数据传输,不用于安全加密
2.3.2 Base64 的基本使用
import base64
# Base64 编码
def base64_encode(text):
"""Base64 编码"""
# 将字符串编码为字节
text_bytes = text.encode('utf-8')
# Base64 编码
encoded_bytes = base64.b64encode(text_bytes)
# 转换为字符串
return encoded_bytes.decode('utf-8')
# Base64 解码
def base64_decode(encoded_text):
"""Base64 解码"""
# 将字符串编码为字节
encoded_bytes = encoded_text.encode('utf-8')
# Base64 解码
decoded_bytes = base64.b64decode(encoded_bytes)
# 转换为字符串
return decoded_bytes.decode('utf-8')
# 使用示例
text = "hello world"
encoded = base64_encode(text)
decoded = base64_decode(encoded)
print(f"原文: {text}")
print(f"编码: {encoded}")
print(f"解码: {decoded}")
# 输出:
# 原文: hello world
# 编码: aGVsbG8gd29ybGQ=
# 解码: hello world
2.3.3 Base64 在接口测试中的应用
import base64
import requests
def test_api_with_base64():
"""使用 Base64 编码的接口测试"""
url = "https://api.example.com/upload"
# 原始数据
data = {
"username": "admin",
"password": "123456"
}
# 将数据转换为 JSON 字符串
import json
json_str = json.dumps(data, ensure_ascii=False)
# Base64 编码
encoded_data = base64.b64encode(json_str.encode('utf-8')).decode('utf-8')
# 构造请求
request_data = {
"data": encoded_data
}
# 发送请求
response = requests.post(url, json=request_data)
assert response.status_code == 200
2.4 AES 加密
2.4.1 什么是 AES
AES(Advanced Encryption Standard) 是一种对称加密算法,是目前最常用的加密算法之一。
AES 的特点:
- 对称加密:加密和解密使用同一个密钥
- 速度快:适合加密大量数据
- 安全性高:目前还没有被破解
- 密钥长度:支持 128位、192位、256位
2.4.2 AES 的基本使用
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
def aes_encrypt(text, key):
"""
AES 加密
参数:
text: 要加密的文本
key: 密钥(16字节、24字节或32字节)
返回:
Base64 编码的加密字符串
"""
# 确保密钥长度正确
if len(key) < 16:
key = key.ljust(16, '0') # 不足16位用0补齐
elif len(key) < 24:
key = key.ljust(24, '0') # 不足24位用0补齐
elif len(key) < 32:
key = key.ljust(32, '0') # 不足32位用0补齐
else:
key = key[:32] # 超过32位截断
# 将文本转换为字节
text_bytes = text.encode('utf-8')
# 创建 AES 加密器(使用 ECB 模式)
cipher = AES.new(key.encode('utf-8'), AES.MODE_ECB)
# 填充数据(AES 要求数据长度必须是16的倍数)
padded_data = pad(text_bytes, AES.block_size)
# 加密
encrypted_data = cipher.encrypt(padded_data)
# Base64 编码
return base64.b64encode(encrypted_data).decode('utf-8')
def aes_decrypt(encrypted_text, key):
"""
AES 解密
参数:
encrypted_text: Base64 编码的加密字符串
key: 密钥
返回:
解密后的文本
"""
# 确保密钥长度正确
if len(key) < 16:
key = key.ljust(16, '0')
elif len(key) < 24:
key = key.ljust(24, '0')
elif len(key) < 32:
key = key.ljust(32, '0')
else:
key = key[:32]
# Base64 解码
encrypted_data = base64.b64decode(encrypted_text)
# 创建 AES 解密器
cipher = AES.new(key.encode('utf-8'), AES.MODE_ECB)
# 解密
decrypted_data = cipher.decrypt(encrypted_data)
# 去除填充
text_bytes = unpad(decrypted_data, AES.block_size)
# 转换为字符串
return text_bytes.decode('utf-8')
# 使用示例
text = "这是要加密的敏感数据"
key = "my_secret_key_123" # 密钥
encrypted = aes_encrypt(text, key)
decrypted = aes_decrypt(encrypted, key)
print(f"原文: {text}")
print(f"加密: {encrypted}")
print(f"解密: {decrypted}")
注意: 使用 AES 需要安装 pycryptodome 库:
pip install pycryptodome
2.4.3 AES 加密的详细示例
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
import json
# 示例1:简单的 AES 加密解密
def test_aes_simple():
"""简单的 AES 加密解密"""
text = "hello world"
key = "1234567890123456" # 16字节密钥
# 加密
cipher = AES.new(key.encode('utf-8'), AES.MODE_ECB)
padded_data = pad(text.encode('utf-8'), AES.block_size)
encrypted = cipher.encrypt(padded_data)
encrypted_str = base64.b64encode(encrypted).decode('utf-8')
print(f"原文: {text}")
print(f"加密: {encrypted_str}")
# 解密
encrypted_bytes = base64.b64decode(encrypted_str)
cipher = AES.new(key.encode('utf-8'), AES.MODE_ECB)
decrypted = unpad(cipher.decrypt(encrypted_bytes), AES.block_size)
decrypted_str = decrypted.decode('utf-8')
print(f"解密: {decrypted_str}")
# 示例2:加密字典数据
def test_aes_dict():
"""加密字典数据"""
data = {
"username": "admin",
"password": "123456",
"id_card": "110101199001011234"
}
key = "my_secret_key_12345678" # 24字节密钥
# 将字典转换为 JSON 字符串
json_str = json.dumps(data, ensure_ascii=False)
# 加密
cipher = AES.new(key.encode('utf-8'), AES.MODE_ECB)
padded_data = pad(json_str.encode('utf-8'), AES.block_size)
encrypted = cipher.encrypt(padded_data)
encrypted_str = base64.b64encode(encrypted).decode('utf-8')
print(f"原始数据: {data}")
print(f"加密后: {encrypted_str}")
if __name__ == "__main__":
test_aes_simple()
test_aes_dict()
2.5 RSA 加密
2.5.1 什么是 RSA
RSA 是一种非对称加密算法,使用公钥加密、私钥解密。
RSA 的特点:
- 非对称加密:加密和解密使用不同的密钥
- 安全性高:适合加密小数据量
- 速度慢:不适合加密大量数据
- 用途:常用于加密密钥、数字签名等
2.5.2 RSA 的基本使用
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import base64
def generate_rsa_key():
"""生成 RSA 密钥对"""
# 生成 2048 位的密钥对
key = RSA.generate(2048)
# 私钥
private_key = key.export_key()
# 公钥
public_key = key.publickey().export_key()
return private_key, public_key
def rsa_encrypt(text, public_key_str):
"""
RSA 加密(使用公钥)
参数:
text: 要加密的文本
public_key_str: 公钥字符串
返回:
Base64 编码的加密字符串
"""
# 导入公钥
public_key = RSA.import_key(public_key_str)
# 创建加密器
cipher = PKCS1_v1_5.new(public_key)
# 加密(RSA 只能加密小于密钥长度的数据)
encrypted_data = cipher.encrypt(text.encode('utf-8'))
# Base64 编码
return base64.b64encode(encrypted_data).decode('utf-8')
def rsa_decrypt(encrypted_text, private_key_str):
"""
RSA 解密(使用私钥)
参数:
encrypted_text: Base64 编码的加密字符串
private_key_str: 私钥字符串
返回:
解密后的文本
"""
# 导入私钥
private_key = RSA.import_key(private_key_str)
# 创建解密器
cipher = PKCS1_v1_5.new(private_key)
# Base64 解码
encrypted_data = base64.b64decode(encrypted_text)
# 解密
decrypted_data = cipher.decrypt(encrypted_data, None)
# 转换为字符串
return decrypted_data.decode('utf-8')
# 使用示例
private_key, public_key = generate_rsa_key()
text = "这是要加密的数据"
encrypted = rsa_encrypt(text, public_key)
decrypted = rsa_decrypt(encrypted, private_key)
print(f"原文: {text}")
print(f"加密: {encrypted}")
print(f"解密: {decrypted}")
3. 接口签名算法
3.1 什么是接口签名
接口签名是一种验证请求合法性的机制,通过计算请求参数的签名值,服务器可以验证请求是否被篡改、是否来自合法客户端。
3.2 签名的基本原理
签名的基本流程:
- 客户端:将请求参数按照一定规则排序,拼接成字符串,加上密钥,计算签名
- 发送请求:将签名和参数一起发送给服务器
- 服务器:使用相同规则计算签名,对比签名是否一致
- 验证结果:签名一致则请求合法,不一致则拒绝请求
3.3 常见的签名算法
3.3.1 MD5 签名
import hashlib
import json
def generate_md5_sign(params, secret_key):
"""
生成 MD5 签名
参数:
params: 请求参数字典
secret_key: 密钥
返回:
签名字符串
"""
# 1. 移除签名字段(如果存在)
params = {k: v for k, v in params.items() if k != 'sign'}
# 2. 按键名排序
sorted_params = sorted(params.items())
# 3. 拼接成字符串(格式:key1=value1&key2=value2)
sign_string = "&".join([f"{k}={v}" for k, v in sorted_params])
# 4. 在末尾拼接密钥
sign_string += f"&key={secret_key}"
# 5. 计算 MD5
sign = hashlib.md5(sign_string.encode('utf-8')).hexdigest()
# 6. 转换为大写(可选)
return sign.upper()
# 使用示例
params = {
"order_id": "20240101001",
"amount": "100.00",
"timestamp": "1704067200"
}
secret_key = "my_secret_key_123"
sign = generate_md5_sign(params, secret_key)
print(f"签名: {sign}")
# 将签名添加到参数中
params["sign"] = sign
print(f"最终参数: {params}")
3.3.2 SHA256 签名
import hashlib
import hmac
def generate_sha256_sign(params, secret_key):
"""
生成 SHA256 签名(使用 HMAC)
参数:
params: 请求参数字典
secret_key: 密钥
返回:
签名字符串
"""
# 1. 移除签名字段
params = {k: v for k, v in params.items() if k != 'sign'}
# 2. 按键名排序
sorted_params = sorted(params.items())
# 3. 拼接成字符串
sign_string = "&".join([f"{k}={v}" for k, v in sorted_params])
# 4. 使用 HMAC-SHA256 计算签名
signature = hmac.new(
secret_key.encode('utf-8'),
sign_string.encode('utf-8'),
hashlib.sha256
).hexdigest()
return signature.upper()
# 使用示例
params = {
"order_id": "20240101001",
"amount": "100.00",
"timestamp": "1704067200"
}
secret_key = "my_secret_key_123"
sign = generate_sha256_sign(params, secret_key)
print(f"SHA256 签名: {sign}")
3.3.3 带时间戳和随机数的签名
import hashlib
import time
import random
import string
def generate_sign_with_nonce(params, secret_key):
"""
生成带时间戳和随机数的签名
参数:
params: 请求参数字典
secret_key: 密钥
返回:
签名字符串和更新后的参数字典
"""
# 1. 添加时间戳
params["timestamp"] = str(int(time.time()))
# 2. 添加随机数(防止重放攻击)
nonce = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
params["nonce"] = nonce
# 3. 移除签名字段
sign_params = {k: v for k, v in params.items() if k != 'sign'}
# 4. 按键名排序
sorted_params = sorted(sign_params.items())
# 5. 拼接成字符串
sign_string = "&".join([f"{k}={v}" for k, v in sorted_params])
# 6. 在末尾拼接密钥
sign_string += f"&key={secret_key}"
# 7. 计算 MD5
sign = hashlib.md5(sign_string.encode('utf-8')).hexdigest().upper()
# 8. 将签名添加到参数中
params["sign"] = sign
return sign, params
# 使用示例
params = {
"order_id": "20240101001",
"amount": "100.00"
}
secret_key = "my_secret_key_123"
sign, final_params = generate_sign_with_nonce(params, secret_key)
print(f"签名: {sign}")
print(f"最终参数: {final_params}")
4. 在 Pytest 中封装加密功能
4.1 创建加密工具类
4.1.1 基础加密工具类
首先,我们创建一个基础的加密工具类,封装常用的加密方法:
# utils/encrypt_utils.py
import hashlib
import hmac
import base64
import json
import time
import random
import string
from typing import Dict, Any, Optional
try:
from Crypto.Cipher import AES, PKCS1_v1_5
from Crypto.PublicKey import RSA
from Crypto.Util.Padding import pad, unpad
CRYPTO_AVAILABLE = True
except ImportError:
CRYPTO_AVAILABLE = False
print("警告: pycryptodome 未安装,AES 和 RSA 功能不可用")
class EncryptUtils:
"""加密工具类"""
@staticmethod
def md5(text: str) -> str:
"""
MD5 加密
参数:
text: 要加密的文本
返回:
32位16进制字符串
"""
return hashlib.md5(text.encode('utf-8')).hexdigest()
@staticmethod
def sha1(text: str) -> str:
"""SHA-1 加密"""
return hashlib.sha1(text.encode('utf-8')).hexdigest()
@staticmethod
def sha256(text: str) -> str:
"""SHA-256 加密"""
return hashlib.sha256(text.encode('utf-8')).hexdigest()
@staticmethod
def sha512(text: str) -> str:
"""SHA-512 加密"""
return hashlib.sha512(text.encode('utf-8')).hexdigest()
@staticmethod
def hmac_sha256(message: str, secret_key: str) -> str:
"""
HMAC-SHA256 签名
参数:
message: 要签名的消息
secret_key: 密钥
返回:
签名字符串
"""
return hmac.new(
secret_key.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).hexdigest()
@staticmethod
def base64_encode(text: str) -> str:
"""Base64 编码"""
return base64.b64encode(text.encode('utf-8')).decode('utf-8')
@staticmethod
def base64_decode(encoded_text: str) -> str:
"""Base64 解码"""
return base64.b64decode(encoded_text.encode('utf-8')).decode('utf-8')
@staticmethod
def aes_encrypt(text: str, key: str) -> str:
"""
AES 加密
参数:
text: 要加密的文本
key: 密钥(16/24/32字节)
返回:
Base64 编码的加密字符串
"""
if not CRYPTO_AVAILABLE:
raise ImportError("请安装 pycryptodome: pip install pycryptodome")
# 确保密钥长度正确
key = EncryptUtils._normalize_key(key)
# 创建加密器
cipher = AES.new(key.encode('utf-8'), AES.MODE_ECB)
# 填充数据
padded_data = pad(text.encode('utf-8'), AES.block_size)
# 加密
encrypted_data = cipher.encrypt(padded_data)
# Base64 编码
return base64.b64encode(encrypted_data).decode('utf-8')
@staticmethod
def aes_decrypt(encrypted_text: str, key: str) -> str:
"""
AES 解密
参数:
encrypted_text: Base64 编码的加密字符串
key: 密钥
返回:
解密后的文本
"""
if not CRYPTO_AVAILABLE:
raise ImportError("请安装 pycryptodome: pip install pycryptodome")
# 确保密钥长度正确
key = EncryptUtils._normalize_key(key)
# Base64 解码
encrypted_data = base64.b64decode(encrypted_text)
# 创建解密器
cipher = AES.new(key.encode('utf-8'), AES.MODE_ECB)
# 解密
decrypted_data = cipher.decrypt(encrypted_data)
# 去除填充
text_bytes = unpad(decrypted_data, AES.block_size)
return text_bytes.decode('utf-8')
@staticmethod
def _normalize_key(key: str) -> str:
"""规范化密钥长度"""
key_len = len(key)
if key_len < 16:
return key.ljust(16, '0')
elif key_len < 24:
return key.ljust(24, '0')
elif key_len < 32:
return key.ljust(32, '0')
else:
return key[:32]
4.1.2 签名工具类
创建专门的签名工具类:
# utils/sign_utils.py
import hashlib
import hmac
import json
import time
import random
import string
from typing import Dict, Any, Optional
from utils.encrypt_utils import EncryptUtils
class SignUtils:
"""签名工具类"""
@staticmethod
def generate_md5_sign(params: Dict[str, Any], secret_key: str,
exclude_keys: Optional[list] = None,
upper: bool = True) -> str:
"""
生成 MD5 签名
参数:
params: 请求参数字典
secret_key: 密钥
exclude_keys: 排除的键名列表(如 ['sign', 'signature'])
upper: 是否转换为大写
返回:
签名字符串
"""
# 1. 复制参数,避免修改原字典
sign_params = params.copy()
# 2. 移除签名字段和排除的字段
exclude_keys = exclude_keys or []
exclude_keys.extend(['sign', 'signature'])
sign_params = {k: v for k, v in sign_params.items()
if k not in exclude_keys}
# 3. 移除空值(可选)
sign_params = {k: v for k, v in sign_params.items() if v is not None and v != ''}
# 4. 按键名排序
sorted_params = sorted(sign_params.items())
# 5. 拼接成字符串
sign_string = "&".join([f"{k}={v}" for k, v in sorted_params])
# 6. 在末尾拼接密钥
sign_string += f"&key={secret_key}"
# 7. 计算 MD5
sign = EncryptUtils.md5(sign_string)
# 8. 转换为大写(如果需要)
return sign.upper() if upper else sign
@staticmethod
def generate_sha256_sign(params: Dict[str, Any], secret_key: str,
exclude_keys: Optional[list] = None,
upper: bool = True) -> str:
"""
生成 SHA256 签名(使用 HMAC)
参数:
params: 请求参数字典
secret_key: 密钥
exclude_keys: 排除的键名列表
upper: 是否转换为大写
返回:
签名字符串
"""
# 1. 复制参数
sign_params = params.copy()
# 2. 移除签名字段和排除的字段
exclude_keys = exclude_keys or []
exclude_keys.extend(['sign', 'signature'])
sign_params = {k: v for k, v in sign_params.items()
if k not in exclude_keys}
# 3. 移除空值
sign_params = {k: v for k, v in sign_params.items()
if v is not None and v != ''}
# 4. 按键名排序
sorted_params = sorted(sign_params.items())
# 5. 拼接成字符串
sign_string = "&".join([f"{k}={v}" for k, v in sorted_params])
# 6. 使用 HMAC-SHA256 计算签名
signature = EncryptUtils.hmac_sha256(sign_string, secret_key)
# 7. 转换为大写(如果需要)
return signature.upper() if upper else signature
@staticmethod
def generate_sign_with_timestamp(params: Dict[str, Any],
secret_key: str,
sign_type: str = 'md5',
add_timestamp: bool = True,
add_nonce: bool = True) -> tuple:
"""
生成带时间戳和随机数的签名
参数:
params: 请求参数字典
secret_key: 密钥
sign_type: 签名类型('md5' 或 'sha256')
add_timestamp: 是否添加时间戳
add_nonce: 是否添加随机数
返回:
(签名字符串, 更新后的参数字典)
"""
# 1. 复制参数
final_params = params.copy()
# 2. 添加时间戳
if add_timestamp:
final_params["timestamp"] = str(int(time.time()))
# 3. 添加随机数
if add_nonce:
nonce = ''.join(random.choices(
string.ascii_letters + string.digits, k=16
))
final_params["nonce"] = nonce
# 4. 生成签名
if sign_type.lower() == 'sha256':
sign = SignUtils.generate_sha256_sign(final_params, secret_key)
else:
sign = SignUtils.generate_md5_sign(final_params, secret_key)
# 5. 将签名添加到参数中
final_params["sign"] = sign
return sign, final_params
@staticmethod
def verify_sign(params: Dict[str, Any], secret_key: str,
sign_type: str = 'md5') -> bool:
"""
验证签名
参数:
params: 请求参数字典(包含 sign 字段)
secret_key: 密钥
sign_type: 签名类型('md5' 或 'sha256')
返回:
验证结果(True/False)
"""
# 1. 获取原始签名
original_sign = params.get('sign') or params.get('signature')
if not original_sign:
return False
# 2. 重新计算签名
if sign_type.lower() == 'sha256':
calculated_sign = SignUtils.generate_sha256_sign(params, secret_key)
else:
calculated_sign = SignUtils.generate_md5_sign(params, secret_key)
# 3. 对比签名
return original_sign.upper() == calculated_sign.upper()
4.2 封装加密的请求客户端
4.2.1 支持加密的 API 客户端
创建一个支持加密的 API 客户端类:
# api/encrypted_api_client.py
import requests
from typing import Dict, Any, Optional
from utils.encrypt_utils import EncryptUtils
from utils.sign_utils import SignUtils
class EncryptedAPIClient:
"""支持加密的 API 客户端"""
def __init__(self, base_url: str, secret_key: Optional[str] = None,
encrypt_config: Optional[Dict[str, Any]] = None):
"""
初始化加密 API 客户端
参数:
base_url: 基础 URL
secret_key: 密钥
encrypt_config: 加密配置
{
"password_encrypt": "md5", # 密码加密方式
"sign_type": "md5", # 签名类型
"auto_sign": True, # 是否自动签名
"sign_exclude_keys": [] # 签名排除的字段
}
"""
self.base_url = base_url.rstrip('/')
self.secret_key = secret_key
self.session = requests.Session()
# 默认加密配置
self.encrypt_config = {
"password_encrypt": "md5",
"sign_type": "md5",
"auto_sign": False,
"sign_exclude_keys": []
}
if encrypt_config:
self.encrypt_config.update(encrypt_config)
def _encrypt_password(self, password: str) -> str:
"""加密密码"""
encrypt_type = self.encrypt_config.get("password_encrypt", "md5")
if encrypt_type == "md5":
return EncryptUtils.md5(password)
elif encrypt_type == "sha1":
return EncryptUtils.sha1(password)
elif encrypt_type == "sha256":
return EncryptUtils.sha256(password)
else:
return password # 不加密
def _add_sign(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""添加签名"""
if not self.secret_key:
return params
sign_type = self.encrypt_config.get("sign_type", "md5")
exclude_keys = self.encrypt_config.get("sign_exclude_keys", [])
if sign_type == "sha256":
sign = SignUtils.generate_sha256_sign(params, self.secret_key, exclude_keys)
else:
sign = SignUtils.generate_md5_sign(params, self.secret_key, exclude_keys)
params["sign"] = sign
return params
def _process_params(self, params: Dict[str, Any],
encrypt_fields: Optional[list] = None,
add_sign: bool = False) -> Dict[str, Any]:
"""
处理参数(加密和签名)
参数:
params: 原始参数字典
encrypt_fields: 需要加密的字段列表(如 ['password'])
add_sign: 是否添加签名
返回:
处理后的参数字典
"""
# 1. 复制参数
processed_params = params.copy()
# 2. 加密指定字段
if encrypt_fields:
for field in encrypt_fields:
if field in processed_params:
if field == "password":
processed_params[field] = self._encrypt_password(
processed_params[field]
)
# 可以扩展其他字段的加密逻辑
# 3. 添加签名
if add_sign or self.encrypt_config.get("auto_sign", False):
processed_params = self._add_sign(processed_params)
return processed_params
def request(self, method: str, endpoint: str,
params: Optional[Dict[str, Any]] = None,
json_data: Optional[Dict[str, Any]] = None,
encrypt_fields: Optional[list] = None,
add_sign: bool = False,
**kwargs) -> requests.Response:
"""
发送请求
参数:
method: HTTP 方法
endpoint: 接口路径
params: URL 参数
json_data: JSON 数据
encrypt_fields: 需要加密的字段列表
add_sign: 是否添加签名
**kwargs: 其他 requests 参数
返回:
响应对象
"""
url = f"{self.base_url}/{endpoint.lstrip('/')}"
# 处理参数
if params:
params = self._process_params(params, encrypt_fields, add_sign)
if json_data:
json_data = self._process_params(json_data, encrypt_fields, add_sign)
# 发送请求
response = self.session.request(
method=method.upper(),
url=url,
params=params,
json=json_data,
**kwargs
)
return response
def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None,
add_sign: bool = False, **kwargs) -> requests.Response:
"""GET 请求"""
return self.request("GET", endpoint, params=params,
add_sign=add_sign, **kwargs)
def post(self, endpoint: str, json_data: Optional[Dict[str, Any]] = None,
params: Optional[Dict[str, Any]] = None,
encrypt_fields: Optional[list] = None,
add_sign: bool = False, **kwargs) -> requests.Response:
"""POST 请求"""
return self.request("POST", endpoint, json_data=json_data,
params=params, encrypt_fields=encrypt_fields,
add_sign=add_sign, **kwargs)
4.3 创建 Pytest Fixture
4.3.1 加密工具 Fixture
# conftest.py
import pytest
from utils.encrypt_utils import EncryptUtils
from utils.sign_utils import SignUtils
from api.encrypted_api_client import EncryptedAPIClient
@pytest.fixture(scope="session")
def encrypt_utils():
"""加密工具 Fixture"""
return EncryptUtils
@pytest.fixture(scope="session")
def sign_utils():
"""签名工具 Fixture"""
return SignUtils
@pytest.fixture(scope="session")
def encrypted_client():
"""加密 API 客户端 Fixture"""
base_url = "https://api.example.com"
secret_key = "my_secret_key_123"
encrypt_config = {
"password_encrypt": "md5",
"sign_type": "md5",
"auto_sign": False,
"sign_exclude_keys": []
}
return EncryptedAPIClient(base_url, secret_key, encrypt_config)
5. 实际应用示例
5.1 示例1:登录接口加密密码
5.1.1 使用加密工具类
# tests/test_login_encrypt.py
import pytest
from utils.encrypt_utils import EncryptUtils
def test_login_with_md5_password(encrypt_utils):
"""测试登录接口 - MD5 加密密码"""
url = "https://api.example.com/login"
username = "admin"
password = "123456"
# 使用 MD5 加密密码
encrypted_password = encrypt_utils.md5(password)
# 构造请求数据
data = {
"username": username,
"password": encrypted_password
}
# 发送请求
import requests
response = requests.post(url, json=data)
# 验证响应
assert response.status_code == 200
result = response.json()
assert result["code"] == 0
assert result["message"] == "登录成功"
print(f"原始密码: {password}")
print(f"加密后密码: {encrypted_password}")
print(f"登录结果: {result['message']}")
5.1.2 使用加密客户端
# tests/test_login_with_client.py
import pytest
from api.encrypted_api_client import EncryptedAPIClient
@pytest.fixture
def login_client():
"""登录客户端"""
return EncryptedAPIClient(
base_url="https://api.example.com",
encrypt_config={
"password_encrypt": "md5",
"auto_sign": False
}
)
def test_login(login_client):
"""测试登录接口"""
data = {
"username": "admin",
"password": "123456" # 会自动加密
}
# 发送请求,password 字段会自动加密
response = login_client.post(
"/login",
json_data=data,
encrypt_fields=["password"]
)
# 验证响应
assert response.status_code == 200
result = response.json()
assert result["code"] == 0
5.2 示例2:支付接口签名
5.2.1 手动生成签名
# tests/test_payment_sign.py
import pytest
from utils.sign_utils import SignUtils
import requests
def test_payment_with_sign(sign_utils):
"""测试支付接口 - 带签名"""
url = "https://api.example.com/payment"
secret_key = "my_secret_key_123"
# 支付参数
params = {
"order_id": "20240101001",
"amount": "100.00",
"user_id": "1001"
}
# 生成签名
sign = sign_utils.generate_md5_sign(params, secret_key)
# 添加签名到参数
params["sign"] = sign
# 发送请求
response = requests.post(url, json=params)
# 验证响应
assert response.status_code == 200
result = response.json()
assert result["code"] == 0
print(f"签名: {sign}")
print(f"请求参数: {params}")
5.2.2 使用加密客户端自动签名
# tests/test_payment_auto_sign.py
import pytest
from api.encrypted_api_client import EncryptedAPIClient
@pytest.fixture
def payment_client():
"""支付客户端"""
return EncryptedAPIClient(
base_url="https://api.example.com",
secret_key="my_secret_key_123",
encrypt_config={
"sign_type": "md5",
"auto_sign": True # 自动签名
}
)
def test_payment(payment_client):
"""测试支付接口 - 自动签名"""
data = {
"order_id": "20240101001",
"amount": "100.00",
"user_id": "1001"
}
# 发送请求,会自动添加签名
response = payment_client.post(
"/payment",
json_data=data,
add_sign=True # 明确指定添加签名
)
# 验证响应
assert response.status_code == 200
result = response.json()
assert result["code"] == 0
# 打印请求数据(可以看到自动添加的签名)
print(f"请求数据: {data}")
5.3 示例3:带时间戳和随机数的签名
# tests/test_sign_with_timestamp.py
import pytest
from utils.sign_utils import SignUtils
import requests
def test_api_with_timestamp_nonce(sign_utils):
"""测试接口 - 带时间戳和随机数的签名"""
url = "https://api.example.com/api/data"
secret_key = "my_secret_key_123"
# 原始参数
params = {
"action": "get_user_info",
"user_id": "1001"
}
# 生成带时间戳和随机数的签名
sign, final_params = sign_utils.generate_sign_with_timestamp(
params,
secret_key,
sign_type="md5",
add_timestamp=True,
add_nonce=True
)
# 发送请求
response = requests.post(url, json=final_params)
# 验证响应
assert response.status_code == 200
print(f"原始参数: {params}")
print(f"最终参数: {final_params}")
print(f"签名: {sign}")
5.4 示例4:AES 加密敏感数据
# tests/test_aes_encrypt.py
import pytest
from utils.encrypt_utils import EncryptUtils
import requests
def test_upload_id_card(encrypt_utils):
"""测试上传身份证 - AES 加密"""
url = "https://api.example.com/upload_id_card"
aes_key = "my_aes_key_12345678" # 16/24/32字节密钥
# 敏感数据
id_card = "110101199001011234"
# AES 加密
encrypted_id_card = encrypt_utils.aes_encrypt(id_card, aes_key)
# 构造请求数据
data = {
"user_id": "1001",
"id_card": encrypted_id_card # 加密后的身份证号
}
# 发送请求
response = requests.post(url, json=data)
# 验证响应
assert response.status_code == 200
print(f"原始身份证号: {id_card}")
print(f"加密后: {encrypted_id_card}")
# 验证解密(服务器端会解密)
decrypted = encrypt_utils.aes_decrypt(encrypted_id_card, aes_key)
assert decrypted == id_card
print(f"解密验证: {decrypted}")
5.5 示例5:使用 YAML 配置加密
5.5.1 YAML 测试用例
# test_cases/login_encrypt.yaml
test_login:
name: 登录接口测试 - MD5加密密码
request:
method: POST
url: /api/login
headers:
Content-Type: application/json
json:
username: admin
password: "123456" # 需要加密
encrypt:
fields:
- password
method: md5
validate:
- eq: [status_code, 200]
- eq: [json.code, 0]
- eq: [json.message, "登录成功"]
test_payment:
name: 支付接口测试 - MD5签名
request:
method: POST
url: /api/payment
headers:
Content-Type: application/json
json:
order_id: "20240101001"
amount: "100.00"
user_id: "1001"
sign:
type: md5
secret_key: "my_secret_key_123"
exclude_keys: []
validate:
- eq: [status_code, 200]
- eq: [json.code, 0]
5.5.2 读取 YAML 并执行加密
# tests/test_yaml_encrypt.py
import pytest
import yaml
from utils.encrypt_utils import EncryptUtils
from utils.sign_utils import SignUtils
import requests
@pytest.fixture
def test_case():
"""读取测试用例"""
with open("test_cases/login_encrypt.yaml", "r", encoding="utf-8") as f:
return yaml.safe_load(f)
def test_login_from_yaml(test_case, encrypt_utils):
"""从 YAML 读取用例并执行加密"""
case = test_case["test_login"]
request_config = case["request"]
encrypt_config = case.get("encrypt", {})
# 获取请求数据
json_data = request_config.get("json", {}).copy()
# 处理加密字段
encrypt_fields = encrypt_config.get("fields", [])
encrypt_method = encrypt_config.get("method", "md5")
for field in encrypt_fields:
if field in json_data:
original_value = json_data[field]
if encrypt_method == "md5":
json_data[field] = encrypt_utils.md5(original_value)
elif encrypt_method == "sha256":
json_data[field] = encrypt_utils.sha256(original_value)
# 发送请求
url = f"https://api.example.com{request_config['url']}"
response = requests.request(
method=request_config["method"],
url=url,
headers=request_config.get("headers", {}),
json=json_data
)
# 验证响应
assert response.status_code == 200
result = response.json()
assert result["code"] == 0
print(f"加密后的请求数据: {json_data}")
def test_payment_from_yaml(test_case, sign_utils):
"""从 YAML 读取用例并执行签名"""
case = test_case["test_payment"]
request_config = case["request"]
sign_config = case.get("sign", {})
# 获取请求数据
json_data = request_config.get("json", {}).copy()
# 生成签名
secret_key = sign_config.get("secret_key")
sign_type = sign_config.get("type", "md5")
exclude_keys = sign_config.get("exclude_keys", [])
if sign_type == "sha256":
sign = sign_utils.generate_sha256_sign(json_data, secret_key, exclude_keys)
else:
sign = sign_utils.generate_md5_sign(json_data, secret_key, exclude_keys)
json_data["sign"] = sign
# 发送请求
url = f"https://api.example.com{request_config['url']}"
response = requests.request(
method=request_config["method"],
url=url,
headers=request_config.get("headers", {}),
json=json_data
)
# 验证响应
assert response.status_code == 200
result = response.json()
assert result["code"] == 0
print(f"签名: {sign}")
print(f"请求数据: {json_data}")
6. 完整实战案例
6.1 案例:电商支付接口测试
6.1.1 需求分析
假设我们要测试一个电商支付接口,需要:
- 对密码进行 MD5 加密
- 对支付参数进行 MD5 签名
- 添加时间戳和随机数防止重放攻击
6.1.2 实现代码
# tests/test_ecommerce_payment.py
import pytest
import requests
from utils.encrypt_utils import EncryptUtils
from utils.sign_utils import SignUtils
class TestEcommercePayment:
"""电商支付接口测试"""
base_url = "https://api.ecommerce.com"
secret_key = "ecommerce_secret_key_123"
@pytest.fixture
def login_token(self, encrypt_utils):
"""登录获取 token"""
url = f"{self.base_url}/login"
username = "test_user"
password = "123456"
# 加密密码
encrypted_password = encrypt_utils.md5(password)
data = {
"username": username,
"password": encrypted_password
}
response = requests.post(url, json=data)
assert response.status_code == 200
result = response.json()
assert result["code"] == 0
token = result["data"]["token"]
return token
def test_payment(self, login_token, sign_utils):
"""测试支付接口"""
url = f"{self.base_url}/payment"
# 支付参数
params = {
"order_id": "ORDER20240101001",
"amount": "199.00",
"user_id": "1001",
"payment_method": "alipay"
}
# 生成带时间戳和随机数的签名
sign, final_params = sign_utils.generate_sign_with_timestamp(
params,
self.secret_key,
sign_type="md5",
add_timestamp=True,
add_nonce=True
)
# 添加 token
headers = {
"Authorization": f"Bearer {login_token}",
"Content-Type": "application/json"
}
# 发送请求
response = requests.post(url, json=final_params, headers=headers)
# 验证响应
assert response.status_code == 200
result = response.json()
assert result["code"] == 0
assert result["data"]["status"] == "success"
print(f"支付成功!订单号: {final_params['order_id']}")
print(f"签名: {sign}")
def test_payment_verify_sign(self, sign_utils):
"""测试签名验证"""
# 模拟服务器收到的请求参数
received_params = {
"order_id": "ORDER20240101001",
"amount": "199.00",
"user_id": "1001",
"timestamp": "1704067200",
"nonce": "abc123def456",
"sign": "A8F5F167F44F4964E6C998DEE827110C" # 假设的签名
}
# 验证签名
is_valid = sign_utils.verify_sign(
received_params,
self.secret_key,
sign_type="md5"
)
print(f"签名验证结果: {is_valid}")
# 注意:这个签名是假设的,实际验证会失败
# 但演示了如何验证签名
6.2 案例:多加密方式切换
6.2.1 需求分析
有些接口可能需要支持多种加密方式,我们可以通过配置来切换。
6.2.2 实现代码
# tests/test_multi_encrypt.py
import pytest
from utils.encrypt_utils import EncryptUtils
import requests
class TestMultiEncrypt:
"""多加密方式测试"""
base_url = "https://api.example.com"
@pytest.mark.parametrize("encrypt_method", ["md5", "sha1", "sha256"])
def test_login_with_different_encrypt(self, encrypt_method):
"""使用不同加密方式测试登录"""
url = f"{self.base_url}/login"
username = "admin"
password = "123456"
# 根据加密方式选择加密方法
if encrypt_method == "md5":
encrypted_password = EncryptUtils.md5(password)
elif encrypt_method == "sha1":
encrypted_password = EncryptUtils.sha1(password)
elif encrypt_method == "sha256":
encrypted_password = EncryptUtils.sha256(password)
else:
encrypted_password = password
data = {
"username": username,
"password": encrypted_password,
"encrypt_method": encrypt_method
}
response = requests.post(url, json=data)
print(f"加密方式: {encrypt_method}")
print(f"加密后密码: {encrypted_password}")
# 根据实际情况验证
assert response.status_code == 200
7. 常见问题和解决方案
7.1 问题1:签名验证失败
问题描述: 生成的签名总是验证失败
可能原因:
- 参数排序不一致
- 空值处理不一致
- 密钥不一致
- 编码问题
解决方案:
# 确保参数排序一致
def generate_sign_fixed(params, secret_key):
"""修复后的签名生成"""
# 1. 移除签名字段
sign_params = {k: v for k, v in params.items() if k != 'sign'}
# 2. 移除空值和None值
sign_params = {k: v for k, v in sign_params.items()
if v is not None and v != ''}
# 3. 按键名排序(确保顺序一致)
sorted_params = sorted(sign_params.items())
# 4. 拼接字符串(注意格式)
sign_string = "&".join([f"{k}={v}" for k, v in sorted_params])
# 5. 拼接密钥
sign_string += f"&key={secret_key}"
# 6. 确保使用 UTF-8 编码
sign = hashlib.md5(sign_string.encode('utf-8')).hexdigest()
return sign.upper()
7.2 问题2:AES 加密解密失败
问题描述: AES 加密后无法解密
可能原因:
- 密钥长度不正确
- 填充方式不一致
- 编码问题
解决方案:
# 确保密钥长度正确
def aes_encrypt_fixed(text, key):
"""修复后的 AES 加密"""
# 规范化密钥长度
if len(key) < 16:
key = key.ljust(16, '0')
elif len(key) < 24:
key = key.ljust(24, '0')
elif len(key) < 32:
key = key.ljust(32, '0')
else:
key = key[:32]
# 确保文本编码正确
text_bytes = text.encode('utf-8')
# 使用正确的填充方式
cipher = AES.new(key.encode('utf-8'), AES.MODE_ECB)
padded_data = pad(text_bytes, AES.block_size)
encrypted_data = cipher.encrypt(padded_data)
# Base64 编码
return base64.b64encode(encrypted_data).decode('utf-8')
7.3 问题3:时间戳格式不一致
问题描述: 服务器要求的时间戳格式与生成的不一致
解决方案:
import time
from datetime import datetime
def get_timestamp(format_type="unix"):
"""
获取时间戳
参数:
format_type: 格式类型
- "unix": Unix 时间戳(秒)
- "unix_ms": Unix 时间戳(毫秒)
- "datetime": 日期时间字符串
"""
if format_type == "unix":
return str(int(time.time()))
elif format_type == "unix_ms":
return str(int(time.time() * 1000))
elif format_type == "datetime":
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
else:
return str(int(time.time()))
# 使用示例
timestamp1 = get_timestamp("unix") # "1704067200"
timestamp2 = get_timestamp("unix_ms") # "1704067200000"
timestamp3 = get_timestamp("datetime") # "2024-01-01 00:00:00"
8. 最佳实践
8.1 密钥管理
不要硬编码密钥:
# ❌ 错误做法
secret_key = "my_secret_key_123"
# ✅ 正确做法:使用环境变量
import os
from dotenv import load_dotenv
load_dotenv()
secret_key = os.getenv("SECRET_KEY")
创建 .env 文件:
# .env
SECRET_KEY=my_secret_key_123
AES_KEY=my_aes_key_12345678
8.2 错误处理
def safe_encrypt(text, method="md5"):
"""安全的加密函数(带错误处理)"""
try:
if method == "md5":
return EncryptUtils.md5(text)
elif method == "sha256":
return EncryptUtils.sha256(text)
else:
raise ValueError(f"不支持的加密方式: {method}")
except Exception as e:
print(f"加密失败: {e}")
return None
8.3 日志记录
import logging
logger = logging.getLogger(__name__)
def encrypt_with_log(text, method="md5"):
"""带日志记录的加密"""
logger.info(f"开始加密,方法: {method}, 原文长度: {len(text)}")
try:
encrypted = EncryptUtils.md5(text) if method == "md5" else EncryptUtils.sha256(text)
logger.info(f"加密成功,密文: {encrypted[:10]}...")
return encrypted
except Exception as e:
logger.error(f"加密失败: {e}")
raise
8.4 单元测试
# tests/test_encrypt_utils.py
import pytest
from utils.encrypt_utils import EncryptUtils
class TestEncryptUtils:
"""加密工具类测试"""
def test_md5(self):
"""测试 MD5 加密"""
text = "hello world"
result = EncryptUtils.md5(text)
assert len(result) == 32 # MD5 结果是32位
assert result == "5eb63bbbe01eeed093cb22bb8f5acdc3"
def test_sha256(self):
"""测试 SHA256 加密"""
text = "hello world"
result = EncryptUtils.sha256(text)
assert len(result) == 64 # SHA256 结果是64位
def test_base64_encode_decode(self):
"""测试 Base64 编码解码"""
text = "hello world"
encoded = EncryptUtils.base64_encode(text)
decoded = EncryptUtils.base64_decode(encoded)
assert decoded == text
@pytest.mark.skipif(not CRYPTO_AVAILABLE, reason="pycryptodome 未安装")
def test_aes_encrypt_decrypt(self):
"""测试 AES 加密解密"""
text = "hello world"
key = "1234567890123456"
encrypted = EncryptUtils.aes_encrypt(text, key)
decrypted = EncryptUtils.aes_decrypt(encrypted, key)
assert decrypted == text
9. 总结
9.1 核心要点
- 理解加密原理:了解各种加密算法的特点和用途
- 封装工具类:将加密功能封装成可复用的工具类
- 统一接口:提供统一的加密接口,方便调用
- 配置化管理:通过配置支持多种加密方式
- 错误处理:完善的错误处理和日志记录
- 安全实践:密钥管理、防止重放攻击等
9.2 学习路径
- 基础阶段:掌握 MD5、SHA、Base64 等基础加密
- 进阶阶段:学习 AES、RSA 等高级加密
- 实战阶段:封装加密工具类,应用到接口测试
- 优化阶段:完善错误处理、日志记录、单元测试
9.3 扩展学习
- JWT Token:学习 JWT 的生成和验证
- OAuth 2.0:了解 OAuth 2.0 认证流程
- HTTPS:理解 HTTPS 的工作原理
- 数字签名:深入学习数字签名的原理和应用
10. 附录
10.1 依赖安装
# 基础依赖
pip install requests pytest pyyaml
# 加密依赖
pip install pycryptodome
# 环境变量管理
pip install python-dotenv
10.2 完整项目结构
project/
├── utils/
│ ├── __init__.py
│ ├── encrypt_utils.py # 加密工具类
│ └── sign_utils.py # 签名工具类
├── api/
│ ├── __init__.py
│ └── encrypted_api_client.py # 加密 API 客户端
├── tests/
│ ├── __init__.py
│ ├── conftest.py # Pytest 配置
│ ├── test_login_encrypt.py
│ ├── test_payment_sign.py
│ └── test_aes_encrypt.py
├── test_cases/
│ └── login_encrypt.yaml
├── .env # 环境变量(不提交到 Git)
├── requirements.txt
└── README.md
10.3 参考资源
祝学习愉快!如有问题,欢迎查阅文档或寻求帮助。