Fernet (symmetric encryption)¶
Fernet guarantees that a message encrypted using it cannot be
manipulated or read without the key. Fernet is an implementation of
symmetric (also known as “secret key”) authenticated cryptography. Fernet also
has support for implementing key rotation via MultiFernet
.
- class cryptography.fernet.Fernet(key)¶
This class provides both encryption and decryption facilities.
>>> from cryptography.fernet import Fernet >>> key = Fernet.generate_key() >>> f = Fernet(key) >>> token = f.encrypt(b"my deep dark secret") >>> token b'...' >>> f.decrypt(token) b'my deep dark secret'
- Parameters:
key (bytes or str) – A URL-safe base64-encoded 32-byte key. This must be kept secret. Anyone with this key is able to create and read messages.
- classmethod generate_key()¶
Generates a fresh fernet key. Keep this some place safe! If you lose it you’ll no longer be able to decrypt messages; if anyone else gains access to it, they’ll be able to decrypt all of your messages, and they’ll also be able forge arbitrary messages that will be authenticated and decrypted.
- encrypt(data)¶
Encrypts data passed. The result of this encryption is known as a “Fernet token” and has strong privacy and authenticity guarantees.
- Parameters:
data (bytes) – The message you would like to encrypt.
- Returns bytes:
A secure message that cannot be read or altered without the key. It is URL-safe base64-encoded. This is referred to as a “Fernet token”.
- Raises:
TypeError – This exception is raised if
data
is notbytes
.
Note
The encrypted message contains the current time when it was generated in plaintext, the time a message was created will therefore be visible to a possible attacker.
- encrypt_at_time(data, current_time)¶
New in version 3.0.
Encrypts data passed using explicitly passed current time. See
encrypt()
for the documentation of thedata
parameter, the return type and the exceptions raised.The motivation behind this method is for the client code to be able to test token expiration. Since this method can be used in an insecure manner one should make sure the correct time (
int(time.time())
) is passed ascurrent_time
outside testing.- Parameters:
current_time (int) – The current time.
Note
Similarly to
encrypt()
the encrypted message contains the timestamp in plaintext, in this case the timestamp is the value of thecurrent_time
parameter.
- decrypt(token, ttl=None)¶
Decrypts a Fernet token. If successfully decrypted you will receive the original plaintext as the result, otherwise an exception will be raised. It is safe to use this data immediately as Fernet verifies that the data has not been tampered with prior to returning it.
- Parameters:
token (bytes or str) – The Fernet token. This is the result of calling
encrypt()
.ttl (int) – Optionally, the number of seconds old a message may be for it to be valid. If the message is older than
ttl
seconds (from the time it was originally created) an exception will be raised. Ifttl
is not provided (or isNone
), the age of the message is not considered.
- Returns bytes:
The original plaintext.
- Raises:
cryptography.fernet.InvalidToken – If the
token
is in any way invalid, this exception is raised. A token may be invalid for a number of reasons: it is older than thettl
, it is malformed, or it does not have a valid signature.TypeError – This exception is raised if
token
is notbytes
orstr
.
- decrypt_at_time(token, ttl, current_time)¶
New in version 3.0.
Decrypts a token using explicitly passed current time. See
decrypt()
for the documentation of thetoken
andttl
parameters (ttl
is required here), the return type and the exceptions raised.The motivation behind this method is for the client code to be able to test token expiration. Since this method can be used in an insecure manner one should make sure the correct time (
int(time.time())
) is passed ascurrent_time
outside testing.- Parameters:
current_time (int) – The current time.
- extract_timestamp(token)¶
New in version 2.3.
Returns the timestamp for the token. The caller can then decide if the token is about to expire and, for example, issue a new token.
- Parameters:
token (bytes or str) – The Fernet token. This is the result of calling
encrypt()
.- Returns int:
The UNIX timestamp of the token.
- Raises:
cryptography.fernet.InvalidToken – If the
token
’s signature is invalid this exception is raised.TypeError – This exception is raised if
token
is notbytes
orstr
.
- class cryptography.fernet.MultiFernet(fernets)¶
New in version 0.7.
This class implements key rotation for Fernet. It takes a
list
ofFernet
instances and implements the same API with the exception of one additional method:MultiFernet.rotate()
:>>> from cryptography.fernet import Fernet, MultiFernet >>> key1 = Fernet(Fernet.generate_key()) >>> key2 = Fernet(Fernet.generate_key()) >>> f = MultiFernet([key1, key2]) >>> token = f.encrypt(b"Secret message!") >>> token b'...' >>> f.decrypt(token) b'Secret message!'
MultiFernet performs all encryption options using the first key in the
list
provided. MultiFernet attempts to decrypt tokens with each key in turn. Acryptography.fernet.InvalidToken
exception is raised if the correct key is not found in thelist
provided.Key rotation makes it easy to replace old keys. You can add your new key at the front of the list to start encrypting new messages, and remove old keys as they are no longer needed.
Token rotation as offered by
MultiFernet.rotate()
is a best practice and manner of cryptographic hygiene designed to limit damage in the event of an undetected event and to increase the difficulty of attacks. For example, if an employee who had access to your company’s fernet keys leaves, you’ll want to generate new fernet key, rotate all of the tokens currently deployed using that new key, and then retire the old fernet key(s) to which the employee had access.- rotate(msg)¶
New in version 2.2.
Rotates a token by re-encrypting it under the
MultiFernet
instance’s primary key. This preserves the timestamp that was originally saved with the token. If a token has successfully been rotated then the rotated token will be returned. If rotation fails this will raise an exception.>>> from cryptography.fernet import Fernet, MultiFernet >>> key1 = Fernet(Fernet.generate_key()) >>> key2 = Fernet(Fernet.generate_key()) >>> f = MultiFernet([key1, key2]) >>> token = f.encrypt(b"Secret message!") >>> token b'...' >>> f.decrypt(token) b'Secret message!' >>> key3 = Fernet(Fernet.generate_key()) >>> f2 = MultiFernet([key3, key1, key2]) >>> rotated = f2.rotate(token) >>> f2.decrypt(rotated) b'Secret message!'
- Parameters:
- Returns bytes:
A secure message that cannot be read or altered without the key. This is URL-safe base64-encoded. This is referred to as a “Fernet token”.
- Raises:
cryptography.fernet.InvalidToken – If a
token
is in any way invalid this exception is raised.TypeError – This exception is raised if the
msg
is notbytes
orstr
.
- class cryptography.fernet.InvalidToken¶
See
Fernet.decrypt()
for more information.
Using passwords with Fernet¶
It is possible to use passwords with Fernet. To do this, you need to run the
password through a key derivation function such as
PBKDF2HMAC
, bcrypt or
Scrypt
.
>>> import base64
>>> import os
>>> from cryptography.fernet import Fernet
>>> from cryptography.hazmat.primitives import hashes
>>> from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
>>> password = b"password"
>>> salt = os.urandom(16)
>>> kdf = PBKDF2HMAC(
... algorithm=hashes.SHA256(),
... length=32,
... salt=salt,
... iterations=390000,
... )
>>> key = base64.urlsafe_b64encode(kdf.derive(password))
>>> f = Fernet(key)
>>> token = f.encrypt(b"Secret message!")
>>> token
b'...'
>>> f.decrypt(token)
b'Secret message!'
In this scheme, the salt has to be stored in a retrievable location in order to derive the same key from the password in the future.
The iteration count used should be adjusted to be as high as your server can tolerate. A good default is at least 480,000 iterations, which is what Django recommends as of July 2022.
Implementation¶
Fernet is built on top of a number of standard cryptographic primitives. Specifically it uses:
AES
inCBC
mode with a 128-bit key for encryption; usingPKCS7
padding.Initialization vectors are generated using
os.urandom()
.
For complete details consult the specification.
Limitations¶
Fernet is ideal for encrypting data that easily fits in memory. As a design feature it does not expose unauthenticated bytes. This means that the complete message contents must be available in memory, making Fernet generally unsuitable for very large files at this time.