debian-python-cryptography/docs/fernet.rst

170 lines
6.4 KiB
ReStructuredText

Fernet (symmetric encryption)
=============================
.. currentmodule:: cryptography.fernet
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 :class:`MultiFernet`.
.. class:: Fernet(key)
This class provides both encryption and decryption facilities.
.. doctest::
>>> from cryptography.fernet import Fernet
>>> key = Fernet.generate_key()
>>> f = Fernet(key)
>>> token = f.encrypt(b"my deep dark secret")
>>> token
'...'
>>> f.decrypt(token)
'my deep dark secret'
:param bytes key: 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.
.. method:: encrypt(data)
:param bytes data: 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 not
``bytes``.
.. 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.
.. method:: decrypt(token, ttl=None)
:param bytes token: The Fernet token. This is the result of calling
:meth:`encrypt`.
:param int ttl: 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. If ``ttl`` is not
provided (or is ``None``), 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 the
``ttl``, it is malformed, or
it does not have a valid
signature.
:raises TypeError: This exception is raised if ``token`` is not
``bytes``.
.. class:: MultiFernet(fernets)
.. versionadded:: 0.7
This class implements key rotation for Fernet. It takes a ``list`` of
:class:`Fernet` instances, and implements the same API:
.. doctest::
>>> 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
'...'
>>> f.decrypt(token)
'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. A :class:`cryptography.fernet.InvalidToken` exception is raised if
the correct key is not found in the ``list`` 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.
.. class:: InvalidToken
See :meth:`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
:class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC`, bcrypt or
scrypt.
.. doctest::
>>> import base64
>>> import os
>>> from cryptography.fernet import Fernet
>>> from cryptography.hazmat.backends import default_backend
>>> 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=100000,
... backend=default_backend()
... )
>>> key = base64.urlsafe_b64encode(kdf.derive(password))
>>> f = Fernet(key)
>>> token = f.encrypt(b"Secret message!")
>>> token
'...'
>>> f.decrypt(token)
'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 100,000 iterations which is what Django
`recommends`_ in 2014.
Implementation
--------------
Fernet is built on top of a number of standard cryptographic primitives.
Specifically it uses:
* :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` in
:class:`~cryptography.hazmat.primitives.ciphers.modes.CBC` mode with a
128-bit key for encryption; using
:class:`~cryptography.hazmat.primitives.padding.PKCS7` padding.
* :class:`~cryptography.hazmat.primitives.hmac.HMAC` using
:class:`~cryptography.hazmat.primitives.hashes.SHA256` for authentication.
* Initialization vectors are generated using ``os.urandom()``.
For complete details consult the `specification`_.
.. _`Fernet`: https://github.com/fernet/spec/
.. _`specification`: https://github.com/fernet/spec/blob/master/Spec.md
.. _`recommends`: https://github.com/django/django/blob/master/django/utils/crypto.py#L148