Bing Bing - 7 months ago 542
Python Question

How to decrypt password from JavaScript CryptoJS.AES.encrypt(password, passphrase) in Python

I have a password which is encrypt from JavaScript via

var password = 'sample'
var passphrase ='sample_passphrase'
CryptoJS.AES.encrypt(password, passphrase)


Then I tried to decrypt the password comes from JavaScript in Python:

from Crypto.Cipher import AES
import base64

PADDING = '\0'

pad_it = lambda s: s+(16 - len(s)%16)*PADDING
key = 'sample_passphrase'
iv='11.0.0.101' #------> here is my question, how can I get this iv to restore password, what should I put here?
key=pad_it(key) #------> should I add padding to keys and iv?
iv=pad_it(iv) ##
source = 'sample'
generator = AES.new(key, AES.MODE_CFB,iv)
crypt = generator.encrypt(pad_it(source))
cryptedStr = base64.b64encode(crypt)
print cryptedStr
generator = AES.new(key, AES.MODE_CBC,iv)
recovery = generator.decrypt(crypt)
print recovery.rstrip(PADDING)


I checked JS from browser console, it shows IV in
CryptoJS.AES.encrypt(password, passphrase)
is a object with some attributes( like
sigBytes:16, words: [-44073646, -1300128421, 1939444916, 881316061]
). It seems generated randomly.

From one web page, it tells me that JS has two way to encrypt password
(reference link ):



  • a.
    crypto.createCipher(algorithm, password)

  • b.
    crypto.createCipheriv(algorithm, key, iv)




What I saw in JavaScript should be option a. However, only option b is equivalent to AES.new() in python.

The questions are:


  1. How can I restore this password in Python without changing JavaScript code?

  2. If I need IV in Python, how can I get it from the password that is used in JavaScript?


Answer

You will have to implement OpenSSL's EVP_BytesToKey, because that is what CryptoJS uses to derive the key and IV from the provided password, but pyCrypto only supports the key+IV type encryption. CryptoJS also generates a random salt which also must be send to the server. If the ciphertext object is converted to a string, then it uses automatically an OpenSSL-compatible format which includes the random salt.

var data = "Some semi-long text for testing";
var password = "some password";
var ctObj = CryptoJS.AES.encrypt(data, password);
var ctStr = ctObj.toString();

out.innerHTML = ctStr;
<script src="https://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"></script>
<div id="out"></div>

Possible output:

U2FsdGVkX1+ATH716DgsfPGjzmvhr+7+pzYfUzR+25u0D7Z5Lw04IJ+LmvPXJMpz

CryptoJS defaults to 256 bit key size for AES, PKCS#7 padding and CBC mode. AES has a 128 bit block size which is also the IV size. This means that we have to request 32+16 = 48 byte from EVP_BytesToKey. I've found a semi-functional implementation here and extended it further.

Here is the full Python (tested with 2.7 and 3.4) code, which is compatible with CryptoJS:

from Crypto import Random
from Crypto.Cipher import AES
import base64
from hashlib import md5

BLOCK_SIZE = 16

def pad(data):
    length = BLOCK_SIZE - (len(data) % BLOCK_SIZE)
    return data + (chr(length)*length).encode()

def unpad(data):
    return data[:-(data[-1] if type(data[-1]) == int else ord(data[-1]))]

def bytes_to_key(data, salt, output=48):
    # extended from https://gist.github.com/gsakkis/4546068
    assert len(salt) == 8, len(salt)
    data += salt
    key = md5(data).digest()
    final_key = key
    while len(final_key) < output:
        key = md5(key + data).digest()
        final_key += key
    return final_key[:output]

def encrypt(message, passphrase):
    salt = Random.new().read(8)
    key_iv = bytes_to_key(passphrase, salt, 32+16)
    key = key_iv[:32]
    iv = key_iv[32:]
    aes = AES.new(key, AES.MODE_CBC, iv)
    return base64.b64encode(b"Salted__" + salt + aes.encrypt(pad(message)))

def decrypt(encrypted, passphrase):
    encrypted = base64.b64decode(encrypted)
    assert encrypted[0:8] == b"Salted__"
    salt = encrypted[8:16]
    key_iv = bytes_to_key(passphrase, salt, 32+16)
    key = key_iv[:32]
    iv = key_iv[32:]
    aes = AES.new(key, AES.MODE_CBC, iv)
    return unpad(aes.decrypt(encrypted[16:]))


password = "some password".encode()
ct_b64 = "U2FsdGVkX1+ATH716DgsfPGjzmvhr+7+pzYfUzR+25u0D7Z5Lw04IJ+LmvPXJMpz"

pt = decrypt(ct_b64, password)
print("pt", pt)

print("pt", decrypt(encrypt(pt, password), password))

Similar code can be found in my answers for Java and PHP.

JavaScript AES encryption in the browser without HTTPS is simple obfuscation and does not provide any real security, because the key must be transmitted alongside the ciphertext.