Created
September 17, 2021 19:41
-
-
Save sh4dowb/efab23eda2fc7c0172c9829cf34dc96c to your computer and use it in GitHub Desktop.
Decrypt crypto-js default AES encryption with OpenSSL KDF in Python 3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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' |
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
Thank you for putting an end to my misery.