24.pytest的接口加密封装

Pytest 接口加密封装详解 – 新手完整教程

1. 什么是接口加密

1.1 接口加密的基本概念

接口加密是指在接口请求和响应过程中,对数据进行加密处理,以确保数据的安全性和完整性。在实际的接口测试中,我们经常会遇到需要加密或签名的接口。

1.2 为什么需要接口加密

想象一下,如果你在银行转账:

不加密的情况:

你发送:我要给张三转1000元
服务器收到:我要给张三转1000元

加密的情况:

你发送:a8f5f167f44f4964e6c998dee827110c(加密后的数据)
服务器收到后解密:我要给张三转1000元

接口加密的主要目的:

  1. 数据安全:防止数据在传输过程中被窃取或篡改
  2. 身份验证:确保请求来自合法的客户端
  3. 防止重放攻击:通过时间戳、随机数等机制防止请求被重复使用
  4. 数据完整性:确保数据在传输过程中没有被修改

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 接口加密的常见方式

接口加密主要有以下几种方式:

  1. MD5 加密:单向加密,常用于密码加密和签名
  2. SHA 加密:单向加密,比MD5更安全
  3. AES 加密:对称加密,速度快,适合大数据量
  4. RSA 加密:非对称加密,安全性高,适合小数据量
  5. Base64 编码:不是加密,只是编码,但常用于数据传输
  6. 签名算法:通过签名验证请求的合法性

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 签名的基本原理

签名的基本流程:

  1. 客户端:将请求参数按照一定规则排序,拼接成字符串,加上密钥,计算签名
  2. 发送请求:将签名和参数一起发送给服务器
  3. 服务器:使用相同规则计算签名,对比签名是否一致
  4. 验证结果:签名一致则请求合法,不一致则拒绝请求

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 需求分析

假设我们要测试一个电商支付接口,需要:

  1. 对密码进行 MD5 加密
  2. 对支付参数进行 MD5 签名
  3. 添加时间戳和随机数防止重放攻击

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:签名验证失败

问题描述: 生成的签名总是验证失败

可能原因:

  1. 参数排序不一致
  2. 空值处理不一致
  3. 密钥不一致
  4. 编码问题

解决方案:

# 确保参数排序一致
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 加密后无法解密

可能原因:

  1. 密钥长度不正确
  2. 填充方式不一致
  3. 编码问题

解决方案:

# 确保密钥长度正确
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 核心要点

  1. 理解加密原理:了解各种加密算法的特点和用途
  2. 封装工具类:将加密功能封装成可复用的工具类
  3. 统一接口:提供统一的加密接口,方便调用
  4. 配置化管理:通过配置支持多种加密方式
  5. 错误处理:完善的错误处理和日志记录
  6. 安全实践:密钥管理、防止重放攻击等

9.2 学习路径

  1. 基础阶段:掌握 MD5、SHA、Base64 等基础加密
  2. 进阶阶段:学习 AES、RSA 等高级加密
  3. 实战阶段:封装加密工具类,应用到接口测试
  4. 优化阶段:完善错误处理、日志记录、单元测试

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 参考资源


祝学习愉快!如有问题,欢迎查阅文档或寻求帮助。

发表评论