解决/register/接口ValueError:Fernet密钥需为32位URL安全Base64字节
问题解决:前端AES加密后端Fernet解密不兼容及密钥错误
问题重现
前端使用CryptoJS AES加密表单数据:
const handleSubmit = (e) => { e.preventDefault(); const encryptedPassword = CryptoJS.AES.encrypt( formData.password, "b'VFRkU0hHSEZORFN2dWx1b3VNT213SVE4OTJkLWFiYU1CQU5TdjVGWjhiST0'" ).toString(); const encryptedPassword2 = CryptoJS.AES.encrypt( formData.password2, "b'VFRkU0hHSEZORFN2dWx1b3VNT213SVE4OTJkLWFiYU1CQU5TdjVGWjhiST0'" ).toString(); const encryptedEmail = CryptoJS.AES.encrypt( formData.email, "b'VFRkU0hHSEZORFN2dWx1b3VNT213SVE4OTJkLWFiYU1CQU5TdjVGWjhiST0'" ).toString(); const encryptedPayload = { ...formData, password: encryptedPassword, password2: encryptedPassword2, email: encryptedEmail, }; axios .post("http://127.0.0.1:8003/register/", encryptedPayload) .then((response) => { console.log(response.data); }) .catch((error) => { console.error(error); }); };
后端用Django REST Framework的APIView尝试用Fernet解密:
class CreateUser(APIView): permission_classes = [AllowAny] def post(self, request): encrypted_password = request.data.get("password") encrypted_password2 = request.data.get("password2") encrypted_email = request.data.get("email") encryption_secret_key = "b'VFRkU0hHSEZORFN2dWx1b3VNT213SVE4OTJkLWFiYU1CQU5TdjVGWjhiST0'" cipher_suite = Fernet(encryption_secret_key) decrypted_password = cipher_suite.decrypt( encrypted_password.encode()).decode() decrypted_password2 = cipher_suite.decrypt( encrypted_password2.encode()).decode() decrypted_email = cipher_suite.decrypt( encrypted_email.encode()).decode() request.data["password"] = decrypted_password request.data["pasword2"] = decrypted_password2 request.data["email"] = decrypted_email serializer = UserSerializer(data=request.data) if serializer.is_valid(): user = serializer.save() user.email = user.email.lower() user.username = user.email.lower() if user.email else None user.first_name = user.first_name.lower() if user.first_name else None user.last_name = user.last_name.lower() if user.last_name else None user.avatar = user.avatar if user.avatar else None if user.first_name and user.last_name: random_string = generate_random_string() slug = slugify( f"{user.first_name} {user.last_name} {random_string}") while User.objects.filter(slug=slug).exists(): random_string = generate_random_string() slug = slugify( f"{user.first_name} {user.last_name} {random_string}" ) user.slug = slug user.set_password(request.data["password"]) user.password2 = None user.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return JsonResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
提交后出现错误:
Exception Type: ValueError at /register/ Exception Value: Fernet key must be 32 url-safe base64-encoded bytes.
核心问题分析
- 前后端加密算法不匹配:前端用CryptoJS AES加密,后端用Fernet解密,两者的加密格式、密钥规则完全不同
- 后端密钥格式错误:密钥字符串带Python字节串的
b''前缀,Fernet要求的是32字节的URL安全Base64编码字节,当前字符串不符合要求 - 后端代码存在拼写错误:
request.data["pasword2"]应为request.data["password2"]
解决步骤
方案一:后端改用AES解密(匹配前端逻辑)
CryptoJS AES默认采用CBC模式、PKCS7填充,密钥会通过特定算法生成。后端使用pycryptodome库实现对应解密逻辑:
- 安装依赖:
pip install pycryptodome
- 修改后端解密代码:
from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import hashlib import base64 from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from django.http import JsonResponse from django.utils.text import slugify class CreateUser(APIView): permission_classes = [AllowAny] def post(self, request): encrypted_password = request.data.get("password") encrypted_password2 = request.data.get("password2") encrypted_email = request.data.get("email") # 去掉密钥的b''前缀,和前端保持一致 encryption_secret_key = "VFRkU0hHSEZORFN2dWx1b3VNT213SVE4OTJkLWFiYU1CQU5TdjVGWjhiST0" def decrypt_cryptojs_aes(encrypted_data, key): # 解析CryptoJS生成的Base64加密数据:salt(8字节) + iv(16字节) + ciphertext decoded_data = base64.b64decode(encrypted_data) salt = decoded_data[:8] iv = decoded_data[8:24] ciphertext = decoded_data[24:] # 用CryptoJS的密钥派生算法生成实际密钥 key_bytes = key.encode('utf-8') derived_key = hashlib.md5(key_bytes + salt).digest() for _ in range(1, 3): derived_key = hashlib.md5(derived_key + key_bytes + salt).digest() # 取前32字节作为AES-256密钥 derived_key = derived_key[:32] cipher = AES.new(derived_key, AES.MODE_CBC, iv) decrypted = unpad(cipher.decrypt(ciphertext), AES.block_size) return decrypted.decode('utf-8') try: decrypted_password = decrypt_cryptojs_aes(encrypted_password, encryption_secret_key) decrypted_password2 = decrypt_cryptojs_aes(encrypted_password2, encryption_secret_key) decrypted_email = decrypt_cryptojs_aes(encrypted_email, encryption_secret_key) except Exception as e: return JsonResponse({"error": "解密失败"}, status=status.HTTP_400_BAD_REQUEST) # 修复拼写错误 request.data["password"] = decrypted_password request.data["password2"] = decrypted_password2 request.data["email"] = decrypted_email # 后续序列化逻辑保持不变 serializer = UserSerializer(data=request.data) if serializer.is_valid(): user = serializer.save() user.email = user.email.lower() user.username = user.email.lower() if user.email else None user.first_name = user.first_name.lower() if user.first_name else None user.last_name = user.last_name.lower() if user.last_name else None user.avatar = user.avatar if user.avatar else None if user.first_name and user.last_name: random_string = generate_random_string() slug = slugify( f"{user.first_name} {user.last_name} {random_string}") while User.objects.filter(slug=slug).exists(): random_string = generate_random_string() slug = slugify( f"{user.first_name} {user.last_name} {random_string}" ) user.slug = slug user.set_password(request.data["password"]) user.password2 = None user.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return JsonResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
方案二:前端改用Fernet加密(匹配后端逻辑)
使用fernet.js库实现前端Fernet加密:
- 安装前端依赖:
npm install fernet
- 修改前端加密代码:
import Fernet from 'fernet'; const handleSubmit = (e) => { e.preventDefault(); // 去掉密钥的b''前缀,使用标准Fernet密钥格式 const secretKey = new Fernet.Secret('VFRkU0hHSEZORFN2dWx1b3VNT213SVE4OTJkLWFiYU1CQU5TdjVGWjhiST0'); const encrypt = (data) => { const token = new Fernet.Token({secret: secretKey}); token.encode(data); return token.toString(); }; const encryptedPassword = encrypt(formData.password); const encryptedPassword2 = encrypt(formData.password2); const encryptedEmail = encrypt(formData.email); const encryptedPayload = { ...formData, password: encryptedPassword, password2: encryptedPassword2, email: encryptedEmail, }; axios .post("http://127.0.0.1:8003/register/", encryptedPayload) .then((response) => { console.log(response.data); }) .catch((error) => { console.error(error); }); };
- 后端修正密钥:
# 去掉b''前缀,确保密钥是32字节URL安全Base64编码 encryption_secret_key = "VFRkU0hHSEZORFN2dWx1b3VNT213SVE4OTJkLWFiYU1CQU5TdjVGWjhiST0" # 若当前密钥无效,可通过Fernet.generate_key()生成合规密钥
额外注意事项
- 生产环境中,密钥不要硬编码,应通过环境变量管理(如Django用
os.getenv("ENCRYPTION_KEY")) - 加密传输必须配合HTTPS,防止密钥或加密数据被窃取
- 后端需确保
generate_random_string和UserSerializer已正确定义
内容的提问来源于stack exchange,提问作者Olasubomi
相关产品推荐
相关产品推荐

