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

从Django后端获取IV后,SvelteKit与Python加密结果不一致

问题:Django后端与SvelteKit前端AES加密结果不一致

从Django(Python)后端获取IV到SvelteKit前端,使用PyCryptodome和CryptoJS实现AES-CBC加密,密钥一致,但两端加密结果完全不同。

后端Python(Django)代码

key =  bytes(os.environ['KEY'], "utf-8")
iv = None
def generate_iv():
    rand = os.urandom(4)
    return struct.unpack('B' * len(rand), rand)

    
def encrypt(username, password):
    global iv
    if iv == None:
        iv = generate_iv()
    byte_data = b''.join(struct.pack('<I', index) for index in iv)
    cipher = AES.new(key, AES.MODE_CBC, iv=byte_data)
    data = f"{username}:{password}".encode()
    padded_data = pad(data, AES.block_size, style='pkcs7')
    encrypted_data = cipher.encrypt(padded_data)
    print(base64.b64encode(encrypted_data).decode('utf-8'))
    return base64.b64encode(encrypted_data).decode('utf-8')

class IV(APIView):
    def get(self, request):
        global iv
        if iv == None:
            iv = generate_iv()
        return Response({'iv': f'{iv}'}, status=status.HTTP_200_OK)

前端SvelteKit(JS)代码

let iv;
let numbers = [];
onMount(async () => {
  const response = await fetch("http://127.0.0.1:8000/api/authapi/iv");
  const data = await response.json();
  const ivNumbers = data.iv.slice(1, -1).split(",").map(Number);
  numbers = ivNumbers;
  console.log(numbers);
  iv = CryptoJS.lib.WordArray.create(new Uint8Array(numbers));
  console.log(iv);
});

function encrypt(username, password) {
  const key = "pR@QvK3oM&9t#S8X";
  const data = `${username}:${password}`;
  const encryptedData = CryptoJS.AES.encrypt(
    data,
    CryptoJS.enc.Utf8.parse(key),
    {
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7,
      iv: iv,
    }
  );
  console.log(encryptedData.toString());
  return encryptedData.toString();
}

API响应示例

{
    "iv": "(246, 72, 5, 222)"
}

两端加密结果

  • 前端(CryptoJS):fA6ifpnK8NCLBRi7EYPf4zyDumGFeBwVnZC0vliN06I=
  • 后端(PyCryptodome):PAwNhCecq4lJRKizJWCMhknk9gleAPRXV2owj29XD2k=

问题根源

核心问题是前后端IV的处理逻辑完全不一致,导致实际使用的IV参数不同:

  1. 后端错误生成IV:
    • generate_iv生成4字节随机数,转成4个0-255的整数元组。
    • 但encrypt函数中用struct.pack('<I', index)将每个整数单独打包成4字节小端整数,最终IV被转换成16字节(4个整数×4字节),这是错误的转换逻辑。
  2. 前端解析IV错误:
    • 前端将后端返回的4个整数直接转成4字节的WordArray,实际使用的IV仅4字节,与后端的16字节IV完全不匹配。
  3. 额外问题:AES-CBC要求IV长度必须等于块大小(16字节),后端的错误转换碰巧凑够了16字节,但前端的IV长度不符合规范,进一步加剧了结果差异。

修正方案

1. 后端修正:生成标准16字节IV,用Base64传递

直接生成符合AES-CBC要求的16字节IV,并用Base64编码后返回,避免解析错误:

import os
import base64
from Cryptodome.Cipher import AES
from Cryptodome.Util.Padding import pad
from rest_framework.views import APIView
from rest_framework import status

key = bytes(os.environ['KEY'], "utf-8")
iv = None

def generate_iv():
    # 生成AES-CBC要求的16字节随机IV
    return os.urandom(16)

def encrypt(username, password):
    global iv
    if iv is None:
        iv = generate_iv()
    cipher = AES.new(key, AES.MODE_CBC, iv=iv)
    data = f"{username}:{password}".encode()
    padded_data = pad(data, AES.block_size, style='pkcs7')
    encrypted_data = cipher.encrypt(padded_data)
    encrypted_b64 = base64.b64encode(encrypted_data).decode('utf-8')
    print(encrypted_b64)
    return encrypted_b64

class IV(APIView):
    def get(self, request):
        global iv
        if iv is None:
            iv = generate_iv()
        # 将IV转成Base64字符串返回,避免手动解析数字的麻烦
        return Response({'iv': base64.b64encode(iv).decode('utf-8')}, status=status.HTTP_200_OK)

2. 前端修正:直接解析Base64格式的IV

前端无需手动解析数字,直接用CryptoJS解码后端返回的Base64 IV:

let iv;
onMount(async () => {
  const response = await fetch("http://127.0.0.1:8000/api/authapi/iv");
  const data = await response.json();
  // 直接解码Base64格式的IV
  iv = CryptoJS.enc.Base64.parse(data.iv);
  console.log(iv);
});

function encrypt(username, password) {
  const key = "pR@QvK3oM&9t#S8X"; // 确保与后端环境变量KEY完全一致
  const data = `${username}:${password}`;
  const encryptedData = CryptoJS.AES.encrypt(
    data,
    CryptoJS.enc.Utf8.parse(key),
    {
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7,
      iv: iv,
    }
  );
  console.log(encryptedData.toString());
  return encryptedData.toString();
}

额外注意事项

  • 密钥长度校验:当前密钥pR@QvK3oM&9t#S8X是16字符,UTF-8编码后为16字节,符合AES-128要求,若修改密钥需确保长度为16/24/32字节(对应AES-128/192/256)。
  • 全局IV的安全风险:后端使用全局变量存储IV会导致多请求复用同一个IV,违反加密安全规范,建议每次加密生成新IV,并将IV与密文一起返回给前端。
  • 跨域配置:若前后端域名不同,需在Django中配置CORS,避免跨域请求被浏览器拦截。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 06:41:05