Search code examples
pythonaescryptojspycryptocbc-mode

Decrypt AES CBC in python


I have the following code in JS

var CryptoJS = require("crypto-js");

function DecodePara(encrypted, key, iv) {
    var key = CryptoJS.SHA256(key);
    var iv = CryptoJS.SHA256(iv);
    var decrypted = CryptoJS.AES.decrypt(encrypted, key, {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.ZeroPadding
    });
    return decrypted.toString(CryptoJS.enc.Utf8);
}

var encrypted = 'bCjzbWfj7KqtJjCvYpq7Pg=='
var key = 'SNhPDFVEQVWZjsHoyDBXfuWC'
var iv = key.substr(5, 10)

console.log(DecodePara(encrypted, key, iv))

Output = '10203050'

the return is as expected, I would like to get the same result through python, I have already performed some tests based on some research but without success.)

I have tried variations of the following code

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import hashlib

code = 'bCjzbWfj7KqtJjCvYpq7Pg=='

key = 'SNhPDFVEQVWZjsHoyDBXfuWC'
iv =  key[5:15]

key = hashlib.sha256(key.encode('utf-8')).hexdigest()
iv = hashlib.sha256(iv.encode('utf-8')).hexdigest()

def decrypt(encrypted,key,iv):   
  cipher = AES.new(key, AES.MODE_CBC, iv)
  decryptedtext = unpad(cipher.decrypt(encrypted), AES.block_size)
  decryptedtextP = decryptedtext.decode("UTF-8")
  return decryptedtextP

decrypted = decrypt(code,key,iv)
print(decrypted)
raise ValueError("Incorrect AES key length (%d bytes)" % len(key)) ValueError: Incorrect AES key length (64 bytes)

Solution

  • Consider the following in the Python code:

    • key and IV must not be hex encoded, i.e. hexdigest() must be replaced by digest().
    • the IV has to be truncated to 16 bytes (implicitly done in the CryptoJS code).
    • the ciphertext has to be Base64 decoded Base64 (implicitly done in the CryptoJS code).
    • remove the Zero padding manually, e.g. with rstrip(b'\0'), because the padding module of PyCryptodome does not support Zero padding.

    The fixed code is:

    from Crypto.Cipher import AES
    import hashlib
    import base64
    
    code = 'bCjzbWfj7KqtJjCvYpq7Pg=='
    
    key = 'SNhPDFVEQVWZjsHoyDBXfuWC'
    iv =  key[5:15]
    
    key = hashlib.sha256(key.encode('utf-8')).digest()              # replace hexdigest() by digest
    iv = hashlib.sha256(iv.encode('utf-8')).digest()[0:16]          # replace hexdigest() by digest; truncate the IV to 16 bytes
    
    def decrypt(encrypted,key,iv):   
      cipher = AES.new(key, AES.MODE_CBC, iv)
      decryptedtext = cipher.decrypt(base64.b64decode(encrypted))   # Base64 decode the ciphertext
      decryptedtextP = decryptedtext.rstrip(b'\0').decode("UTF-8")  # remove the Zero padding
      return decryptedtextP
    
    decrypted = decrypt(code,key,iv)
    print(decrypted) # 10203050
    

    Keep in mind:

    • it's insecure to derive the IV from the key, since this results in reuse of key/IV pairs if the key is fixed. Instead, a random IV should be used for each encryption.
    • Zero padding is unreliable. Instead PKCS#7 padding should be used (which is also supported by PyCryptodome's padding module).
    • it's a vulnerability to use a digest as key derivation function. Instead, a dedicated key derivation function such as Argon2 or PBKDF2 should be applied.