客户端与服务端数据加密方案及实现

在前后端交互中,以下业务场景通常需要进行数据加密:

需要加密的典型业务场景

  1. 用户认证信息:登录用户名、密码、短信验证码
  2. 个人敏感信息:身份证号、银行卡号、手机号
  3. 财务数据:交易金额、账户余额、支付信息
  4. 隐私内容:私信、医疗记录、位置信息
  5. 业务敏感数据:合同内容、商业机密、专利信息

推荐加密方案

方案1:HTTPS + 敏感字段单独加密(推荐)

  • 所有通信走HTTPS
  • 对特别敏感的数据在应用层额外加密

方案2:HTTPS + 全报文加密(高安全需求)

  • 所有通信走HTTPS
  • 整个请求/响应体进行加密

完整实现示例

前端实现 (Vue3/JavaScript)

安装加密库
npm install crypto-js
加密工具类 (utils/crypto.js)
import CryptoJS from 'crypto-js'

// AES加密 (CBC模式)
export function encryptAES(data, key, iv) {
  const encrypted = CryptoJS.AES.encrypt(
    JSON.stringify(data),
    CryptoJS.enc.Utf8.parse(key),
    {
      iv: CryptoJS.enc.Utf8.parse(iv),
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
    }
  )
  return encrypted.toString()
}

// AES解密
export function decryptAES(ciphertext, key, iv) {
  const decrypted = CryptoJS.AES.decrypt(
    ciphertext,
    CryptoJS.enc.Utf8.parse(key),
    {
      iv: CryptoJS.enc.Utf8.parse(iv),
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
    }
  )
  return JSON.parse(decrypted.toString(CryptoJS.enc.Utf8))
}

// 生成随机密钥和IV (前端生成,通过RSA公钥加密传输给后端)
export function generateRandomKey() {
  const key = CryptoJS.lib.WordArray.random(32).toString()
  const iv = CryptoJS.lib.WordArray.random(16).toString()
  return { key, iv }
}
API请求封装示例
import { encryptAES, generateRandomKey } from '@/utils/crypto'

// 假设这是从后端获取的RSA公钥
const RSA_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx6qHfR6pJZQ...`
  
// 加密请求数据
async function encryptRequestData(data) {
  // 生成临时AES密钥
  const { key, iv } = generateRandomKey()
  
  // 用AES加密实际数据
  const encryptedData = encryptAES(data, key, iv)
  
  // 用RSA加密AES密钥
  const encryptedKey = await window.crypto.subtle.encrypt(
    { name: "RSA-OAEP" },
    await importRsaKey(RSA_PUBLIC_KEY),
    new TextEncoder().encode(key)
  )
  
  return {
    data: encryptedData,
    key: arrayBufferToBase64(encryptedKey),
    iv: iv
  }
}

// 发送加密请求
async function sendEncryptedRequest(url, payload) {
  const encrypted = await encryptRequestData(payload)
  return axios.post(url, encrypted)
}

// 示例:登录请求
async function login(username, password) {
  const response = await sendEncryptedRequest('/api/login', {
    username,
    password
  })
  return response.data
}

后端实现 (Java Spring Boot)

添加依赖 (pom.xml)
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.70</version>
</dependency>
加密工具类 (AesUtils.java)
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class AesUtils {
    
    private static final String AES_ALGORITHM = "AES/CBC/PKCS5Padding";
    
    public static String encrypt(String data, String key, String iv) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
        
        Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
        
        byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(encryptedData);
    }
    
    public static String decrypt(String encryptedData, String key, String iv) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
        
        Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
        
        byte[] decodedData = Base64.getDecoder().decode(encryptedData);
        byte[] decryptedData = cipher.doFinal(decodedData);
        return new String(decryptedData, StandardCharsets.UTF_8);
    }
}
RSA工具类 (RsaUtils.java)
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

public class RsaUtils {
    
    private static final String RSA_ALGORITHM = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
    
    // 解密AES密钥
    public static String decryptAesKey(String encryptedKey, String privateKeyStr) throws Exception {
        byte[] encryptedBytes = Base64.getDecoder().decode(encryptedKey);
        
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyStr));
        PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec);
        
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }
}
控制器示例 (LoginController.java)
@RestController
@RequestMapping("/api")
public class LoginController {
    
    @Value("${rsa.private-key}")
    private String rsaPrivateKey;
    
    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody EncryptedRequest request) {
        try {
            // 1. 用RSA私钥解密AES密钥
            String aesKey = RsaUtils.decryptAesKey(request.getKey(), rsaPrivateKey);
            
            // 2. 用AES密钥解密数据
            String decryptedData = AesUtils.decrypt(request.getData(), aesKey, request.getIv());
            
            // 3. 处理业务逻辑
            LoginDTO loginDTO = objectMapper.readValue(decryptedData, LoginDTO.class);
            // ... 验证用户名密码等业务逻辑
            
            // 4. 加密响应数据
            String responseData = AesUtils.encrypt(
                objectMapper.writeValueAsString(response),
                aesKey,
                request.getIv()
            );
            
            return ResponseEntity.ok(new EncryptedResponse(responseData));
            
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
    
    // 请求体
    public static class EncryptedRequest {
        private String data;  // AES加密的业务数据
        private String key;    // RSA加密的AES密钥
        private String iv;     // AES IV
        
        // getters & setters
    }
    
    // 响应体
    public static class EncryptedResponse {
        private String data;  // AES加密的响应数据
        
        // constructor & getter
    }
}

密钥管理最佳实践

  1. 前端

    • 每次会话生成新的AES密钥
    • 使用RSA公钥加密传输AES密钥
    • 不要在前端存储长期有效的密钥
  2. 后端

    • 使用安全的密钥管理系统存储RSA私钥
    • 定期轮换RSA密钥对
    • 对不同业务使用不同的密钥
  3. 传输层

    • 必须使用HTTPS
    • 启用HSTS防止SSL剥离攻击

以下是加密解密流程及密钥来源说明:

客户端服务端密钥准备阶段生成随机AES密钥(key)和IV(iv)获取服务端RSA公钥(预置或动态获取)加密阶段用AES加密业务数据(key+iv)用RSA公钥加密AES密钥发送加密数据包:{data: AES加密的业务数据, key: RSA加密的AES密钥, iv: AES的IV向量}解密阶段用RSA私钥解密AES密钥用AES密钥+IV解密业务数据业务处理处理明文业务数据响应加密使用同一AES密钥加密响应数据返回加密响应:{data: AES加密的响应}响应解密用本地AES密钥解密响应数据客户端服务端

密钥来源说明(表格版)

组件密钥类型来源说明存储位置
RSA公钥非对称公钥1. 服务端生成密钥对
2. 公钥预置在客户端或首次连接时动态获取
客户端配置文件/内存
RSA私钥非对称私钥服务端生成后存储在安全位置(HSM/KMS/配置文件)服务端安全存储
AES密钥对称密钥1. 客户端每次会话随机生成
2. 通过RSA加密传输给服务端
会话期间内存存储
IV向量初始化向量客户端随机生成,随请求一起传输不存储,每次请求重新生成

关键流程图示

  1. 密钥交换流程

    [客户端]                   [服务端]
      |-- RSA公钥 (预置) ------>|
      |                         |
      |-- RSA加密的AES密钥 ---->|
      |                         |-- RSA私钥解密获得AES密钥
    
  2. 数据加密流程

    [客户端业务数据] 
      --> AES加密(key+iv) 
      --> 密文数据
      --> 通过HTTPS传输
      --> 服务端AES解密
      --> 明文业务数据
    
  3. 密钥生命周期

    AES密钥生成 -> RSA加密传输 -> 内存暂存 -> 会话结束销毁
    (客户端)       (HTTPS通道)    (服务端)     (自动清除)
    

这种设计结合了:

  • RSA的非对称加密安全性(用于密钥交换)
  • AES的对称加密高效性(用于数据加密)
  • 每次会话独立的密钥(前向安全性)
  • HTTPS的传输层保护(防窃听)

性能考虑

  1. 对于性能敏感场景,可以:

    • 会话期间缓存AES密钥
    • 对非敏感数据不加密或使用更轻量级的加密方式
    • 考虑使用ChaCha20-Poly1305替代AES-GCM(在移动设备上性能更好)
  2. 对于高安全需求场景:

    • 实现完整的端到端加密
    • 考虑使用专业的加密库如Google Tink
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值