aboutsummaryrefslogtreecommitdiffstats
path: root/docs/fernet.rst
diff options
context:
space:
mode:
Diffstat (limited to 'docs/fernet.rst')
-rw-r--r--docs/fernet.rst175
1 files changed, 166 insertions, 9 deletions
diff --git a/docs/fernet.rst b/docs/fernet.rst
index eacbc2ae..dd9d75bd 100644
--- a/docs/fernet.rst
+++ b/docs/fernet.rst
@@ -3,7 +3,7 @@ Fernet (symmetric encryption)
.. currentmodule:: cryptography.fernet
-Fernet provides guarantees that a message encrypted using it cannot be
+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`.
@@ -19,13 +19,14 @@ has support for implementing key rotation via :class:`MultiFernet`.
>>> f = Fernet(key)
>>> token = f.encrypt(b"my deep dark secret")
>>> token
- '...'
+ b'...'
>>> f.decrypt(token)
- 'my deep dark secret'
+ b'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.
+ :param 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.
+ :type key: bytes or str
.. classmethod:: generate_key()
@@ -37,6 +38,9 @@ has support for implementing key rotation via :class:`MultiFernet`.
.. method:: encrypt(data)
+ Encrypts data passed. The result of this encryption is known as a
+ "Fernet token" and has strong privacy and authenticity guarantees.
+
: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
@@ -50,8 +54,35 @@ has support for implementing key rotation via :class:`MultiFernet`.
generated in *plaintext*, the time a message was created will
therefore be visible to a possible attacker.
+ .. method:: encrypt_at_time(data, current_time)
+
+ .. versionadded:: 3.0
+
+ Encrypts data passed using explicitly passed current time. See
+ :meth:`encrypt` for the documentation of the ``data`` 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 as ``current_time`` outside testing.
+
+ :param int current_time: The current time.
+
+ .. note::
+
+ Similarly to :meth:`encrypt` the encrypted message contains the
+ timestamp in *plaintext*, in this case the timestamp is the value
+ of the ``current_time`` parameter.
+
+
.. method:: 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.
+
: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
@@ -72,13 +103,47 @@ has support for implementing key rotation via :class:`MultiFernet`.
:raises TypeError: This exception is raised if ``token`` is not
``bytes``.
+ .. method:: decrypt_at_time(token, ttl, current_time)
+
+ .. versionadded:: 3.0
+
+ Decrypts a token using explicitly passed current time. See
+ :meth:`decrypt` for the documentation of the ``token`` and ``ttl``
+ 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 as ``current_time`` outside testing.
+
+ :param int current_time: The current time.
+
+
+ .. method:: extract_timestamp(token)
+
+ .. versionadded:: 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.
+
+ :param bytes token: The Fernet token. This is the result of calling
+ :meth:`encrypt`.
+ :returns int: The UNIX timestamp of the token.
+ :raises cryptography.fernet.InvalidToken: If the ``token``'s signature
+ is invalid this exception
+ is raised.
+ :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:
+ :class:`Fernet` instances and implements the same API with the exception
+ of one additional method: :meth:`MultiFernet.rotate`:
.. doctest::
@@ -88,9 +153,9 @@ has support for implementing key rotation via :class:`MultiFernet`.
>>> f = MultiFernet([key1, key2])
>>> token = f.encrypt(b"Secret message!")
>>> token
- '...'
+ b'...'
>>> f.decrypt(token)
- 'Secret message!'
+ 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
@@ -101,11 +166,96 @@ has support for implementing key rotation via :class:`MultiFernet`.
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 :meth:`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.
+
+ .. method:: rotate(msg)
+
+ .. versionadded:: 2.2
+
+ Rotates a token by re-encrypting it under the :class:`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.
+
+ .. 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
+ 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!'
+
+ :param bytes msg: The token to re-encrypt.
+ :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.
+ :raises TypeError: This exception is raised if the ``msg`` is not
+ ``bytes``.
+
.. 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
+:class:`~cryptography.hazmat.primitives.kdf.scrypt.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
+ 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 100,000 iterations which is what Django
+recommended in 2014.
+
Implementation
--------------
@@ -122,6 +272,13 @@ Specifically it uses:
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. Unfortunately, this makes it
+generally unsuitable for very large files at this time.
+
.. _`Fernet`: https://github.com/fernet/spec/
.. _`specification`: https://github.com/fernet/spec/blob/master/Spec.md