快递100 地址解析 API 加密方式说明
一、加密概述
快递100地址解析接口支持参数加密传输,使用 AES-256-CBC 算法对业务参数进行加密,有效保护数据传输安全。
加密核心要素
| 项目 | 说明 |
|---|---|
| 加密算法 | AES-256-CBC |
| 填充方式 | PKCS5Padding |
| 密钥派生 | HMAC-SHA256 |
| IV长度 | 16字节(128位)随机数 |
| AES密钥长度 | 256位(32字节) |
| 输出编码 | Base64 |
二、加密原理图解
原始明文业务 JSON
{"content":"李雷13544765984广东省深圳市南山区金蝶软件园"}
│
▼
步骤① ─── 生成16字节随机IV
│
▼
步骤② ─── 密钥派生:AES_KEY = HMAC-SHA256(secret, IV) 取前32字节
│
▼
步骤③ ─── AES-256-CBC 加密(PKCS5Padding填充)
│
▼
步骤④ ─── 拼接:IV(16字节) + 密文字节
│
▼
步骤⑤ ─── Base64编码,得到最终密文
密文结构(Base64解码后)
┌──────────────────────┬────────────────────────────────┐
│ IV (16 bytes) │ AES 密文 (N bytes) │
└──────────────────────┴────────────────────────────────┘
设计优势:IV直接嵌入密文头部,服务端解密时自动提取,调用方无需单独传输IV。
三、逐步加密示例
准备工作
假设你有以下凭证(从快递100开放平台获取):
API密钥 (secret): 0e639d545654565455f2944da77eb
业务参数 (param字段内容): {"content":"李雷13544765984广东省深圳市南山区金蝶软件园"}
步骤①:生成随机IV(16字节)
使用安全的随机数生成器,生成16字节的随机数据。
示例IV(十六进制表示):
4c c4 4e 87 e7 74 a6 e7 f5 7a d3 0e 8b a7 76 3b
⚠️ 重要:每次加密必须使用不同的随机IV,确保相同明文每次产生不同密文。
步骤②:密钥派生
使用 HMAC-SHA256 算法,以 secret 为密钥,IV 为消息,计算HMAC值,
然后取结果的前32字节(256位)作为AES密钥。
派生过程:
HMAC_INPUT = IV (16字节)
HMAC_KEY = secret字符串的UTF-8编码
HMAC_RESULT = HMAC-SHA256(HMAC_KEY, HMAC_INPUT) // 输出32字节
AES_KEY = HMAC_RESULT 的前32字节
计算示例:
HMAC-SHA256("0e639d6545606545f2944da77eb", IV_16Bytes)
= e8 f2 a3 4b 7c 9d 1e 5f 2a 6b 8d 4e f1 c3 7a 9b
d4 e6 5f 2c 8a 1b 3d 7e 4f 9c 6a b2 5e 8d f0 1a
取前32字节作为AES_KEY:
e8 f2 a3 4b 7c 9d 1e 5f 2a 6b 8d 4e f1 c3 7a 9b
d4 e6 5f 2c 8a 1b 3d 7e 4f 9c 6a b2 5e 8d f0 1a
💡 设计要点:密钥派生结合了固定密钥(secret)和随机IV,增强了安全性。即使知道secret,没有IV也无法推导出AES密钥。
步骤③:AES-256-CBC 加密
使用步骤②生成的AES_KEY和步骤①的IV,对明文JSON进行AES-256-CBC加密。
加密参数:
算法 : AES/CBC/PKCS5Padding
密钥 : AES_KEY (32字节)
IV : 原始IV (16字节)
明文 : {"content":"李雷13544765984广东省深圳市南山区金蝶软件园"}
得到密文(部分字节示例):
8a 3b 7c 9d 2e 5f 1a 6b 4c 8e f3 a7 d0 2b 9c 6e
f5 1a 3d 7e 8c 4f 6b 2a 9e 5d b1 3f 7a c2 8d 0e
...
步骤④:拼接 IV + 密文
将16字节IV与加密后的密文拼接,形成完整密文字节数组:
COMBINED = IV (16字节) + CIPHERTEXT (N字节)
字节序列示意:
[4c c4 4e 87 ... 76 3b] [8a 3b 7c 9d ... 8d 0e]
├─── 16字节IV ────┤ ├─── N字节密文 ────┤
步骤⑤:Base64 编码
将拼接后的完整字节数组进行标准Base64编码,得到最终传输的密文:
Base64(COMBINED) = "TMRNh+d0puf1etMOi6d2O4o7fJ0uXxpqTI7zp9Arm+71Gj1+jE9rKp5dsT96wo0O..."
这个Base64字符串就是调用API时的 param 参数值。
四、完整加密代码实现
Java 实现
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
public class EncryptDemo {
public static String encrypt(String plaintext, String secret) throws Exception {
// 步骤①:生成16字节随机IV
SecureRandom random = new SecureRandom();
byte[] iv = new byte[16];
random.nextBytes(iv);
// 步骤②:密钥派生 - HMAC-SHA256(secret, IV) 取前32字节
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec macKey = new SecretKeySpec(
secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
mac.init(macKey);
byte[] hmacResult = mac.doFinal(iv);
byte[] aesKey = new byte[32];
System.arraycopy(hmacResult, 0, aesKey, 0, 32);
// 步骤③:AES-256-CBC加密
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec aesKeySpec = new SecretKeySpec(aesKey, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, aesKeySpec, ivSpec);
byte[] ciphertext = cipher.doFinal(
plaintext.getBytes(StandardCharsets.UTF_8));
// 步骤④⑤:拼接IV+密文,并Base64编码
byte[] combined = new byte[16 + ciphertext.length];
System.arraycopy(iv, 0, combined, 0, 16);
System.arraycopy(ciphertext, 0, combined, 16, ciphertext.length);
return Base64.getEncoder().encodeToString(combined);
}
public static void main(String[] args) throws Exception {
String secret = "0e639dbb65456565545bf2944da77eb";
String plaintext = "{\"content\":\"李雷13544765984广东省深圳市南山区金蝶软件园\"}";
String encryptedParam = encrypt(plaintext, secret);
System.out.println("加密结果: " + encryptedParam);
}
}
Python 实现
import os
import hmac
import hashlib
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
def encrypt_param(plaintext: str, secret: str) -> str:
"""
快递100地址解析API - 业务参数加密
Args:
plaintext: 明文JSON字符串
secret: API密钥
Returns:
Base64编码的密文
"""
# 步骤①:生成16字节随机IV
iv = os.urandom(16)
# 步骤②:密钥派生
aes_key = hmac.new(
secret.encode('utf-8'),
iv,
hashlib.sha256
).digest()[:32]
# 步骤③:AES-256-CBC加密
cipher = AES.new(aes_key, AES.MODE_CBC, iv)
ciphertext = cipher.encrypt(
pad(plaintext.encode('utf-8'), AES.block_size)
)
# 步骤④⑤:拼接并Base64编码
return base64.b64encode(iv + ciphertext).decode('utf-8')
# 使用示例
if __name__ == "__main__":
secret = "0e639dbb5494460b8833bf2944da77eb"
plaintext = '{"content":"李雷13544765984广东省深圳市南山区金蝶软件园"}'
encrypted = encrypt_param(plaintext, secret)
print(f"加密结果: {encrypted}")
五、常见问题与注意事项
⚠️ 关键注意点
| 注意点 | 说明 |
|---|---|
| IV必须随机 | 每次加密生成新的随机IV,绝不能重复使用 |
| 密钥派生 | 必须使用HMAC-SHA256,取前32字节作为AES密钥 |
| 字节顺序 | Base64编码前,IV必须在密文之前(IV在前,密文在后) |
| 字符编码 | 明文JSON和secret都使用UTF-8编码 |
| URL编码 | 密文通过URL传输时,需对特殊字符进行URL编码 |
🔧 调试检查清单
如果解密失败(错误码10027),请依次检查:
☐ ① 加密算法是否正确? → AES-256-CBC + PKCS5Padding
☐ ② 密钥派生是否正确? → HMAC-SHA256(secret, IV) 取前32字节
☐ ③ IV长度是否为16字节?
☐ ④ 密文结构是否正确? → Base64(IV + 密文)
☐ ⑤ secret是否正确? → 检查是否与开放平台显示一致
☐ ⑥ IV和密文的拼接顺序是否正确? → IV在前,密文在后
📋 加密模式 vs 明文模式
| 对比项 | 加密模式(推荐) | 明文模式 |
|---|---|---|
| 安全性 | ⭐⭐⭐⭐⭐ 高 | ⭐⭐ 低 |
| 传输内容 | Base64密文 | 明文JSON |
| 适用场景 | 生产环境 | 调试/测试 |
| encryptType参数 | HMAC-SHA256 | 不传 |
| param内容 | 加密后的Base64字符串 | 原始JSON字符串 |
💡 建议:生产环境务必使用加密模式,保护用户隐私数据在传输过程中的安全。
六、完整调用流程整合
当你完成加密后,接下来还需要计算签名并发起请求:
// 1. 加密得到密文
param = encrypt(plaintext, secret)
= "TMRNh+d0puf1etMOi6d2O4o7fJ0uXxpqTI7..."
// 2. 计算签名(使用密文!)
t = "1757904767424"
data = param + t + key + secret
sign = HMAC-SHA256(data, secret).toUpperCase()
= "564C292856A8C1079F660805A1EB0736"
// 3. 发送请求
GET /api/address/resolution?
key=YOUR_KEY&
t=1757904767424&
sign=564C292856A8C1079F660805A1EB0736&
signMethod=hmac-sha256&
encryptType=HMAC-SHA256&
param=TMRNh%2Bd0puf... ← 需要URL编码
🔑 关键:签名计算时使用的param必须是加密后的密文,不是原始JSON!