Skip to content

Instantly share code, notes, and snippets.

@sh4dowb
Created September 17, 2021 19:41
Show Gist options
  • Save sh4dowb/efab23eda2fc7c0172c9829cf34dc96c to your computer and use it in GitHub Desktop.
Save sh4dowb/efab23eda2fc7c0172c9829cf34dc96c to your computer and use it in GitHub Desktop.
Decrypt crypto-js default AES encryption with OpenSSL KDF in Python 3
# I absolutely hated crypto-js for this. non-standard configurations, weird algorithms, ...
# well obviously you can encrypt it with a better configuration which people will not
# go crazy figuring out its implementation, but in this case I wasn't encrypting the data.
import base64
from Crypto.Hash import MD5
from Crypto.Util.Padding import unpad
from Crypto.Cipher import AES
# generated using: CryptoJS.AES.encrypt('test 123456 plaintext', 'some password').toString()
ciphertext_b64 = "U2FsdGVkX180iqdQznzbX/7S9Zk9fRNwmtPeogG5TjB7565wNv15JoUWQ75VSYc7"
password = "some password"
encryptedData = base64.b64decode(ciphertext_b64)
# Get the salt for KDF
salt = encryptedData[8:16]
ciphertext = encryptedData[16:]
# PBKDF2 won't work, apparently crypto-js uses some shit called EVP or something
# it uses MD5 with 1 iteration by default, making me post this snippet
# oh, also guess what? when you ask crypto-js for the key size (CryptoJS.algo.AES.keySize)
# it will tell you it's 8, and use it like that in code, but turns out their "1" is 4 bytes
derived = b""
while len(derived) < 48: # "key size" + "iv size" (8 + 4 magical units = 12 * 4 = 48)
hasher = MD5.new()
hasher.update(derived[-16:] + password.encode('utf-8') + salt)
derived += hasher.digest()
# "8" key size = 32 actual bytes
key = derived[0:32]
iv = derived[32:48]
# fucking finally we got the actual key and IV
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = unpad(cipher.decrypt(ciphertext), 16)
print(decrypted) # output: b'test 123456 plaintext'
@lasofeli
Copy link

Thank you for putting an end to my misery.

@Danyfirex
Copy link

thank you for the snippet 🤟.

Here’s my two cents: I just put the code into a function and added encryption.

import base64
from Crypto.Hash import MD5
from Crypto.Util.Padding import unpad
from Crypto.Util.Padding import pad
from Crypto.Cipher import AES
import secrets

def decrypt(ciphertext, password):
    encryptedData = base64.b64decode(ciphertext)

    salt = encryptedData[8:16]
    ciphertext = encryptedData[16:]

    derived = b""
    while len(derived) < 48:  # "key size" + "iv size" (8 + 4 magical units = 12 * 4 = 48)
        hasher = MD5.new()
        hasher.update(derived[-16:] + password.encode('utf-8') + salt)
        derived += hasher.digest()

    key = derived[0:32]
    iv = derived[32:48]

    # Decrypt the ciphertext
    cipher = AES.new(key, AES.MODE_CBC, iv)
    decrypted = unpad(cipher.decrypt(ciphertext), 16)
    return decrypted.decode('utf-8')

def encrypt(plaintext, password):
    salt = secrets.token_bytes(8)

    derived = b""
    while len(derived) < 48:  # "key size" + "iv size" (8 + 4 magical units = 12 * 4 = 48)
        hasher = MD5.new()
        hasher.update(derived[-16:] + password.encode('utf-8') + salt)
        derived += hasher.digest()

    key = derived[0:32]
    iv = derived[32:48]

    # Encrypt the plaintext
    cipher = AES.new(key, AES.MODE_CBC, iv)
    encrypted = cipher.encrypt(pad(plaintext.encode('utf-8'), 16))

    # Combine salt and encrypted data
    encrypted_bytes = base64.b64encode(b'Salted__' + salt + encrypted)
    return encrypted_bytes.decode('utf-8')

# Example
plaintext = "Hello World!"
password = "some password"
encrypted = encrypt(plaintext, password)
print("Encrypted ciphertext (base64):", encrypted)
decrypted = decrypt(encrypted, password)
print("Decrypted ciphertext (base64):", decrypted)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment