File: index.rst

package info (click to toggle)
python-itsdangerous 0.24%2Bdfsg1-2
  • links: PTS, VCS
  • area: main
  • in suites: buster, stretch
  • size: 244 kB
  • ctags: 224
  • sloc: python: 811; makefile: 166
file content (310 lines) | stat: -rw-r--r-- 10,081 bytes parent folder | download | duplicates (3)
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
itsdangerous
============

.. module:: itsdangerous

Sometimes you just want to send some data to untrusted environments.  But
how to do this safely?  The trick involves signing.  Given a key only you
know, you can cryptographically sign your data and hand it over to someone
else.  When you get the data back you can easily ensure that nobody tampered
with it.

Granted, the receiver can decode the contents and look into the package,
but they can not modify the contents unless they also have your secret
key.  So if you keep the key secret and complex, you will be fine.

Internally itsdangerous uses HMAC and SHA1 for signing by default and bases the
implementation on the `Django signing module
<https://docs.djangoproject.com/en/dev/topics/signing/>`_.  It also
however supports JSON Web Signatures (JWS).  The library is BSD licensed and
written by Armin Ronacher though most of the copyright for the design and
implementation goes to Simon Willison and the other amazing Django people
that made this library possible.

Installation
------------

You can get the library directly from PyPI::

    pip install itsdangerous

Example Use Cases
-----------------

-   You can serialize and sign a user ID for unsubscribing of newsletters
    into URLs.  This way you don't need to generate one-time tokens and
    store them in the database.  Same thing with any kind of activation
    link for accounts and similar things.
-   Signed objects can be stored in cookies or other untrusted sources
    which means you don't need to have sessions stored on the server, which
    reduces the number of necessary database queries.
-   Signed information can safely do a roundtrip between server and client
    in general which makes them useful for passing server-side state to a
    client and then back.

Signing Interface
-----------------

The most basic interface is the signing interface.  The :class:`Signer` class
can be used to attach a signature to a specific string:

>>> from itsdangerous import Signer
>>> s = Signer('secret-key')
>>> s.sign('my string')
'my string.wh6tMHxLgJqB6oY1uT73iMlyrOA'
    
The signature is appended to the string, separated by a dot (``.``).  To
validate the string, use the :meth:`~Signer.unsign` method:

>>> s.unsign('my string.wh6tMHxLgJqB6oY1uT73iMlyrOA')
'my string'

If unicode strings are provided, an implicit encoding to utf-8 happens.
However after unsigning you won't be able to tell if it was unicode or
a bytestring.

If the unsigning fails you will get an exception:

>>> s.unsign('my string.wh6tMHxLgJqB6oY1uT73iMlyrOX')
Traceback (most recent call last):
  ...
itsdangerous.BadSignature: Signature "wh6tMHxLgJqB6oY1uT73iMlyrOX" does not match

Signatures with Timestamps
--------------------------

If you want to expire signatures you can use the :class:`TimestampSigner`
class which will additionally put in a timestamp information and sign it.
On unsigning you can validate that the timestamp did not expire:

>>> from itsdangerous import TimestampSigner
>>> s = TimestampSigner('secret-key')
>>> string = s.sign('foo')
>>> s.unsign(string, max_age=5)
Traceback (most recent call last):
  ...
itsdangerous.SignatureExpired: Signature age 15 > 5 seconds

Serialization
-------------

Because strings are hard to handle this module also provides a
serialization interface similar to json/pickle and others.  (Internally
it uses simplejson by default, however this can be changed by subclassing.)
The :class:`Serializer` class implements that:

>>> from itsdangerous import Serializer
>>> s = Serializer('secret-key')
>>> s.dumps([1, 2, 3, 4])
'[1, 2, 3, 4].r7R9RhGgDPvvWl3iNzLuIIfELmo'

And it can of course also load:

>>> s.loads('[1, 2, 3, 4].r7R9RhGgDPvvWl3iNzLuIIfELmo')
[1, 2, 3, 4]

If you want to have the timestamp attached you can use the
:class:`TimedSerializer`.

URL Safe Serialization
----------------------

Often it is helpful if you can pass these trusted strings to environments
where you only have a limited set of characters available.  Because of
this, itsdangerous also provides URL safe serializers:

>>> from itsdangerous import URLSafeSerializer
>>> s = URLSafeSerializer('secret-key')
>>> s.dumps([1, 2, 3, 4])
'WzEsMiwzLDRd.wSPHqC0gR7VUqivlSukJ0IeTDgo'
>>> s.loads('WzEsMiwzLDRd.wSPHqC0gR7VUqivlSukJ0IeTDgo')
[1, 2, 3, 4]

JSON Web Signatures
-------------------

Starting with “itsdangerous” 0.18 JSON Web Signatures are also supported.
They generally work very similar to the already existing URL safe
serializer but will emit headers according to the current draft (10) of
the JSON Web Signature (JWS) [``draft-ietf-jose-json-web-signature``].

>>> from itsdangerous import JSONWebSignatureSerializer
>>> s = JSONWebSignatureSerializer('secret-key')
>>> s.dumps({'x': 42})
'eyJhbGciOiJIUzI1NiJ9.eyJ4Ijo0Mn0.ZdTn1YyGz9Yx5B5wNpWRL221G1WpVE5fPCPKNuc6UAo'

When loading the value back the header will not be returned by default
like with the other serializers.  However it is possible to also ask for
the header by passing ``return_header=True``.
Custom header fields can be provided upon serialization:

>>> s.dumps(0, header_fields={'v': 1})
'eyJhbGciOiJIUzI1NiIsInYiOjF9.MA.wT-RZI9YU06R919VBdAfTLn82_iIQD70J_j-3F4z_aM'
>>> s.loads('eyJhbGciOiJIUzI1NiIsInYiOjF9.MA.wT-RZI9YU06R919VBdAf'
...         'TLn82_iIQD70J_j-3F4z_aM', return_header=True)
...
(0, {u'alg': u'HS256', u'v': 1})

“itsdangerous” only provides HMAC SHA derivatives and the none algorithm
at the moment and does not support the ECC based ones.  The algorithm in
the header is checked against the one of the serializer and on a mismatch
a :exc:`BadSignature` exception is raised.

.. _the-salt:

The Salt
--------

All classes also accept a salt argument.  The name might be misleading
because usually if you think of salts in cryptography you would expect the
salt to be something that is stored alongside the resulting signed string
as a way to prevent rainbow table lookups.  Such salts are usually public.

In “itsdangerous”, like in the original Django implementation, the salt
serves a different purpose.  You could describe it as namespacing.  It's
still not critical if you disclose it because without the secret key it
does not help an attacker.

Let's assume that you have two links you want to sign.  You have the
activation link on your system which can activate a user account and then
you have an upgrade link that can upgrade a user's account to a paid
account which you send out via email.  If in both cases all you sign is
the user ID a user could reuse the variable part in the URL from the
activation link to upgrade the account.  Now you could either put more
information in there which you sign (like the intention: upgrade or
activate), but you could also use different salts:

>>> s1 = URLSafeSerializer('secret-key', salt='activate-salt')
>>> s1.dumps(42)
'NDI.kubVFOOugP5PAIfEqLJbXQbfTxs'
>>> s2 = URLSafeSerializer('secret-key', salt='upgrade-salt')
>>> s2.dumps(42)
'NDI.7lx-N1P-z2veJ7nT1_2bnTkjGTE'
>>> s2.loads(s1.dumps(42))
Traceback (most recent call last):
  ...
itsdangerous.BadSignature: Signature "kubVFOOugP5PAIfEqLJbXQbfTxs" does not match

Only the serializer with the same salt can load the value:

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

Responding to Failure
---------------------

Starting with itsdangerous 0.14 exceptions have helpful attributes which
allow you to inspect payload if the signature check failed.  This has to
be done with extra care because at that point you know that someone
tampered with your data but it might be useful for debugging purposes.

Example usage::

    from itsdangerous import URLSafeSerializer, BadSignature, BadData
    s = URLSafeSerializer('secret-key')
    decoded_payload = None
    try:
        decoded_payload = s.loads(data)
        # This payload is decoded and safe
    except BadSignature, e:
        encoded_payload = e.payload
        if encoded_payload is not None:
            try:
                decoded_payload = s.load_payload(encoded_payload)
            except BadData:
                pass
            # This payload is decoded but unsafe because someone
            # tampered with the signature.  The decode (load_payload)
            # step is explicit because it might be unsafe to unserialize
            # the payload (think pickle instead of json!)

If you don't want to inspect attributes to figure out what exactly went
wrong you can also use the unsafe loading::

    from itsdangerous import URLSafeSerializer
    s = URLSafeSerializer('secret-key')
    sig_okay, payload = s.loads_unsafe(data)

The first item in the returned tuple is a boolean that indicates if the
signature was correct.

Python 3 Notes
--------------

On Python 3 the interface that itsdangerous provides can be confusing at
first.  Depending on the internal serializer it wraps the return value of
the functions can alter between unicode strings or bytes objects.  The
internal signer is always byte based.

This is done to allow the module to operate on different serializers
independent of how they are implemented.  The module decides on the
type of the serializer by doing a test serialization of an empty object.


.. include:: ../CHANGES

API
---

Signers
~~~~~~~

.. autoclass:: Signer
   :members:

.. autoclass:: TimestampSigner
   :members:

Signing Algorithms
~~~~~~~~~~~~~~~~~~

.. autoclass:: NoneAlgorithm

.. autoclass:: HMACAlgorithm

Serializers
~~~~~~~~~~~

.. autoclass:: Serializer
   :members:

.. autoclass:: TimedSerializer
   :members:

.. autoclass:: JSONWebSignatureSerializer
   :members:

.. autoclass:: TimedJSONWebSignatureSerializer
   :members:

.. autoclass:: URLSafeSerializer

.. autoclass:: URLSafeTimedSerializer

Exceptions
~~~~~~~~~~

.. autoexception:: BadData
   :members:

.. autoexception:: BadSignature
   :members:

.. autoexception:: BadTimeSignature
   :members:

.. autoexception:: SignatureExpired
   :members:

.. autoexception:: BadHeader
   :members:

.. autoexception:: BadPayload
   :members:

Useful Helpers
~~~~~~~~~~~~~~

.. autofunction:: base64_encode

.. autofunction:: base64_decode