pythonencryptionpycryptopycryptodome

how do i decrypt a cyphertext encrypted with AES with pycryptodome?


I'm making a password manager with python that stores and encrypts passwords (using the pycriptodome module) with a main password set by the user, but the decryption does not work Here's the function I'm using to decrypt the passwords

def decrypt_data(key, iv, ct):  # Makes the credentials readable and usable
    print(f'{bcolors.warning}\nDebug decrypt_data(1):\tiv:\t\t\t{iv}') # debug text
    print(f'Debug decrypt_data(2):\tiv variable type:\t{type(iv)}') # debug text
    print(f'Debug decrypt_data(3):\tiv_bytes:\t\t{bytes(iv, 'utf-8')}') # debug text
    print(f'Debug decrypt_data(4):\tiv_bytes type:\t\t{type(bytes(iv, 'utf-8'))}\n{bcolors.endc}') # debug text
    cipher = AES.new(key, AES.MODE_CBC)
    pt = unpad(cipher.decrypt(bytes(ct, 'utf-8')), AES.block_size)
    print(f'{bcolors.okcyan}Decripting done{bcolors.endc}')
    return decrypted.decode("UTF-8")

Console output:

Debug decrypt_data(1):  iv:                     +00X7cdUZm7L61ifRb9EDQ==
Debug decrypt_data(2):  iv variable type:       <class 'str'>
Debug decrypt_data(3):  iv_bytes:               b'+00X7cdUZm7L61ifRb9EDQ=='
Debug decrypt_data(4):  iv_bytes type:          <class 'bytes'>

Traceback (most recent call last):
  File "D:\py_proj\Password Manager\.venv\Lib\pw_manager.py", line 151, in <module>
    print(f'{bcolors.warning}Debug main(5):\t\tdecrypted main_pw:\t{decrypt_data(padded_key, encrypt_data(padded_key, main_pw)[1], encrypt_data(padded_key, main_pw)[0])}') # debug text
                                                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\py_proj\Password Manager\.venv\Lib\pw_manager.py", line 107, in decrypt_data
    pt = unpad(cipher.decrypt(bytes(ct, 'utf-8')), AES.block_size)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Utente\AppData\Local\Programs\Python\Python312\Lib\site-packages\Crypto\Cipher\_mode_cbc.py", line 246, in decrypt
    raise ValueError("Data must be padded to %d byte boundary in CBC mode" % self.block_size)
ValueError: Data must be padded to 16 byte boundary in CBC mode

Just for reference here's the code for the encryption and part of the main function as well

def encrypt_data(key, pt): # protects the credentials in the file once saved
    print(f'\nDebug encrypt_data(1):\tplaintext:\t\t{pt}') # debug text
    print(f'Debug encrypt_data(2):\tbytetext:\t\t{bytes(pt, 'utf-8')}') # debug text
    print(f'Debug encrypt_data(3):\tpadded plaintext:\t{pad(bytes(pt, 'utf-8'), AES.block_size)}') # debug text
    cipher = AES.new(key, AES.MODE_CBC)
    ct_bytes = cipher.encrypt(pad(bytes(pt, 'utf-8'), AES.block_size))
    print(f'Debug encrypt_data(4):\tct_bytes:\t\t{ct_bytes}') # debug text
    iv = b64encode(cipher.iv).decode('utf-8')
    ct = b64encode(ct_bytes).decode('utf-8')
    print(f'Debug encrypt_data(5):\tb64 decoded ct:\t\t{ct}') # debug text
    print(f'Debug encrypt_data(6):\tiv:\t\t\t{iv}') # debug text
    print(f'Debug encrypt_data(7):\tciphertext:\t\t{ct}\n') # debug text
    print(f'Encripting done')

    return ct, iv

if __name__ == '__main__':
    # login screen
    if not os.path.exists(credentials_file):
        signin()
    main_pw = login()
    padded_key = pad(bytes(main_pw, 'utf-8'), AES.block_size)  # Pad the key to the correct length
    print(f'Debug main(1):\t\tmain_pw:\t\t{main_pw}') # debug text
    print(f'Debug main(2):\t\tbyte_pw:\t\t{bytes(main_pw, 'utf-8')}') # debug text
    print(f'Debug main(3):\t\tpadded_key:\t\t{padded_key}') # debug text
    print(f'Debug main(4):\t\tencrypted main_pw:\t{encrypt_data(padded_key, main_pw)[0]}') # debug text
    print(f'Debug main(5):\t\tdecrypted main_pw:\t{decrypt_data(padded_key, encrypt_data(padded_key, main_pw)[1], encrypt_data(padded_key, main_pw)[0])}') # debug text
    print(f'Debug main(6):\t\tunpadded decrypted main_pw:\t{decrypt_data(padded_key, encrypt_data(padded_key, main_pw)[1], encrypt_data(padded_key, main_pw)[0])}\n') # debug text

this is the full output of the console:

Debug main(1):          main_pw:                test
Debug main(2):          byte_pw:                b'test'
Debug main(3):          padded_key:             b'test\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c'

Debug encrypt_data(1):  plaintext:              test
Debug encrypt_data(2):  bytetext:               b'test'
Debug encrypt_data(3):  padded plaintext:       b'test\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c'
Debug encrypt_data(4):  ct_bytes:               b'I\xdd\x95\x9fXl"\xa1\xd9Xfd~\xc5\xc2\xcd'
Debug encrypt_data(5):  b64 decoded ct:         Sd2Vn1hsIqHZWGZkfsXCzQ==
Debug encrypt_data(6):  iv:                     lmuFGlygOos07SPYpBWNBw==
Debug encrypt_data(7):  ciphertext:             Sd2Vn1hsIqHZWGZkfsXCzQ==

Encripting done
Debug main(4):          encrypted main_pw:      Sd2Vn1hsIqHZWGZkfsXCzQ==

Debug encrypt_data(1):  plaintext:              test
Debug encrypt_data(2):  bytetext:               b'test'
Debug encrypt_data(3):  padded plaintext:       b'test\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c'
Debug encrypt_data(4):  ct_bytes:               b'\t\r\xfbd\x07\x1d\xcay>\xa49\xdeK\xd9W\xbb'
Debug encrypt_data(5):  b64 decoded ct:         CQ37ZAcdynk+pDneS9lXuw==
Debug encrypt_data(6):  iv:                     +00X7cdUZm7L61ifRb9EDQ==
Debug encrypt_data(7):  ciphertext:             CQ37ZAcdynk+pDneS9lXuw==

Encripting done

Debug encrypt_data(1):  plaintext:              test
Debug encrypt_data(2):  bytetext:               b'test'
Debug encrypt_data(3):  padded plaintext:       b'test\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c'
Debug encrypt_data(4):  ct_bytes:               b'=\xf6\xa4\xcetfD\xe4\x12\xef\xff\xce\x0f(]t'
Debug encrypt_data(5):  b64 decoded ct:         PfakznRmROQS7//ODyhddA==
Debug encrypt_data(6):  iv:                     w/FfW0RCcLqv8FjqRtCpxg==
Debug encrypt_data(7):  ciphertext:             PfakznRmROQS7//ODyhddA==

Encripting done

Debug decrypt_data(1):  iv:                     +00X7cdUZm7L61ifRb9EDQ==
Debug decrypt_data(2):  iv variable type:       <class 'str'>
Debug decrypt_data(3):  iv_bytes:               b'+00X7cdUZm7L61ifRb9EDQ=='
Debug decrypt_data(4):  iv_bytes type:          <class 'bytes'>

Traceback (most recent call last):
  File "D:\py_proj\Password Manager\.venv\Lib\pw_manager.py", line 151, in <module>
    print(f'Debug main(5):\t\tdecrypted main_pw:\t{decrypt_data(padded_key, encrypt_data(padded_key, main_pw)[1], encrypt_data(padded_key, main_pw)[0])}') # debug text
                                                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\py_proj\Password Manager\.venv\Lib\pw_manager.py", line 107, in decrypt_data
    pt = unpad(cipher.decrypt(bytes(ct, 'utf-8')), AES.block_size)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Utente\AppData\Local\Programs\Python\Python312\Lib\site-packages\Crypto\Cipher\_mode_cbc.py", line 246, in decrypt
    raise ValueError("Data must be padded to %d byte boundary in CBC mode" % self.block_size)
ValueError: Data must be padded to 16 byte boundary in CBC mode

[processo terminato con codice 1 (0x00000001)]

I tried padding before decrypting using pt = unpad(cipher.decrypt(pad(bytes(ct, 'utf-8'), AES.block_size)), AES.block_size) instead of pt = unpad(cipher.decrypt(pad(bytes(ct, 'utf-8'), AES.block_size)), AES.block_size) but it gave this error:

Traceback (most recent call last):
  File "D:\py_proj\Password Manager\.venv\Lib\pw_manager.py", line 151, in <module>
    print(f'Debug main(5):\t\tdecrypted main_pw:\t{decrypt_data(padded_key, encrypt_data(padded_key, main_pw)[1], encrypt_data(padded_key, main_pw)[0])}') # debug text
                                                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\py_proj\Password Manager\.venv\Lib\pw_manager.py", line 107, in decrypt_data
    pt = unpad(cipher.decrypt(pad(bytes(ct, 'utf-8'), AES.block_size)), AES.block_size)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Utente\AppData\Local\Programs\Python\Python312\Lib\site-packages\Crypto\Util\Padding.py", line 92, in unpad
    raise ValueError("Padding is incorrect.")
ValueError: Padding is incorrect.

[processo terminato con codice 1 (0x00000001)]

Solution

  • Here's a simplified and functioning version of your code that, for simplicity, accepts base64 encoded plain-text strings as key and IV material (these are easy to store in e.g. text files).

    Do note that CBC mode is probably not what you want since it doesn't provide any authentication (i.e. that you're getting back the text that was originally encrypted).

    import base64
    import secrets
    
    from Crypto.Cipher import AES
    from Crypto.Util.Padding import unpad, pad
    
    
    def encrypt_data(key_str: str, plaintext: str) -> tuple[str, str]:
        key_bytes = base64.b64decode(key_str.encode("utf-8"))
        cipher = AES.new(key_bytes, AES.MODE_CBC)
        ct_bytes = cipher.encrypt(pad(plaintext.encode("utf-8"), AES.block_size))
        iv = base64.b64encode(cipher.iv).decode("utf-8")
        ciphertext = base64.b64encode(ct_bytes).decode("utf-8")
        return ciphertext, iv
    
    
    def decrypt_data(key_str: str, iv_str: str, ciphertext_str: str) -> str:
        key_bytes = base64.b64decode(key_str.encode("utf-8"))
        iv_bytes = base64.b64decode(iv_str.encode("utf-8"))
        ciphertext_bytes = base64.b64decode(ciphertext_str.encode("utf-8"))
        cipher = AES.new(key_bytes, AES.MODE_CBC, iv=iv_bytes)
        plaintext_bytes = unpad(cipher.decrypt(ciphertext_bytes), AES.block_size)
        return plaintext_bytes.decode("UTF-8")
    
    
    key_bytes = secrets.token_bytes(32)
    key_str = base64.b64encode(key_bytes).decode("utf-8")
    plaintext = "Hello, World!"
    
    print(f"{key_str=}, {plaintext=}")
    ciphertext, iv = encrypt_data(key_str, plaintext)
    print(f"{ciphertext=}, {iv=}")
    decrypted = decrypt_data(key_str, iv, ciphertext)
    print(f"{decrypted=}")
    

    This prints out (e.g.)

    key_str='Zdu1DUQC/dZhGC8Q1n0raD8BlW7pfzLZxwmPqn0o6s8=', plaintext='Hello, World!'
    ciphertext='Es7w8eWcRHGhfpwBDnr6vQ==', iv='Grf/VFkH8Qp7VS6RVj2e2g=='
    decrypted='Hello, World!'