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. 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 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 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 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 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
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 BadSignature
exception is raised.
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.
It’s Dangerous Changelog¶
Version 0.24¶
Added a BadHeader exception that is used for bad headers that replaces the old BadPayload exception that was reused in those cases.
Version 0.23¶
Fixed a packaging mistake that caused the tests and license files to not be included.
Version 0.22¶
Added support for TimedJSONWebSignatureSerializer.
made it possible to override the signature verification function to allow implementing asymmetrical algorithms.
Version 0.21¶
Fixed an issue on Python 3 which caused invalid errors to be generated.
Version 0.20¶
Fixed an incorrect call into want_bytes that broke some uses of itsdangerous on Python 2.6.
Version 0.19¶
Dropped support for 2.5 and added support for 3.3.
Version 0.18¶
Added support for JSON Web Signatures (JWS).
Version 0.17¶
Fixed a name error when overriding the digest method.
Version 0.16¶
made it possible to pass unicode values to load_payload to make it easier to debug certain things.
Version 0.15¶
made standalone load_payload more robust by raising one specific error if something goes wrong.
refactored exceptions to catch more cases individually, added more attributes.
fixed an issue that caused load_payload not work in some situations with timestamp based serializers
added an loads_unsafe method.
Version 0.14¶
API refactoring to support different key derivations.
Added attributes to exceptions so that you can inspect the data even if the signature check failed.
Version 0.13¶
Small API change that enables customization of the digest module.
Version 0.12¶
Fixed a problem with the local timezone being used for the epoch calculation. This might invalidate some of your signatures if you were not running in UTC timezone. You can revert to the old behavior by monkey patching itsdangerous.EPOCH.
Version 0.11¶
Fixed an uncought value error.
Version 0.10¶
Refactored interface that the underlying serializers can be swapped by passing in a module instead of having to override the payload loaders and dumpers. This makes the interface more compatible with Django’s recent changes.
API¶
Signers¶
- class itsdangerous.Signer(secret_key, salt=None, sep='.', key_derivation=None, digest_method=None, algorithm=None)¶
This class can sign bytes and unsign it and validate the signature provided.
Salt can be used to namespace the hash, so that a signed string is only valid for a given namespace. Leaving this at the default value or re-using a salt value across different parts of your application where the same signed value in one part can mean something different in another part is a security risk.
See The Salt for an example of what the salt is doing and how you can utilize it.
New in version 0.14: key_derivation and digest_method were added as arguments to the class constructor.
New in version 0.18: algorithm was added as an argument to the class constructor.
- static default_digest_method(string=b'')¶
The digest method to use for the signer. This defaults to sha1 but can be changed for any other function in the hashlib module.
Changed in version 0.14.
- default_key_derivation = 'django-concat'¶
Controls how the key is derived. The default is Django style concatenation. Possible values are
concat
,django-concat
andhmac
. This is used for deriving a key from the secret key with an added salt.New in version 0.14.
- derive_key()¶
This method is called to derive the key. If you’re unhappy with the default key derivation choices you can override them here. Keep in mind that the key derivation in itsdangerous is not intended to be used as a security method to make a complex key out of a short password. Instead you should use large random secret keys.
- get_signature(value)¶
Returns the signature for the given value
- sign(value)¶
Signs the given string.
- unsign(signed_value)¶
Unsigns the given string.
- validate(signed_value)¶
Just validates the given signed value. Returns True if the signature exists and is valid, False otherwise.
- verify_signature(value, sig)¶
Verifies the signature for the given value.
- class itsdangerous.TimestampSigner(secret_key, salt=None, sep='.', key_derivation=None, digest_method=None, algorithm=None)¶
Works like the regular
Signer
but also records the time of the signing and can be used to expire signatures. The unsign method can rause aSignatureExpired
method if the unsigning failed because the signature is expired. This exception is a subclass ofBadSignature
.- get_timestamp()¶
Returns the current timestamp. This implementation returns the seconds since 1/1/2011. The function must return an integer.
- sign(value)¶
Signs the given string and also attaches a time information.
- timestamp_to_datetime(ts)¶
Used to convert the timestamp from get_timestamp into a datetime object.
- unsign(value, max_age=None, return_timestamp=False)¶
Works like the regular
unsign()
but can also validate the time. See the base docstring of the class for the general behavior. If return_timestamp is set to True the timestamp of the signature will be returned as naivedatetime.datetime
object in UTC.
- validate(signed_value, max_age=None)¶
Just validates the given signed value. Returns True if the signature exists and is valid, False otherwise.
Signing Algorithms¶
- class itsdangerous.NoneAlgorithm¶
This class provides a algorithm that does not perform any signing and returns an empty signature.
- class itsdangerous.HMACAlgorithm(digest_method=None)¶
This class provides signature generation using HMACs.
Serializers¶
- class itsdangerous.Serializer(secret_key, salt=b'itsdangerous', serializer=None, signer=None, signer_kwargs=None)¶
This class provides a serialization interface on top of the signer. It provides a similar API to json/pickle and other modules but is slightly differently structured internally. If you want to change the underlying implementation for parsing and loading you have to override the
load_payload()
anddump_payload()
functions.This implementation uses simplejson if available for dumping and loading and will fall back to the standard library’s json module if it’s not available.
Starting with 0.14 you do not need to subclass this class in order to switch out or customer the
Signer
. You can instead also pass a different class to the constructor as well as keyword arguments as dictionary that should be forwarded:s = Serializer(signer_kwargs={'key_derivation': 'hmac'})
Changed in version 0.14:: The signer and signer_kwargs parameters were added to the constructor.
- default_serializer = <module 'json' from '/home/user/.pyenv/versions/3.8.18/lib/python3.8/json/__init__.py'>¶
If a serializer module or class is not passed to the constructor this one is picked up. This currently defaults to
json
.
- default_signer¶
The default
Signer
class that is being used by this serializer.New in version 0.14.
alias of
Signer
- dump(obj, f, salt=None)¶
Like
dumps()
but dumps into a file. The file handle has to be compatible with what the internal serializer expects.
- dump_payload(obj)¶
Dumps the encoded object. The return value is always a bytestring. If the internal serializer is text based the value will automatically be encoded to utf-8.
- dumps(obj, salt=None)¶
Returns a signed string serialized with the internal serializer. The return value can be either a byte or unicode string depending on the format of the internal serializer.
- load_payload(payload, serializer=None)¶
Loads the encoded object. This function raises
BadPayload
if the payload is not valid. The serializer parameter can be used to override the serializer stored on the class. The encoded payload is always byte based.
- load_unsafe(f, *args, **kwargs)¶
Like
loads_unsafe()
but loads from a file.New in version 0.15.
- loads(s, salt=None)¶
Reverse of
dumps()
, raisesBadSignature
if the signature validation fails.
- loads_unsafe(s, salt=None)¶
Like
loads()
but without verifying the signature. This is potentially very dangerous to use depending on how your serializer works. The return value is(signature_okay, payload)
instead of just the payload. The first item will be a boolean that indicates if the signature is okay (True
) or if it failed. This function never fails.Use it for debugging only and if you know that your serializer module is not exploitable (eg: do not use it with a pickle serializer).
New in version 0.15.
- class itsdangerous.TimedSerializer(secret_key, salt=b'itsdangerous', serializer=None, signer=None, signer_kwargs=None)¶
Uses the
TimestampSigner
instead of the defaultSigner()
.- default_signer¶
alias of
TimestampSigner
- loads(s, max_age=None, return_timestamp=False, salt=None)¶
Reverse of
dumps()
, raisesBadSignature
if the signature validation fails. If a max_age is provided it will ensure the signature is not older than that time in seconds. In case the signature is outdated,SignatureExpired
is raised which is a subclass ofBadSignature
. All arguments are forwarded to the signer’sunsign()
method.
- loads_unsafe(s, max_age=None, salt=None)¶
Like
loads()
but without verifying the signature. This is potentially very dangerous to use depending on how your serializer works. The return value is(signature_okay, payload)
instead of just the payload. The first item will be a boolean that indicates if the signature is okay (True
) or if it failed. This function never fails.Use it for debugging only and if you know that your serializer module is not exploitable (eg: do not use it with a pickle serializer).
New in version 0.15.
- class itsdangerous.JSONWebSignatureSerializer(secret_key, salt=None, serializer=None, signer=None, signer_kwargs=None, algorithm_name=None)¶
This serializer implements JSON Web Signature (JWS) support. Only supports the JWS Compact Serialization.
- default_algorithm = 'HS256'¶
The default algorithm to use for signature generation
- default_serializer = <itsdangerous._CompactJSON object>¶
If a serializer module or class is not passed to the constructor this one is picked up. This currently defaults to
json
.
- dump_payload(header, obj)¶
Dumps the encoded object. The return value is always a bytestring. If the internal serializer is text based the value will automatically be encoded to utf-8.
- dumps(obj, salt=None, header_fields=None)¶
Like
dumps()
but creates a JSON Web Signature. It also allows for specifying additional fields to be included in the JWS Header.
- load_payload(payload, return_header=False)¶
Loads the encoded object. This function raises
BadPayload
if the payload is not valid. The serializer parameter can be used to override the serializer stored on the class. The encoded payload is always byte based.
- loads(s, salt=None, return_header=False)¶
Reverse of
dumps()
. If requested via return_header it will return a tuple of payload and header.
- loads_unsafe(s, salt=None, return_header=False)¶
Like
loads()
but without verifying the signature. This is potentially very dangerous to use depending on how your serializer works. The return value is(signature_okay, payload)
instead of just the payload. The first item will be a boolean that indicates if the signature is okay (True
) or if it failed. This function never fails.Use it for debugging only and if you know that your serializer module is not exploitable (eg: do not use it with a pickle serializer).
New in version 0.15.
- class itsdangerous.TimedJSONWebSignatureSerializer(secret_key, expires_in=None, **kwargs)¶
Works like the regular
JSONWebSignatureSerializer
but also records the time of the signing and can be used to expire signatures.JWS currently does not specify this behavior but it mentions a possibility extension like this in the spec. Expiry date is encoded into the header similarily as specified in `draft-ietf-oauth-json-web-token <http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#expDef`_.
The unsign method can raise a
SignatureExpired
method if the unsigning failed because the signature is expired. This exception is a subclass ofBadSignature
.- loads(s, salt=None, return_header=False)¶
Reverse of
dumps()
. If requested via return_header it will return a tuple of payload and header.
- class itsdangerous.URLSafeSerializer(secret_key, salt=b'itsdangerous', serializer=None, signer=None, signer_kwargs=None)¶
Works like
Serializer
but dumps and loads into a URL safe string consisting of the upper and lowercase character of the alphabet as well as'_'
,'-'
and'.'
.
- class itsdangerous.URLSafeTimedSerializer(secret_key, salt=b'itsdangerous', serializer=None, signer=None, signer_kwargs=None)¶
Works like
TimedSerializer
but dumps and loads into a URL safe string consisting of the upper and lowercase character of the alphabet as well as'_'
,'-'
and'.'
.
Exceptions¶
- exception itsdangerous.BadData(message)¶
Raised if bad data of any sort was encountered. This is the base for all exceptions that itsdangerous is currently using.
New in version 0.15.
- exception itsdangerous.BadSignature(message, payload=None)¶
This error is raised if a signature does not match. As of itsdangerous 0.14 there are helpful attributes on the exception instances. You can also catch down the baseclass
BadData
.- payload¶
The payload that failed the signature test. In some situations you might still want to inspect this, even if you know it was tampered with.
New in version 0.14.
- exception itsdangerous.BadTimeSignature(message, payload=None, date_signed=None)¶
Raised for time based signatures that fail. This is a subclass of
BadSignature
so you can catch those down as well.- date_signed¶
If the signature expired this exposes the date of when the signature was created. This can be helpful in order to tell the user how long a link has been gone stale.
New in version 0.14.
- exception itsdangerous.SignatureExpired(message, payload=None, date_signed=None)¶
Signature timestamp is older than required max_age. This is a subclass of
BadTimeSignature
so you can use the baseclass for catching the error.
- exception itsdangerous.BadHeader(message, payload=None, header=None, original_error=None)¶
Raised if a signed header is invalid in some form. This only happens for serializers that have a header that goes with the signature.
New in version 0.24.
- header¶
If the header is actually available but just malformed it might be stored here.
- original_error¶
If available, the error that indicates why the payload was not valid. This might be None.
- exception itsdangerous.BadPayload(message, original_error=None)¶
This error is raised in situations when payload is loaded without checking the signature first and an exception happend as a result of that. The original exception that caused that will be stored on the exception as
original_error
.This can also happen with a
JSONWebSignatureSerializer
that is subclassed and uses a different serializer for the payload than the expected one.New in version 0.15.
- original_error¶
If available, the error that indicates why the payload was not valid. This might be None.
Useful Helpers¶
- itsdangerous.base64_encode(string)¶
base64 encodes a single bytestring (and is tolerant to getting called with a unicode string). The resulting bytestring is safe for putting into URLs.
- itsdangerous.base64_decode(string)¶
base64 decodes a single bytestring (and is tolerant to getting called with a unicode string). The result is also a bytestring.