概述
环能聚合API采用AES-256-GCM对称加密算法保护敏感数据传输,确保数据的机密性、完整性和认证性。所有敏感字段在传输前必须进行加密处理。
加密规范
算法:
AES-256-GCM
密钥长度:
256位 (32字节)
IV长度:
96位 (12字节)
认证标签:
128位 (16字节)
模式:
AEAD (Authenticated Encryption with Associated Data)
加密流程
步骤1:密钥派生
PBKDF2密钥派生
加密密钥 = PBKDF2(
password: app_secret,
salt: app_key + timestamp,
iterations: 10000,
key_length: 32 bytes,
hash_algorithm: SHA-256
)
步骤2:生成初始化向量(IV)
IV生成规则
- 使用加密安全的随机数生成器
- IV长度为12字节(96位)
- 每次加密必须使用不同的IV
- IV可以明文传输,不需要保密
Python示例
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
def generate_iv():
return os.urandom(12) # 96位IV
步骤3:数据加密
加密函数
加密数据 = AES-256-GCM-加密(
key: 加密密钥,
iv: 初始化向量,
plaintext: 原始数据,
associated_data: 附加认证数据
)
完整加密示例
import json
import base64
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.backends import default_backend
def encrypt_sensitive_data(data, app_key, app_secret, timestamp):
"""
加密敏感数据
Args:
data: 要加密的数据(dict或str)
app_key: 应用标识
app_secret: 应用密钥
timestamp: 时间戳
Returns:
dict: 包含加密结果和元数据
"""
# 1. 准备数据
if isinstance(data, dict):
plaintext = json.dumps(data, separators=(',', ':'))
else:
plaintext = str(data)
plaintext_bytes = plaintext.encode('utf-8')
# 2. 派生密钥
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=f"{app_key}{timestamp}".encode()[:16],
iterations=10000,
backend=default_backend()
)
key = kdf.derive(app_secret.encode())
# 3. 生成IV
iv = os.urandom(12)
# 4. 加密数据
aesgcm = AESGCM(key)
associated_data = f"{app_key}{timestamp}".encode()
ciphertext = aesgcm.encrypt(iv, plaintext_bytes, associated_data)
# 5. 分离认证标签
encrypted_data = ciphertext[:-16]
auth_tag = ciphertext[-16:]
# 6. 返回结果
return {
"encrypted_data": base64.b64encode(encrypted_data).decode(),
"iv": base64.b64encode(iv).decode(),
"auth_tag": base64.b64encode(auth_tag).decode(),
"algorithm": "AES-256-GCM",
"key_derivation": "PBKDF2-SHA256",
"iterations": 10000
}
步骤4:数据解密
解密函数
def decrypt_sensitive_data(encrypted_package, app_key, app_secret, timestamp):
"""
解密敏感数据
Args:
encrypted_package: 加密包(包含encrypted_data, iv, auth_tag)
app_key: 应用标识
app_secret: 应用密钥
timestamp: 时间戳
Returns:
str: 解密后的原始数据
"""
try:
# 1. 解码数据
encrypted_data = base64.b64decode(encrypted_package["encrypted_data"])
iv = base64.b64decode(encrypted_package["iv"])
auth_tag = base64.b64decode(encrypted_package["auth_tag"])
# 2. 派生密钥(与加密时相同)
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=f"{app_key}{timestamp}".encode()[:16],
iterations=10000,
backend=default_backend()
)
key = kdf.derive(app_secret.encode())
# 3. 组合密文和认证标签
ciphertext = encrypted_data + auth_tag
# 4. 解密数据
aesgcm = AESGCM(key)
associated_data = f"{app_key}{timestamp}".encode()
plaintext_bytes = aesgcm.decrypt(iv, ciphertext, associated_data)
# 5. 返回解密结果
return plaintext_bytes.decode('utf-8')
except Exception as e:
raise ValueError(f"解密失败: {str(e)}")
Java实现示例
Java加密工具类
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Base64;
import com.fasterxml.jackson.databind.ObjectMapper;
public class AesEncryptionUtil {
private static final int GCM_TAG_LENGTH = 16; // 128 bits
private static final int GCM_IV_LENGTH = 12; // 96 bits
private static final int KEY_LENGTH = 32; // 256 bits
private static final int PBKDF2_ITERATIONS = 10000;
public static class EncryptedData {
public String encrypted_data;
public String iv;
public String auth_tag;
public String algorithm = "AES-256-GCM";
public String key_derivation = "PBKDF2-SHA256";
public int iterations = 10000;
}
public static EncryptedData encrypt(Object data, String appKey, String appSecret, long timestamp)
throws Exception {
// 1. 准备数据
ObjectMapper mapper = new ObjectMapper();
String plaintext = mapper.writeValueAsString(data);
// 2. 派生密钥
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
String salt = appKey + timestamp;
KeySpec spec = new PBEKeySpec(appSecret.toCharArray(), salt.getBytes(), PBKDF2_ITERATIONS, KEY_LENGTH * 8);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
// 3. 生成IV
byte[] iv = new byte[GCM_IV_LENGTH];
new SecureRandom().nextBytes(iv);
// 4. 加密数据
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec);
byte[] aad = (appKey + timestamp).getBytes();
cipher.updateAAD(aad);
byte[] ciphertext = cipher.doFinal(plaintext.getBytes("UTF-8"));
// 5. 分离数据
byte[] encryptedData = new byte[ciphertext.length - GCM_TAG_LENGTH];
byte[] authTag = new byte[GCM_TAG_LENGTH];
System.arraycopy(ciphertext, 0, encryptedData, 0, encryptedData.length);
System.arraycopy(ciphertext, encryptedData.length, authTag, 0, authTag.length);
// 6. 返回结果
EncryptedData result = new EncryptedData();
result.encrypted_data = Base64.getEncoder().encodeToString(encryptedData);
result.iv = Base64.getEncoder().encodeToString(iv);
result.auth_tag = Base64.getEncoder().encodeToString(authTag);
return result;
}
public static String decrypt(EncryptedData encryptedPackage, String appKey, String appSecret, long timestamp)
throws Exception {
// 1. 解码数据
byte[] encryptedData = Base64.getDecoder().decode(encryptedPackage.encrypted_data);
byte[] iv = Base64.getDecoder().decode(encryptedPackage.iv);
byte[] authTag = Base64.getDecoder().decode(encryptedPackage.auth_tag);
// 2. 派生密钥
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
String salt = appKey + timestamp;
KeySpec spec = new PBEKeySpec(appSecret.toCharArray(), salt.getBytes(), PBKDF2_ITERATIONS, KEY_LENGTH * 8);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
// 3. 组合数据
byte[] ciphertext = new byte[encryptedData.length + authTag.length];
System.arraycopy(encryptedData, 0, ciphertext, 0, encryptedData.length);
System.arraycopy(authTag, 0, ciphertext, encryptedData.length, authTag.length);
// 4. 解密数据
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmSpec);
byte[] aad = (appKey + timestamp).getBytes();
cipher.updateAAD(aad);
byte[] plaintext = cipher.doFinal(ciphertext);
return new String(plaintext, "UTF-8");
}
}
加密应用场景
敏感字段加密
字段类型 |
加密要求 |
示例 |
说明 |
身份证号 |
必须加密 |
"id_card": "加密后字符串" |
个人敏感信息 |
银行卡号 |
必须加密 |
"bank_card": "加密后字符串" |
金融敏感信息 |
手机号 |
建议加密 |
"mobile": "加密后字符串" |
个人联系方式 |
地址信息 |
可选加密 |
"address": "加密后字符串" |
个人位置信息 |
错误处理
加密异常
错误码 |
错误描述 |
可能原因 |
解决方案 |
5001 |
加密数据格式错误 |
JSON格式不正确 |
检查JSON格式 |
5002 |
密钥派生失败 |
app_secret错误或缺失 |
检查密钥配置 |
5003 |
解密失败 |
数据被篡改或密钥错误 |
重新获取数据 |
5004 |
认证标签无效 |
数据完整性验证失败 |
检查传输过程 |
性能优化
加密性能对比
数据大小 |
加密时间 |
解密时间 |
CPU占用 |
1KB |
~1ms |
~1ms |
<1% |
10KB |
~2ms |
~2ms |
<2% |
100KB |
~10ms |
~10ms |
<5% |
1MB |
~100ms |
~100ms |
<20% |
安全建议
🔐 加密最佳实践
- 密钥管理:使用专门的密钥管理服务,避免硬编码
- 定期轮换:建议每90天更换一次加密密钥
- 最小权限:仅对必要的敏感字段进行加密
- 传输安全:加密后的数据仍需通过HTTPS传输
- 审计日志:记录加密操作,便于安全审计
- 异常处理:加密失败时提供清晰的错误信息