Search code examples
pythonencryptioncryptographyfernet

Python 3 Fernet Decryption Succeeds with Slightly Modified Encryption Key


def encrypt_message(message, key) -> bytes:
  """Encrypts a message using Fernet."""
  f = Fernet(key)
  encrypted_message = f.encrypt(message.encode())
  return encrypted_message


def decrypt_message(encrypted_message, key) -> str:
  """Decrypts a message using Fernet."""
  f = Fernet(key)
  decrypted_message = f.decrypt(encrypted_message).decode()
  return decrypted_message


message_ = "This is a secret message!"

encrypted = encrypt_message(message_, b"HeYt_XX4wRLvnDP9fC9-7hlMgasQ270tpWSrjKy0jRE=")
# The encrypted message will change in every encryption process which is OK
print("Encrypted:", encrypted)

decrypted = decrypt_message(encrypted, b"HeYt_XX4wRLvnDP9fC9-7hlMgasQ270tpWSrjKy0jRF=")
print("Decrypted:", decrypted)

When calling decrypt_message() I'm modifying the decryption key, I'm changing the 'E' for an 'F' (the same with G, H) and it still succeeds in the decryption process. I don't know why, in change if I put an 'I' for example it fails. As I understand it, symmetric encryption relies on using the exact same key for both encryption and decryption. I'm a newbie in the encryption/decryption world.

Output:

Encrypted: b'gAAAAABmcc7eNiwRP4lYl7gM0mthNdlvYsPUs_nttJMchmZ_mfJNMS4D2BC3QZHZZiZ7vrPhPwvBqK2DDOXSuB5wQkk9t-tU0PYefGvNy2IoDZNGNQMcdc0='
Decrypted: This is a secret message!

Process finished with exit code 0


Solution

  • The "key" you're playing with is the urlsafe base64 encoded value of the 32-byte AES key, not the actual AES key. Because of the nature of the base64 encoding algorithm, some of the bits of the last base64 character are discarded. Remember that the encoding algorithm groups every 3 input bytes (24 bits) and encodes them to 4 base64 characters.

    So what happens if the number of input bytes is not a multiple of 3? For example, a 32-byte AES key is 2 more than a multiple of 3 (i.e., it is 2 mod 3). Then the final two bytes that are left over, which is 16 bits, are encoded by padding out the 16 bits to the next multiple of 6 bits, which is 18 bits. The padding bits are set to zero, but the decoder ignores them, so you can set those two bits to anything you want, they're just thrown away anyway.

    If the number of bytes in the input is 1 mod 3, then there are 4 bits of zeros used to pad, so there are 16 different values of the last base64 character that will decode to the same byte.

    For more details, take a look at the Wikipedia entry or RFC 4648.