Search code examples
javascriptpythoncryptographycryptojs

CryptoJS decrypt AES (CBC) with salt to Python Crypto


I have this code on javascript. I want to rewrite this code on python but when i try to use salt with key i get wrong decrypted string, what i do wrong (how to use salt with key as it use in CryptoJS)? (I can't change type of encryption, but i need to decrypt existing string)

Code in js


// y is array with all data
var decryptjson = {"ct": y[0].substr(2), "iv": y[1], "s": y[2]}
var cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Base64.parse(decryptjson.ct)
});
if (decryptjson.iv) cipherParams.iv = CryptoJS.enc.Hex.parse(decryptjson.iv);
if (decryptjson.s) cipherParams.salt = CryptoJS.enc.Hex.parse(decryptjson.s);
var a = CryptoJS.AES.decrypt(cipherParams, key).toString(CryptoJS.enc.Utf8)

Code in python


decryptdict = {"ct":y[0], "iv": y[1], "s": y[2]}
ct = b64decode(decryptdict["ct"])
iv = bytes.fromhex(decryptdict["iv"])
salt = bytes.fromhex(decryptdict["s"])
key = pbkdf2.PBKDF2(key, salt).read(32)
cipher = AES.new(key, AES.MODE_CBC, iv)
decryptedstring = cipher.decrypt(ct)

Solution

  • The posted codes use different key derivation functions: The JavaScript code implicitly applies EVP_BytesToKey(), the Python code explicitly uses PBKDF2.

    Note that the IV specified in the JavaScript object is ignored. Instead, the IV determined via key derivation is used. This can be easily proven by using a random IV in the JavaScript object which has no effect on the decryption showing that the IV is ignored!

    var key = 'my password' // actually not a key but a password
    var decryptjson = {
      ct: 'CoOMBKETmRnGvir8p7JyRsWhOm9VCtV08POy6TmFgkbOoT00tk3UXKa0QGrtGoI5', 
      iv: CryptoJS.lib.WordArray.random(16).toString(), // is ignored
      s: 'ce9448fbb051ea03'
    }
    var cipherParams = CryptoJS.lib.CipherParams.create({ciphertext: CryptoJS.enc.Base64.parse(decryptjson.ct)});
    if (decryptjson.iv) cipherParams.iv = CryptoJS.enc.Hex.parse(decryptjson.iv);
    if (decryptjson.s) cipherParams.salt = CryptoJS.enc.Hex.parse(decryptjson.s);
    var decrypted = CryptoJS.AES.decrypt(cipherParams, key).toString(CryptoJS.enc.Utf8);
    console.log("decryptjson.iv:", decryptjson.iv)
    console.log("decrypted:     ", decrypted); // The quick brown fox jumps over the lazy dog
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>

    A possible Python implementation of the functionality from EVP_BytesToKey() needed for this case is (alternatively, an implementation from the web can be used, e.g. here):

    from Crypto.Hash import MD5
    ...
    def bytesToKey(salt, password):
        data = b''
        tmp = b''
        while len(data) < 48:
            md5 = MD5.new()
            md5.update(tmp + password + salt)
            tmp = md5.digest()
            data += tmp
        return data
    

    With this, the JavaScript code can be ported as follows (as in the JavaScript code, the IV is determined from the key derivation):

    from Crypto.Cipher import AES
    from Crypto.Util.Padding import unpad
    import base64
    ...
    password = b'my password'
    decryptdict = {'ct': 'CoOMBKETmRnGvir8p7JyRsWhOm9VCtV08POy6TmFgkbOoT00tk3UXKa0QGrtGoI5', 'iv': '958a46b44cfbf7871c625666c4cbad8e', 's': 'ce9448fbb051ea03'}
    ciphertext = base64.b64decode(decryptdict['ct'])
    salt = bytes.fromhex(decryptdict['s'])
    keyIv = bytesToKey(salt, password)
    key = keyIv[:32]
    iv = keyIv[32:] # from key derivation
    cipher = AES.new(key, AES.MODE_CBC, iv)
    decryptedstring = unpad(cipher.decrypt(ciphertext), 16)
    print(decryptedstring.decode('utf8')) # The quick brown fox jumps over the lazy dog
    

    In both code snippets PyCryptodome is used as crypto library.


    Be aware that EVP_BytesToKey() is deprecated and deemed insecure, while PBKDF2 is comparatively secure.