R. Bourgeon
R. Bourgeon

Reputation: 1013

Decrypting a string encrypted with AES-256-CBC using Python results in a zero-length result

I need to access some resources protected by passwords. A colleague of mine gave me access to a database table that contains these passwords, but they are encrypted. (and I think the result of the encryption is encoded in base64, since it's 44 characters long and it always ends with an equal "=" character).

My colleague gave me the name of the algorithm he used to encrypt the passwords: AES-256-CBC, and he also gave me the encryption key.

I thought that with all this information it would be easy to decipher the passwords, but it was not.

I used the functions suggested on this page: https://paperbun.org/encrypt-and-decrypt-using-pycrypto-aes-256-python/

that are the following:

# Define the encryption function
def encrypt_AES_CBC_256(key, message):
    key_bytes = key.encode('utf-8')
    message_bytes = message.encode('utf-8')
    iv = get_random_bytes(AES.block_size)
    cipher = AES.new(key_bytes, AES.MODE_CBC, iv)
    padded_message = pad(message_bytes, AES.block_size)
    ciphertext_bytes = cipher.encrypt(padded_message)
    ciphertext = b64encode(iv + ciphertext_bytes).decode('utf-8')
    return ciphertext


# Define the decryption function
def decrypt_AES_CBC_256(key, ciphertext):
    key_bytes = key.encode('utf-8')
    ciphertext_bytes = b64decode(ciphertext)
    iv = ciphertext_bytes[:AES.block_size]
    cipher = AES.new(key_bytes, AES.MODE_CBC, iv)
    ciphertext_bytes = ciphertext_bytes[AES.block_size:]
    decrypted_bytes = cipher.decrypt(ciphertext_bytes)
    plaintext_bytes = unpad(decrypted_bytes, AES.block_size)
    plaintext = plaintext_bytes.decode('utf-8')
    return plaintext

Then, I tried these functions by encrypting a message, decrypting it back and ensuring that the decrypted result was identical to the initial string. Here is my code:

key = "XXXXXXXXXXXXXXXXXXXXXXXXXXX" # actual key is 32 characters long

print('-----------')
raw_input = "HERE IS MY TEST STRING"
print(f"Entrée : {raw_input}")
test4 = encrypt_AES_CBC_256(key, raw_input)
print(f"Entrée chiffrée : {test4}")
test5 = decrypt_AES_CBC_256(key, test4)
print(f"Message déchiffré : {test5}")

The output is:

-----------
Entrée : HERE IS MY TEST STRING
Entrée chiffrée : sZ1iZks9+qGVDHzt9WEO1hU3YrXKHvJj5sQzVd64HscPT8fiFBNbeihGFxpFyGC3
Message déchiffré : HERE IS MY TEST STRING

We can see that everything seems to work fine, the decrypted message equals the input.

Now, I tried to decrypt (with the same key) an encrypted password given by my colleague:

print('-----------')
raw = "e46mK0OmmYMBa2qkaALx5aUea4y/pT43OapQ6lmnDOA="
print(f"Mot de passe chiffré : {raw}")
FINAL_TEST = decrypt_AES_CBC_256(key, raw)
print(f"Mot de passe déchiffré : {FINAL_TEST}")
print(f"Longueur du résultat : {len(FINAL_TEST)} caractères")

The output was:

-----------
Mot de passe chiffré : e46mK0OmmYMBa2qkaALx5aUea4y/pT43OapQ6lmnDOA=
Mot de passe déchiffré : 
Longueur du résultat : 0 caractères

We can see that the result is 0 character long. And it was the same with every encrypted password he gave me. What am I missing?

And why is the encrypted password only 44 characters long and not 64? I know that this is somewhat related to 256/6 = 42.66 and then you pad the missing characters with = signs, and since you want the length to be a multiple of 4 you pad up to 44, but if my coworker indeed use the AES-CBC-256 algorithm, why would his encrypted passwords not end up being 64 characters long like my encrypted test message?

Upvotes: 0

Views: 4289

Answers (1)

R. Bourgeon
R. Bourgeon

Reputation: 1013

I found a solution! In fact, to encrypt the passwords, my coworker used the PHP function openssl_encrypt: https://www.php.net/manual/en/function.openssl-encrypt

 openssl_encrypt(
    string $data,
    string $cipher_algo,
    string $passphrase,
    int $options = 0,
    string $iv = "",
    string &$tag = null,
    string $aad = "",
    int $tag_length = 16
): string|false

You can notice that the IV used in the encryption has a default value of an empty string "". It means that the first 16 bytes are the null character \x00. And in fact, my coworker kept this default value (which is not very secure...) so, having this information, I was abled to decipher the passwords using an IV whose all 16 characters are \x00.

Here is my code:

from base64 import b64decode

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad


def decipher(encrypted, passphrase):
    nulls = b'\x00' * 16
    my_cipher = AES.new(
        key=passphrase.encode('utf-8'),
        mode=AES.MODE_CBC,
        iv=nulls
    )
    result = unpad(
        my_cipher.decrypt(b64decode(encrypted)),
        AES.block_size
    ).decode('utf-8')
    return result

Upvotes: 0

Related Questions