加密算法

概述

环能聚合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传输
  • 审计日志:记录加密操作,便于安全审计
  • 异常处理:加密失败时提供清晰的错误信息