外观
响应加密
NullVerify 支持对客户端 API 的响应内容进行加密,防止中间人窃取业务数据。
支持的算法
| 算法 | 密钥来源 | 密文格式 | 安全性 |
|---|---|---|---|
rc4 (默认) | 独立加密密钥 | base64(cipher) | 低,仅防简单抓包 |
aes-cbc | 独立加密密钥 | base64(iv).base64(cipher) | 中,推荐对称方案 |
des-cbc | 独立加密密钥 | base64(iv).base64(cipher) | 低,仅兼容旧系统 |
rsa | 开发者上传 RSA 公钥 | base64(rsaKey).base64(iv).base64(cipher) | 高,推荐非对称方案 |
加密算法必选
创建应用时默认使用 RC4 加密,所有应用必须选择一种加密算法,不支持关闭加密。
配置方式
在管理后台 → 应用配置 → 安全配置 中选择响应加密算法。
- 对称加密(RC4 / AES-CBC / DES-CBC):系统自动生成独立加密密钥,可在安全配置页查看和重新生成。
- RSA 混合加密:需上传 RSA 公钥(PEM 格式,≥ 2048 位)。
注意
修改加密配置后,所有客户端必须同步更新解密逻辑,否则将无法解析响应。建议先在测试环境验证后再上线。
加密流程
启用响应加密后,原始的业务 JSON 响应会被整体加密,并包装在新的 JSON 结构中返回:
json
// 未加密(原始业务响应)
{
"code": 0,
"message": "ok",
"result": { "token": "abc123", "expires": "2026-12-31 23:59:59" }
}
// 加密后(以 AES-CBC 为例)
{
"code": 200,
"message": "ok",
"encrypted": true,
"data": {
"ciphertext": "dGhpcyBpcyBpdi4=.Y2lwaGVydGV4dC4="
}
}勘误说明
外层包装结构的 code(固定 200)和 message 是明文占位,不代表业务状态。客户端收到响应后,需先判断 encrypted 是否为 true,然后解密 data.ciphertext,从解密后的 JSON 中读取实际的业务 code、message 和 result。
客户端解密
解密步骤
- 判断是否加密:检查响应 JSON 的
encrypted字段是否为true。 - 提取密文:获取
data.ciphertext字段值。 - 执行解密:根据算法规则解密密文字符串,得到明文 JSON。
- 解析业务响应:将明文解析为 JSON 对象,从中读取实际的
code、message和result。
对称加密(RC4 / AES-CBC / DES-CBC)
密钥获取:在管理后台 → 应用配置 → 安全配置 中查看独立加密密钥(64 字符 hex 字符串)。
客户端需将 hex 密钥解码为字节数组,并按算法取指定长度前缀作为实际密钥:
key = hex_decode(encrypt_key)[:keyLen]| 算法 | keyLen(字节) |
|---|---|
rc4 | 16 |
aes-cbc | 32 |
des-cbc | 8 |
RC4 解密
python
import base64
def get_key(encrypt_key_hex: str, length: int) -> bytes:
return bytes.fromhex(encrypt_key_hex)[:length]
def rc4_decrypt(key: bytes, ciphertext_b64: str) -> str:
data = base64.b64decode(ciphertext_b64)
# RC4 加解密逻辑相同
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
out = bytearray(len(data))
i = j = 0
for k in range(len(data)):
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
out[k] = data[k] ^ S[(S[i] + S[j]) % 256]
return out.decode('utf-8')
# 使用:encrypt_key 从管理后台安全配置页获取
# ciphertext_field 从响应的 data.ciphertext 获取
key = get_key("your_encrypt_key_hex", 16)
plaintext = rc4_decrypt(key, ciphertext_field)AES-CBC 解密
密文格式:base64(iv).base64(ciphertext),使用 . 分隔。
python
import base64
from Crypto.Cipher import AES
def aes_cbc_decrypt(key: bytes, encrypted: str) -> str:
parts = encrypted.split(".")
iv = base64.b64decode(parts[0])
ciphertext = base64.b64decode(parts[1])
cipher = AES.new(key, AES.MODE_CBC, iv)
padded = cipher.decrypt(ciphertext)
# PKCS#7 去填充
pad_len = padded[-1]
return padded[:-pad_len].decode('utf-8')
key = get_key("your_encrypt_key_hex", 32)
# ciphertext_field 从响应的 data.ciphertext 获取
plaintext = aes_cbc_decrypt(key, ciphertext_field)DES-CBC 解密
密文格式与 AES-CBC 相同:base64(iv).base64(ciphertext)。
python
from Crypto.Cipher import DES
key = get_key("your_encrypt_key_hex", 8)
# 解密逻辑同 AES-CBC,替换为 DES.new(key, DES.MODE_CBC, iv)RSA 混合加密解密
密文格式:base64(rsaEncryptedKey).base64(iv).base64(ciphertext),使用 . 分隔为 3 段。
解密流程:
- 用私钥通过 RSA-OAEP (SHA-256) 解密第一段,得到 AES-256 密钥
- 用该 AES 密钥 + IV(第二段)通过 AES-CBC 解密第三段
- 去除 PKCS#7 填充
python
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP, AES
from Crypto.Hash import SHA256
import base64
def rsa_hybrid_decrypt(private_key_pem: str, encrypted: str) -> str:
parts = encrypted.split(".")
enc_key = base64.b64decode(parts[0])
iv = base64.b64decode(parts[1])
ciphertext = base64.b64decode(parts[2])
# RSA-OAEP 解密 AES key
rsa_key = RSA.import_key(private_key_pem)
cipher_rsa = PKCS1_OAEP.new(rsa_key, hashAlgo=SHA256)
aes_key = cipher_rsa.decrypt(enc_key)
# AES-CBC 解密数据
cipher_aes = AES.new(aes_key, AES.MODE_CBC, iv)
padded = cipher_aes.decrypt(ciphertext)
pad_len = padded[-1]
return padded[:-pad_len].decode('utf-8')RSA 公钥要求
- 格式:PEM(
-----BEGIN PUBLIC KEY-----) - 最小位数:2048 位
- 上传后系统自动计算 SHA-256 指纹用于标识
算法选择建议
| 场景 | 推荐算法 | 原因 |
|---|---|---|
| 快速接入、低安全需求 | rc4 | 实现简单,性能最好 |
| 通用场景 | aes-cbc | 安全性与性能平衡 |
| 兼容旧系统 | des-cbc | 仅在旧系统无法升级时使用 |
| 高安全需求 | rsa | 密钥不暴露在客户端,最安全 |
对应实现
- 加密逻辑:
pkg/crypto/response_encrypt.go - 响应中间件:
internal/platform/httpx/response.go