javascriptdjango-rest-frameworkcryptojspycryptodome

Get IV from Django backend


I want to obtain IV from Python backend to Sveltekit and use it to check if the token is the same. I use CryptoJS and pycryptodome. Key is the same.

Python:

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)

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();
  }

My API responds with e.g.:

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

But the outcome returns two different values

data: fA6ifpnK8NCLBRi7EYPf4zyDumGFeBwVnZC0vliN06I= (from JS)

token: PAwNhCecq4lJRKizJWCMhknk9gleAPRXV2owj29XD2k= (actual token)


Solution

  • What you are doing is unnecessarily complicated and a vulnerability (since your IV only uses 4 random bytes).

    First, the IV for AES is 16 bytes in size: os.urandom(16).
    For a cross-platform transfer, the IV is most conveniently converted into a string using a binary-to-text encoding such as Base64 (either alone or concatenated with the ciphertext).
    CryptoJS provides a Base64 encoder for importing Base64 encoded data.


    Sample implementation for Python:

    import os
    import base64
    from Crypto.Cipher import AES
    from Crypto.Util.Padding import pad
    
    key =  b'pR@QvK3oM&9t#S8X' #bytes(os.environ['KEY'], "utf-8")
    iv = None
    def generate_iv():
        return os.urandom(16)
    
    def encrypt(username, password):
        global iv
        if iv == 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)
        print("IV         (Base64 encoded): " + base64.b64encode(iv).decode('utf-8'))
        print("Ciphertext (Base64 encoded): " + base64.b64encode(encrypted_data).decode('utf-8'))
        return base64.b64encode(encrypted_data).decode('utf-8')
    
    encrypt('username', 'password')
    

    possible output:

    IV         (Base64 encoded): R22EiVAgUqIo90WfLx2ppQ==
    Ciphertext (Base64 encoded): Q5+74TQ7q3y51egBSVVebt6bty22mDolmIjlVldUR1Q=
    

    Sample implementation for JavaScript:

    let iv = CryptoJS.enc.Base64.parse("R22EiVAgUqIo90WfLx2ppQ==");
    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();
    }
    
    encrypt('username', 'password');
    

    with the output

    Q5+74TQ7q3y51egBSVVebt6bty22mDolmIjlVldUR1Q=
    

    in accordance with the output from the Python code.