You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

解决/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库实现对应解密逻辑:

  1. 安装依赖:
pip install pycryptodome
  1. 修改后端解密代码:
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加密:

  1. 安装前端依赖:
npm install fernet
  1. 修改前端加密代码:
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);
      });
};
  1. 后端修正密钥:
# 去掉b''前缀,确保密钥是32字节URL安全Base64编码
encryption_secret_key = "VFRkU0hHSEZORFN2dWx1b3VNT213SVE4OTJkLWFiYU1CQU5TdjVGWjhiST0"
# 若当前密钥无效,可通过Fernet.generate_key()生成合规密钥

额外注意事项

  • 生产环境中,密钥不要硬编码,应通过环境变量管理(如Django用os.getenv("ENCRYPTION_KEY"))
  • 加密传输必须配合HTTPS,防止密钥或加密数据被窃取
  • 后端需确保generate_random_string和UserSerializer已正确定义

内容的提问来源于stack exchange,提问作者Olasubomi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 20:51:00