File: concepts.rst

package info (click to toggle)
python-itsdangerous 2.2.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 412 kB
  • sloc: python: 1,055; makefile: 21; sh: 9
file content (154 lines) | stat: -rw-r--r-- 5,230 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
General Concepts
================


Serializer vs Signer
--------------------

ItsDangerous provides two levels of data handling. The :doc:`/signer` is
the basic system that signs a given ``bytes`` value based on the given
signing parameters. The :doc:`/serializer` wraps a signer to enable
serializing and signing other data besides ``bytes``.

Typically, you'll want to use a serializer, not a signer. You can
configure the signing parameters through the serializer, and even
provide fallback signers to upgrade old tokens to new parameters.


The Secret Key
--------------

Signatures are secured by the ``secret_key``. Typically one secret key
is used with all signers, and the salt is used to distinguish different
contexts. Changing the secret key will invalidate existing tokens.

It should be a long random string of bytes. This value must be kept
secret and should not be saved in source code or committed to version
control. If an attacker learns the secret key, they can change and
resign data to look valid. If you suspect this happened, change the
secret key to invalidate existing tokens.

One way to keep the secret key separate is to read it from an
environment variable. When deploying for the first time, generate a key
and set the environment variable when running the application. All
process managers (like systemd) and hosting services have a way to
specify environment variables.

.. code-block:: python

    import os
    from itsdangerous.serializer import Serializer
    SECRET_KEY = os.environ.get("SECRET_KEY")
    s = Serializer(SECRET_KEY)

.. code-block:: text

    $ export SECRET_KEY="base64 encoded random bytes"
    $ python application.py

One way to generate a key is to use :func:`os.urandom`.

.. code-block:: text

    $ python3 -c 'import os; print(os.urandom(16).hex())'


The Salt
--------

The salt is combined with the secret key to derive a unique key for
distinguishing different contexts. Unlike the secret key, the salt
doesn't have to be random, and can be saved in code. It only has to be
unique between contexts, not private.

For example, you want to email activation links to activate user
accounts, and upgrade links to upgrade users to a paid accounts. If all
you sign is the user id, and you don't use different salts, a user could
reuse the token from the activation link to upgrade the account. If you
use different salts, the signatures will be different and will not be
valid in the other context.

.. code-block:: python

    from itsdangerous.url_safe import URLSafeSerializer

    s1 = URLSafeSerializer("secret-key", salt="activate")
    s1.dumps(42)
    'NDI.MHQqszw6Wc81wOBQszCrEE_RlzY'

    s2 = URLSafeSerializer("secret-key", salt="upgrade")
    s2.dumps(42)
    'NDI.c0MpsD6gzpilOAeUPra3NShPXsE'

The second serializer can't load data dumped with the first because the
salts differ.

.. code-block:: python

    s2.loads(s1.dumps(42))
    Traceback (most recent call last):
      ...
    BadSignature: Signature does not match

Only the serializer with the same salt can load the data.

.. code-block:: python

    s2.loads(s2.dumps(42))
    42


Key Rotation
------------

Key rotation can provide an extra layer of mitigation against an
attacker discovering a secret key. A rotation system will keep a list of
valid keys, generating a new key and removing the oldest key
periodically. If it takes four weeks for an attacker to crack a key, but
the key is rotated out after three weeks, they will not be able to use
any keys they crack. However, if a user doesn't refresh their token
within three weeks it will be invalid too.

The system that generates and maintains this list is outside the scope
of ItsDangerous, but ItsDangerous does support validating against a list
of keys.

Instead of passing a single key, you can pass a list of keys, oldest to
newest. When signing the last (newest) key will be used, and when
validating each key will be tried from newest to oldest before raising
a validation error.

.. code-block:: python

    SECRET_KEYS = ["2b9cd98e", "169d7886", "b6af09f5"]

    # sign some data with the latest key
    s = Serializer(SECRET_KEYS)
    t = s.dumps({"id": 42})

    # rotate a new key in and the oldest key out
    SECRET_KEYS.append("cf9b3588")
    del SECRET_KEYS[0]

    s = Serializer(SECRET_KEYS)
    s.loads(t)  # valid even though it was signed with a previous key


Digest Method Security
----------------------

A signer is configured with a ``digest_method``, a hash function that
is used as an intermediate step when generating the HMAC signature. The
default method is :func:`hashlib.sha1`. Occasionally, users are
concerned about this default because they have heard about hash
collisions with SHA-1.

When used as the intermediate, iterated step in HMAC, SHA-1 is not
insecure. In fact, even MD5 is still secure in HMAC. The security of the
hash alone doesn't apply when used in HMAC.

If a project considers SHA-1 a risk anyway, they can configure the
signer with a different digest method such as :func:`hashlib.sha512`.
A fallback signer for SHA-1 can be configured so that old tokens will be
upgraded. SHA-512 produces a longer hash, so tokens will take up more
space, which is relevant in cookies and URLs.