16

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?

4
  • CryptoJS AES defaults to CBC mode, so no CBF mode in python needed. AES.encrypt returns also a object. with toString() you get a base64 encoded blob containing salt, iv and message. All of them you need to seperate end feed into Python. also, be aware that password and key are 2 different things. Commented Apr 21, 2016 at 8:46
  • Thanks, just modify key to sample_passphrase, so it will be more clear. but it what if I cannot get toString() output? Is this become a impossible job? @SKR Commented Apr 21, 2016 at 10:09
  • so you mean you have no control at all over the js? How does the js-code continue? what happens with the object returned by encrypt? You need to get the salt and iv out of the js somehow. Or do you just want to "mock" the behavior of the js and generate a similar encrypted message for a external api or something? Commented Apr 21, 2016 at 14:16
  • Yes, you are right. I have no control at all over the js. I can only get the password string as a single input. What I want to is create a same password as javascript generated so I can use it to pass some web testing from python. @SKR Commented Apr 22, 2016 at 2:39

1 Answer 1

45

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://cdn.rawgit.com/CryptoStore/crypto-js/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 Cryptodome import Random from Cryptodome.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.

[UPDATE]:

You should use pycryptodome instead of pycrypto because pycrypto(latest pypi version is 2.6.1) no longer maintained and it has vulnerabilities CVE-2013-7459 and CVE-2018-6594 (CVE warning reported by github). I choose pycryptodomex package here(Cryptodome replace Crypto in code) instead of pycryptodome package to avoid conflict name with Crypto from pycrypto package.

Sign up to request clarification or add additional context in comments.

15 Comments

Have tried this one, it works perfectly. Thanks a lot. I don't know much on how encrypt and decrypt work with these options, if you could mark some reference links I can get these info, that will be very appreciated.
I'm not sure I understand which references you're looking for. If you're talking about how I know all this, then the easiest way would be to simply take the unminified source code and read it.
Awesome code. Thank you. Spend a lot of time in cryptojs code trying to figure this out
@ArtjomB. It has been documented at pycryptodome.org/en/latest/src/vs_pycrypto.html
Don't forget to decode your string.return unpad(aes.decrypt(encrypted[16:])).decode()
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.