According to the documentation for the cryptography.fernet module, fernet keys are:
A URL-safe base64-encoded 32-byte key
Yet this doesn't work:
import secrets
from cryptography import fernet
f = fernet.Fernet(secrets.token_urlsafe(32))
failing with ValueError: Fernet key must be 32 url-safe base64-encoded bytes - however the documentation for token_urlsafe claims that it returns
a random URL-safe text string, containing nbytes random bytes. The text is Base64 encoded ...
Likewise, this doesn't work:
import base64
from cryptography import fernet
key = fernet.Fernet.generate_key()
print(base64.b64decode(key))
failing with: binascii.Error: Incorrect padding.
So what is a Fernet key and what is the right way to go about generating one from a pre-shared string?
FWIW, it took ChatGPT a lot of attempts (and me telling it how to do it) to get this right. To generate a key from a pre-shared string:
This is not a good approach; Fernet uses the first 16 bytes of the key for signing and the second 16 bytes for encryption, so the above example ends up with an encryption key of
"================".The issue with
secrets.token_urlsafe(32)is that, although it does produce a URL-safe base-64-encoded string, the result is not correctly padded for base-64 decoding. This can be rectified like this:In this case, the padding is benign because it's not actually added to the key content, just to the base64-encoded key.
A better approach to generating a key from a pre-shared string is to use a key derivation function such as those in the
kdfpackage.