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.
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.
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()
.
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
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.
Given the above, this is not surprising.
If you don't truncate the key at all, the same exception is produced.
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:
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))