pythoncryptographybase64fernet

Fernet key must be 32 url-safe base64-encoded bytes - How to create a key for use with Fernet?


An example Fernet key

The following code produces an example Fernet key, and reports that the length is 44.

from cryptography.fernet import Fernet

generated_key = Fernet.generate_key()
print(f'Example generated key: {generated_key}')
print(f'Length of example key: {len(generated_key)}')

The output is (for example)

Example generated key: b'U4f1fCfXWlz7pQ-7WdZKmCY-VtSAln7R-hhvF6qgYa4='
Length of example key: 44

A quick look at this reference tells us that = is a padding character. There are actually 43 characters, if this = symbol is not included.

U4f1fCfXWlz7pQ-7WdZKmCY-VtSAln7R-hhvF6qgYa4

1    2    3    4    5    6    7    8    9    10   11
U4f1 fCfX Wlz7 pQ-7 WdZK mCY- VtSA ln7R -hhv F6qg Ya4

That is 10 blocks of 4 characters plus 3 characters = 43.

Generating an example Fernet key

I am attempting to generate an example key using the following code:

key = "1234567890123456789012345678901234567890"
print(f'Encryption key is: {key}')

base64_key = base64.b64encode(key.encode('utf-8'))
print(f'Base64 encoded key is: {base64_key}')
print(f'Length of base64 encoded key: {len(base64_key)}')

if len(base64_key) > 43:
    base64_key = base64_key[:43]
    print(f'Truncating key to length {43}: {base64_key}')
    print(f'Length of base64 encoded key is now {len(base64_key)}')
elif len(base64_key) < required_key_length_characters:
    print(f'error: key is too short')
    raise RuntimeError(f'key too short')

fernet_instance = Fernet(base64_key)

However this produces the following output and exception:

Encryption key is: 1234567890123456789012345678901234567890
Base64 encoded key is: b'MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MA=='
Length of base64 encoded key: 56
Truncating key to length 43: b'MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI'
Length of base64 encoded key is now 43

binascii.Error: Incorrect padding

Indeed, manually hacking the padding

base64_key = base64_key + b'='

fixes the problem.

How long should a Fernet key be?

The exception produced by my code says

ValueError: Fernet key must be 32 url-safe base64-encoded bytes

32 bytes = 256 bits. Each base64 encoded character is 6 bits.

256 / 6 = 42.6666... or 43 if you round up. That matches the length of the key produced by Fernet.generate_key().

Why does it not work if the key is too short?

A 43 character key contains enough bits to have 256 bits of encryption. So why does this not work?

It actually has slightly more than 256 bits of information. 43 * 6 = 258

Why does it not work if the key is exactly the right length?

A 44 character key contains enough bits to have 256 bits of encryption, and it aligns with a 4 byte boundary. But, it still fails and produces the following exception:

ValueError: Fernet key must be 32 url-safe base64-encoded bytes

So even if the 4 byte boundary is some odd arbitrary requirement (is it a bug, perhaps?) then this should work, especially given that a key of length 43 does not have exactly 256 bits of information. It has 258.

Does not work if key too long

Given the above, this is not surprising.

If you don't truncate the key at all, the same exception is produced.

So what is the right way to generate this key?

The logic of truncating to 43 characters and then adding a padding character on the end to increase the length to 44 seems arbitrary.

Is there a better way to generate this kind of key using a password style input?


To clarify a couple of things:


Solution

  • One convenient way to do it is to hash the password using a fixed length hashing algorithm, such as sha256. This is how I have done it:

    def get_fernet_key_from_password_string(password: str) -> bytes:
        key = password
        hashed_key = hashlib.sha256(key.encode('utf-8'))
        hashed_key_digest = hashed_key.digest()
        base64_key = base64.b64encode(hashed_key_digest)
        return base64_key
    

    This can then be used with Fernet.

    base64_key = get_fernet_key_from_password(password)
    print(f'Base64 encoded key is: {base64_key}')
    
    fernet_instance = Fernet(base64_key)
    token = fernet_instance.encrypt(b"some example content to encrypt")
    print(token)
    print(fernet_instance.decrypt(token))