前端使用 RSA 4096 加密数据并发送给后端的完整实现

本文将详细介绍如何在前端使用 RSA 4096 位加密数据,并将加密后的数据发送给后端的完整实现方案。RSA 是一种非对称加密算法,4096 位密钥长度提供了更高的安全性,适合加密敏感数据。

一、前端加密实现

1. 安装必要的加密库

首先,我们需要在前端项目中安装jsencrypt库,这是一个专门用于 RSA 加密的 JavaScript 库:

npm install jsencrypt

或者通过 CDN 引入:

<script src=" https://cdn.jsdelivr.net/npm/jsencrypt@3.3.2/bin/jsencrypt.min.js "></script>

2. 创建加密工具类

创建一个专门的加密工具文件src/utils/crypto.js

import JSEncrypt from 'jsencrypt'

/**
 * RSA 4096加密函数
 * @param {string|object} data - 要加密的数据(字符串或对象)
 * @param {string} publicKey - RSA公钥
 * @returns {string} 加密后的Base64字符串
 */
export function encryptRSA(data, publicKey) {
  const encryptor = new JSEncrypt({ default_key_size: 4096 })
  encryptor.setPublicKey(publicKey)
  
  // 如果数据是对象,先转为JSON字符串
  const dataStr = typeof data === 'object' ? JSON.stringify(data) : data
  
  // 加密数据
  const encrypted = encryptor.encrypt(dataStr)
  
  if (!encrypted) {
    throw new Error('加密失败,请检查公钥是否正确')
  }
  
  return encrypted
}

/**
 * 生成RSA密钥对(4096位)
 * @returns {Promise<{publicKey: string, privateKey: string}>} 密钥对
 */
export async function generateKeyPair() {
  return new Promise((resolve) => {
    const encryptor = new JSEncrypt({ default_key_size: 4096 })
    encryptor.getKey(() => {
      resolve({
        publicKey: encryptor.getPublicKey(),
        privateKey: encryptor.getPrivateKey()
      })
    })
  })
}

3. 使用示例

在组件中使用加密功能发送数据:

import { encryptRSA } from '@/utils/crypto'

// 假设这是从后端获取的公钥
const RSA_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvZ1Y9L0dXzL8JwJ1e6QN
...
-----END PUBLIC KEY-----`

async function sendEncryptedData(data) {
  try {
    // 加密数据
    const encryptedData = encryptRSA(data, RSA_PUBLIC_KEY)
    
    // 发送加密数据到后端
    const response = await fetch('/api/secure-endpoint', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Encrypted': 'true' // 添加加密标识头
      },
      body: JSON.stringify({
        encryptedData: encryptedData
      })
    })
    
    if (!response.ok) {
      throw new Error(`HTTP错误: ${response.status}`)
    }
    
    return await response.json()
  } catch (error) {
    console.error('加密请求失败:', error)
    throw error
  }
}

// 使用示例
const sensitiveData = {
  username: 'testUser',
  password: 'secret123',
  creditCard: '4111111111111111'
}

sendEncryptedData(sensitiveData)
  .then(response => console.log('服务器响应:', response))
  .catch(error => console.error('请求错误:', error))

二、后端解密实现

1. Java后端解密示例

import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

public class RSA4096Decryptor {
    private static final String PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n" +
            "MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC9nVj0vR1fMvwn\n" +
            "...\n" +
            "-----END PRIVATE KEY-----";
    
    private static final String ALGORITHM = "RSA";
    
    /**
     * 解密RSA加密的数据
     * @param encryptedData Base64编码的加密数据
     * @return 解密后的原始字符串
     * @throws Exception 解密过程中的任何异常
     */
    public static String decrypt(String encryptedData) throws Exception {
        // 移除PEM格式的头部和尾部
        String privateKeyPEM = PRIVATE_KEY
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s", "");
        
        // 解码Base64编码的私钥
        byte[] encoded = Base64.getDecoder().decode(privateKeyPEM);
        
        // 创建私钥对象
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        
        // 初始化解密器
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        
        // 解密数据
        byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        
        return new String(decryptedBytes);
    }
}

2. Spring Boot控制器示例

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SecureDataController {
    
    @PostMapping("/api/secure-endpoint")
    public ResponseEntity<?> handleEncryptedData(@RequestBody EncryptedRequest request) {
        try {
            // 解密数据
            String decryptedData = RSA4096Decryptor.decrypt(request.getEncryptedData());
            
            // 将JSON字符串转换为对象
            ObjectMapper mapper = new ObjectMapper();
            SensitiveData data = mapper.readValue(decryptedData, SensitiveData.class);
            
            // 处理业务逻辑...
            
            return ResponseEntity.ok().body(Map.of(
                "status", "success",
                "message", "数据接收并解密成功"
            ));
        } catch (Exception e) {
            return ResponseEntity.badRequest().body(Map.of(
                "status", "error",
                "message", "解密失败: " + e.getMessage()
            ));
        }
    }
    
    static class EncryptedRequest {
        private String encryptedData;
        
        // getter和setter
        public String getEncryptedData() {
            return encryptedData;
        }
        
        public void setEncryptedData(String encryptedData) {
            this.encryptedData = encryptedData;
        }
    }
    
    static class SensitiveData {
        private String username;
        private String password;
        private String creditCard;
        
        // getter和setter
    }
}

三、密钥管理最佳实践

1. 密钥生成与存储

RSA 4096 密钥对可以通过以下方式生成:

# 使用OpenSSL生成PKCS#8格式的密钥对
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:4096
openssl rsa -pubout -in private_key.pem -out public_key.pem

安全建议

  • 私钥必须严格保密,只存储在服务器上

  • 公钥可以安全地分发给客户端

  • 考虑使用密钥管理服务 (KMS) 或硬件安全模块 (HSM) 存储私钥

  • 定期轮换密钥 (建议每 6-12 个月)

2. 前端公钥获取方案

不建议在前端代码中硬编码公钥,而是应该通过 API 动态获取:

async function getPublicKey() {
  const response = await fetch('/api/public-key')
  if (!response.ok) {
    throw new Error('获取公钥失败')
  }
  return await response.text()
}

// 使用示例
const publicKey = await getPublicKey()
const encryptedData = encryptRSA(sensitiveData, publicKey)

四、性能优化与注意事项

1. RSA加密限制

RSA 4096 加密有以下限制:

  • 加密数据大小有限制 (约 446 字节)

  • 加密 / 解密操作较慢,不适合大数据量

解决方案: 对于大数据量,建议使用混合加密方案:

  1. 前端生成随机 AES 密钥

  2. 使用 AES 加密实际数据

  3. 使用 RSA 加密 AES 密钥

  4. 将加密后的数据和加密后的密钥一起发送到后端

2. 混合加密示例

import { encryptRSA } from './crypto'
import CryptoJS from 'crypto-js'

async function sendHybridEncryptedData(data) {
  // 1. 生成随机AES密钥
  const aesKey = CryptoJS.lib.WordArray.random(32).toString()
  const aesIv = CryptoJS.lib.WordArray.random(16).toString()
  
  // 2. 使用AES加密数据
  const encryptedData = CryptoJS.AES.encrypt(
    JSON.stringify(data),
    CryptoJS.enc.Utf8.parse(aesKey),
    { iv: CryptoJS.enc.Utf8.parse(aesIv) }
  ).toString()
  
  // 3. 使用RSA加密AES密钥
  const encryptedKey = encryptRSA(aesKey, RSA_PUBLIC_KEY)
  const encryptedIv = encryptRSA(aesIv, RSA_PUBLIC_KEY)
  
  // 4. 发送加密数据
  const response = await fetch('/api/hybrid-encrypted', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Encrypted': 'hybrid'
    },
    body: JSON.stringify({
      encryptedData: encryptedData,
      encryptedKey: encryptedKey,
      encryptedIv: encryptedIv
    })
  })
  
  return await response.json()
}

3. 错误处理与日志

完善的错误处理对于加密系统至关重要:

// 增强的加密函数
export function encryptRSA(data, publicKey) {
  try {
    if (!data) throw new Error('加密数据不能为空')
    if (!publicKey) throw new Error('公钥不能为空')
    
    const encryptor = new JSEncrypt({ default_key_size: 4096 })
    encryptor.setPublicKey(publicKey)
    
    // 验证公钥格式
    if (!encryptor.getKey().verify()) {
      throw new Error('无效的公钥格式')
    }
    
    const dataStr = typeof data === 'object' ? JSON.stringify(data) : data
    const encrypted = encryptor.encrypt(dataStr)
    
    if (!encrypted) {
      throw new Error('加密失败,可能是数据太大或公钥不匹配')
    }
    
    return encrypted
  } catch (error) {
    console.error('RSA加密错误:', error)
    throw error // 重新抛出以便调用者处理
  }
}

五、安全最佳实践

  1. 始终使用 HTTPS:加密传输必须在 HTTPS 环境下进行,防止中间人攻击

  2. 验证数据完整性:考虑添加 HMAC 签名验证数据未被篡改

  3. 防止重放攻击:在加密数据中添加时间戳和随机数 (nonce)

  4. 输入验证:后端解密后仍需验证数据格式和内容

  5. 限制请求频率:防止暴力破解攻击

  6. 使用最新的加密库:定期更新加密库以修复已知漏洞

  7. 禁用旧版协议:确保服务器配置禁用不安全的 SSL/TLS 协议和加密套件

六、完整的前端加密组件示例

import JSEncrypt from 'jsencrypt'
import CryptoJS from 'crypto-js'

class SecureDataSender {
  constructor() {
    this.publicKey = null
    this.keyExpiry = null
    this.KEY_CACHE_TIME = 3600000 // 1小时缓存
  }
  
  async init() {
    await this.loadPublicKey()
  }
  
  async loadPublicKey() {
    // 检查缓存
    if (this.publicKey && this.keyExpiry > Date.now()) {
      return
    }
    
    try {
      const response = await fetch('/api/public-key')
      if (!response.ok) throw new Error(`HTTP ${response.status}`)
      
      this.publicKey = await response.text()
      this.keyExpiry = Date.now() + this.KEY_CACHE_TIME
    } catch (error) {
      console.error('加载公钥失败:', error)
      throw new Error('无法获取加密公钥')
    }
  }
  
  async encryptData(data) {
    await this.loadPublicKey()
    
    // 对大文件使用混合加密
    const dataSize = JSON.stringify(data).length
    if (dataSize > 400) { // 超过RSA推荐大小
      return this.hybridEncrypt(data)
    }
    
    // 小数据直接使用RSA
    return {
      encryptedData: encryptRSA(data, this.publicKey),
      encryptionType: 'rsa'
    }
  }
  
  async hybridEncrypt(data) {
    await this.loadPublicKey()
    
    // 生成随机AES密钥
    const aesKey = CryptoJS.lib.WordArray.random(32).toString()
    const aesIv = CryptoJS.lib.WordArray.random(16).toString()
    
    // AES加密数据
    const encryptedData = CryptoJS.AES.encrypt(
      JSON.stringify(data),
      CryptoJS.enc.Utf8.parse(aesKey),
      { iv: CryptoJS.enc.Utf8.parse(aesIv) }
    ).toString()
    
    // RSA加密AES密钥
    const encryptedKey = encryptRSA(aesKey, this.publicKey)
    const encryptedIv = encryptRSA(aesIv, this.publicKey)
    
    return {
      encryptedData,
      encryptedKey,
      encryptedIv,
      encryptionType: 'hybrid'
    }
  }
  
  async sendSecureData(url, data) {
    try {
      const encrypted = await this.encryptData(data)
      
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-Encryption-Type': encrypted.encryptionType
        },
        body: JSON.stringify(encrypted)
      })
      
      if (!response.ok) {
        throw new Error(`HTTP错误: ${response.status}`)
      }
      
      return await response.json()
    } catch (error) {
      console.error('安全请求失败:', error)
      throw error
    }
  }
}

// 工具函数
function encryptRSA(data, publicKey) {
  const encryptor = new JSEncrypt({ default_key_size: 4096 })
  encryptor.setPublicKey(publicKey)
  
  const dataStr = typeof data === 'object' ? JSON.stringify(data) : data
  const encrypted = encryptor.encrypt(dataStr)
  
  if (!encrypted) {
    throw new Error('RSA加密失败')
  }
  
  return encrypted
}

// 使用示例
const secureSender = new SecureDataSender()
await secureSender.init()

const sensitiveData = {
  userId: '12345',
  paymentInfo: {
    cardNumber: '4111111111111111',
    expiry: '12/25',
    cvv: '123'
  }
}

secureSender.sendSecureData('/api/payment', sensitiveData)
  .then(response => console.log('成功:', response))
  .catch(error => console.error('失败:', error))

七、总结

本文详细介绍了如何在前端使用 RSA 4096 加密数据并发送给后端的完整实现方案,包括:

  1. 前端使用jsencrypt库进行 RSA 加密

  2. Java 后端的解密实现

  3. 密钥管理的最佳实践

  4. 性能优化和混合加密方案

  5. 安全最佳实践和错误处理

  6. 完整的可重用前端组件

对于特别敏感的数据传输,RSA 4096 提供了强大的安全保障,但需要注意其性能限制和数据大小限制。在实际应用中,应根据具体需求选择合适的加密策略,并始终遵循安全最佳实践。

通过实现这种端到端的加密方案,可以显著提高应用程序的数据安全性,保护用户敏感信息不被泄露。


前端使用 RSA 4096 加密数据并发送给后端的完整实现
https://uniomo.com/archives/qian-duan-shi-yong-rsa-4096-jia-mi-shu-ju-bing-fa-song-gei-hou-duan-de-wan-zheng-shi-xian
作者
雨落秋垣
发布于
2025年03月15日
许可协议