如何实现Django Rest Framework API加密响应并在React前端解密?
可行的API数据加密/解密方案
针对你前后端分离的架构(React+Axios前端,Django+DRF后端),以下是几种成熟的API数据加密实现方案,涵盖对称加密、非对称混合加密和端到端加密场景:
1. 对称加密(AES-GCM/Fernet)
这是最常用的方案,性能高、实现简单,适合大多数场景。核心是前后端共享一个加密密钥,后端用密钥加密响应数据,前端用同一密钥解密。
后端(Django+DRF)实现
- 使用
cryptography库提供的Fernet(封装了AES-GCM,自动处理密钥管理和签名)或直接用AES-GCM:- 安装依赖:
pip install cryptography - 编写自定义DRF渲染器,替换默认的JSONRenderer:
from rest_framework.renderers import BaseRenderer from cryptography.fernet import Fernet import json # 密钥建议从环境变量读取,不要硬编码 ENCRYPTION_KEY = b"your_fernet_key_here" # 可通过Fernet.generate_key()生成 fernet = Fernet(ENCRYPTION_KEY) class EncryptedJSONRenderer(BaseRenderer): media_type = 'application/json' format = 'json' def render(self, data, accepted_media_type=None, renderer_context=None): # 将数据序列化为JSON字符串,再加密 json_data = json.dumps(data).encode('utf-8') encrypted_data = fernet.encrypt(json_data) # 返回加密后的base64字符串 return json.dumps({"encrypted_data": encrypted_data.decode('utf-8')}).encode('utf-8')- 在DRF配置中全局启用或为单个视图设置该渲染器:
REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': [ 'path.to.EncryptedJSONRenderer', ] } - 安装依赖:
前端(React+Axios)实现
- 使用
crypto-js或浏览器原生的Web Crypto API解密:- 安装依赖:
npm install crypto-js(或直接用Web Crypto) - 编写Axios响应拦截器,自动解密数据:
import axios from 'axios'; import CryptoJS from 'crypto-js'; // 密钥要和后端一致,建议从安全渠道获取(如登录接口返回,而非硬编码) const ENCRYPTION_KEY = 'your_fernet_key_here'; const api = axios.create({ baseURL: 'https://api.mydomain.com', }); // 响应拦截器:解密数据 api.interceptors.response.use( (response) => { if (response.data.encrypted_data) { // Fernet解密需要处理格式,这里以CryptoJS适配为例(或直接用fernet.js库) const decryptedBytes = CryptoJS.AES.decrypt(response.data.encrypted_data, ENCRYPTION_KEY); response.data = JSON.parse(decryptedBytes.toString(CryptoJS.enc.Utf8)); } return response; }, (error) => Promise.reject(error) ); export default api; - 安装依赖:
注意事项
- 密钥绝对不能硬编码在前端代码中,建议用户登录后通过HTTPS接口返回临时会话密钥,定期轮换。
- 必须启用HTTPS,防止密钥或加密数据被中间人窃取。
2. 非对称加密+对称加密混合方案
解决对称加密密钥共享的安全风险,通过RSA交换临时AES密钥,再用AES加密数据(兼顾安全和性能)。
流程说明
- 前端生成RSA密钥对(公钥+私钥),私钥存在前端内存中(不存储),公钥传给后端。
- 后端生成临时AES密钥,用前端公钥加密AES密钥,同时用AES密钥加密响应数据。
- 后端返回
加密的AES密钥+加密的数据。 - 前端用私钥解密AES密钥,再用AES密钥解密数据。
后端(Django)实现
from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives import serialization, hashes from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes import os, json def encrypt_aes_key_with_rsa(public_key_pem, aes_key): # 加载前端公钥 public_key = serialization.load_pem_public_key(public_key_pem.encode('utf-8')) # 加密AES密钥 encrypted_aes_key = public_key.encrypt( aes_key, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None ) ) return encrypted_aes_key def encrypt_data_with_aes(data, aes_key): # AES-GCM加密数据 iv = os.urandom(12) cipher = Cipher(algorithms.AES(aes_key), modes.GCM(iv)) encryptor = cipher.encryptor() json_data = json.dumps(data).encode('utf-8') ciphertext = encryptor.update(json_data) + encryptor.finalize() return iv, ciphertext, encryptor.tag # 在视图中使用:接收前端公钥,返回加密的AES密钥和加密数据 def some_api_view(request): public_key_pem = request.data.get('public_key') if not public_key_pem: return Response({'error': 'Public key required'}, status=400) # 生成临时AES密钥 aes_key = os.urandom(32) # 加密数据 iv, ciphertext, tag = encrypt_data_with_aes({'message': 'sensitive data'}, aes_key) # 加密AES密钥 encrypted_aes_key = encrypt_aes_key_with_rsa(public_key_pem, aes_key) return Response({ 'encrypted_aes_key': encrypted_aes_key.hex(), 'iv': iv.hex(), 'ciphertext': ciphertext.hex(), 'tag': tag.hex() })
前端(React)实现
使用Web Crypto API处理RSA和AES操作:
// 生成RSA密钥对 async function generateRSAKeyPair() { return window.crypto.subtle.generateKey( { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: "SHA-256", }, true, ["encrypt", "decrypt"] ); } // 导出公钥为PEM格式 async function exportPublicKey(key) { const exported = await window.crypto.subtle.exportKey("spki", key); return btoa(String.fromCharCode(...new Uint8Array(exported))); } // 解密AES密钥 async function decryptAESKey(privateKey, encryptedAesKeyHex) { const encryptedAesKey = new Uint8Array(Buffer.from(encryptedAesKeyHex, 'hex')); return window.crypto.subtle.decrypt( { name: "RSA-OAEP" }, privateKey, encryptedAesKey ); } // AES-GCM解密数据 async function decryptData(aesKey, ivHex, ciphertextHex, tagHex) { const iv = new Uint8Array(Buffer.from(ivHex, 'hex')); const ciphertext = new Uint8Array(Buffer.from(ciphertextHex, 'hex')); const tag = new Uint8Array(Buffer.from(tagHex, 'hex')); const cipher = await window.crypto.subtle.importKey( "raw", aesKey, { name: "AES-GCM" }, false, ["decrypt"] ); const decrypted = await window.crypto.subtle.decrypt( { name: "AES-GCM", iv: iv, tag: tag }, cipher, ciphertext ); return JSON.parse(new TextDecoder().decode(decrypted)); } // 发送请求示例 async function fetchEncryptedData() { const keyPair = await generateRSAKeyPair(); const publicKeyPem = await exportPublicKey(keyPair.publicKey); const response = await axios.post('/api/data', { public_key: publicKeyPem }); const { encrypted_aes_key, iv, ciphertext, tag } = response.data; const aesKey = await decryptAESKey(keyPair.privateKey, encrypted_aes_key); const decryptedData = await decryptData(aesKey, iv, ciphertext, tag); console.log(decryptedData); }
优点
- 无需长期共享密钥,临时AES密钥每次请求可轮换,安全性更高。
- 兼顾非对称加密的安全性和对称加密的性能。
3. 端到端加密(E2EE)
适用于高度敏感的数据场景,后端完全无法解密数据,只有前端持有解密密钥(比如基于用户密码派生密钥)。
实现思路
- 用户登录时,前端用用户密码通过PBKDF2或Argon2派生加密密钥。
- 前端将密钥的公钥(或派生的对称密钥标识)传给后端,后端存储该标识。
- 后端用该公钥加密数据,返回给前端;前端用派生的密钥解密。
技术栈
- 后端:使用
libsodium的Python绑定pysodium处理加密。 - 前端:使用
libsodium-wrappers库,简化加密操作。
注意事项
- 后端无法恢复明文数据,必须由用户自己保管密钥(如密码),丢失则无法恢复数据。
- 需处理密钥轮换、设备同步等复杂场景。
通用安全建议
- 所有通信必须使用HTTPS,防止中间人攻击窃取加密数据或密钥。
- 前端避免将密钥存储在localStorage/sessionStorage,建议存在内存中,页面刷新后重新生成或从登录流程获取。
- 对前端解密后的明文数据做XSS防护,避免数据泄露。
- 定期轮换密钥,尤其是对称加密的长期密钥。
内容的提问来源于stack exchange,提问作者Muhammad Talha Abbas
相关产品推荐
相关产品推荐

