Search code examples
pythoncryptographyaesquantum-computingpython-cryptography

Use AES-256 with python's cryptography.fernet.Fernet by changing values of _signing_key and _encryption_key fields


I want to use AES-256 with python's cryptography.fernet.Fernet class. I have generated two keys with Fernet.generate_key() and tried to run the version of the following code (where instead of key1 and key2 there are actual keys):

import base64
from cryptography.fernet import Fernet

f = Fernet(Fernet.generate_key())

f._signing_key = base64.urlsafe_b64decode(key1)
f._encryption_key = base64.urlsafe_b64decode(key2)

token = f.encrypt(b'Secret message!')
print(f'{token}')

print(f.decrypt(token).decode())

It worked, but does this use AES-256 instead of AES-128?

Also, is this code quantum computers resistant? If this is not the case, how can I use Fernet class to do post-quantum cryptography?


Solution

  • Fernet is specified to use AES-128-CBC for encryption, so if you use AES-256, it isn't Fernet anymore. The Fernet class is only specified to implement Fernet, not almost-Fernet-but-with-AES-256.

    The _encryption_key field of Fernet is not documented. By convention, in Python, fields whose name begins with an underscore should not be accessed outside the class. So you use it at your own risk. It can only be an exercise for curiosity's sake, not production code.

    We can try out what happens. As an exercise in using low-level cryptography, here's some code that lets you inspect the plaintext of a Fernet token. It uses the Cipher class from the hazmat section of the cryptography package. It only does the easy part: raw decryption, without unpadding or authentication.

    #!/usr/bin/env python3
    from base64 import urlsafe_b64decode
    import os
    from cryptography.fernet import Fernet
    from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
    
    def fernet_decrypt_unsafe(key, token):
        token_bin = urlsafe_b64decode(token)
        iv = token_bin[9:25]
        ciphertext = token_bin[25:-32]
        cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
        decryptor = cipher.decryptor()
        padded_plaintext = decryptor.update(ciphertext)
        padded_plaintext += decryptor.finalize()
        return padded_plaintext
    
    fernet1 = Fernet(Fernet.generate_key())
    token1 = fernet1.encrypt(b'Secret message!')
    print('decoded token:', urlsafe_b64decode(token1).hex())
    print('manual inspection:', fernet_decrypt_unsafe(fernet1._encryption_key, token1))
    print('decrypted:', fernet1.decrypt(token1))
    
    fernet2 = Fernet(Fernet.generate_key())
    aes256_key = os.urandom(32)
    print('Injecting 32-byte keys')
    fernet2._encryption_key = aes256_key
    fernet2._signing_key = os.urandom(32)
    token2 = fernet2.encrypt(b'Secret message!')
    print('decoded token:', urlsafe_b64decode(token2).hex())
    print('manual inspection:', fernet_decrypt_unsafe(aes256_key, token2))
    print('decrypted:', fernet2.decrypt(token2))
    

    Let's try this out.

    decoded token: 800000000066d2428e8b28fb68bc52281c2e91ff548094d1472d34da863663cf5cd00a6d60e845b80e211e77927032be14d1d4fd0c36488162596680b1fce9f523d9eefe6a60d8659e
    manual inspection: b'Secret message!\x01'
    decrypted: b'Secret message!'
    Injecting 32-byte keys
    decoded token: 800000000066d2428e01a49121e6c34236730c4b85a4827e1a170b7784ce40229462b04ec338c3d068ceb782f3db89dd04187faa323894b4ad06e2a0abd91445f73996567db734d4d2
    manual inspection: b'Secret message!\x01'
    decrypted: b'Secret message!'
    

    So, at least with cryptography 41.0.1, it does work as one might expect. You do get a token that is very much like a Fernet token, except that the ciphertext is encrypted with AES-256-CBC instead of AES-128-CBC. However the next version might do it differently.

    There's no way to use a different algorithm for the authentication part: it always uses HMAC-SHA-256. But that's enough even if quantum cryptanalysis turns out to be doable anyway.

    Quantum computers that are capable of breaking 128-bit cryptography require solving some tough problems in theoretical physics, so they are not your biggest problem right now. API misuse is a much, much bigger problem. As a cryptography API designer, I find it hard enough to make APIs that are robust. If you start using undocumented interfaces, you don't stand a chance.

    If you do need to protect some confidential data in the long term (decades), resistance against quantum computers is a concern. In that case, use an established format with established software that is quantum-safe. PyNaCl has an implementation of SecretBox, which is similar to Fernet but encrypts XSalsa20 which uses a 256-bit key.