itsdangerous

Sometimes you just want to send some data to untrusted environments. But how to do this? The trick involves signing. Given a key only you know and some random data, you can cryptographically sign them and, hand it over to someone else, and 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 it uses HMAC and SHA1 for signing and bases the implementation on the Django signing module. 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. With Signer 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 don’t be able to tell if it was unicode or a bytestring.

If the unsining 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 which will additionally put a timestamp information in and sign this. On unsigning you can validate if 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 serilization interface similar to json/pickle and others. Internally it does in fact use simplejson by default which however can be changed if you subclass. 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]

The Salt

All classes also accept a salt argument. The name might me 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 to 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

It’s Dangerous Changelog

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='.')

This class can sign a string 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.

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.

class itsdangerous.TimestampSigner(secret_key, salt=None, sep='.')

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 a SignatureExpired method if the unsigning failed because the signature is expired. This exception is a subclass of BadSignature.

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 naive datetime.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.

Serializers

class itsdangerous.Serializer(secret_key, salt='itsdangerous', serializer=None)

This class provides a serialization interface on top of the signer. It provides a similar API to json/pickle/simplejson 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() and dump_payload() functions.

This implementation uses simplejson for dumping and loading.

Changed in version 0..

default_serializer = <module 'json' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.pyc'>
dump(obj, f)

Like dumps() but dumps into a file.

dump_payload(obj)

Dumps the encoded object into a bytestring. This implementation uses simplejson.

dumps(obj)

Returns URL-safe, sha1 signed base64 compressed JSON string.

If compress is True (the default) checks if compressing using zlib can save some space. Prepends a ‘.’ to signify compression. This is included in the signature, to protect against zip bombs.

load(f)

Like loads() but loads from a file.

load_payload(payload)

Loads the encoded object. This implementation uses simplejson.

loads(s)

Reverse of dumps(), raises BadSignature if the signature validation fails.

make_signer()

A method that creates a new instance of the signer to be used. The default implementation uses the Signer baseclass.

class itsdangerous.TimedSerializer(secret_key, salt='itsdangerous', serializer=None)

Uses the TimestampSigner instead of the default Signer().

loads(s, max_age=None, return_timestamp=False)

Reverse of dumps(), raises BadSignature 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 of BadSignature. All arguments are forwarded to the signer’s unsign() method.

class itsdangerous.URLSafeSerializer(secret_key, salt='itsdangerous', serializer=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='itsdangerous', serializer=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.BadSignature

This error is raised if a signature does not match

exception itsdangerous.SignatureExpired

Signature timestamp is older than required max_age. This is a subclass of BadSignature so you can use the baseclass for catching the error.

Useful Helpers

itsdangerous.base64_encode(string)

base64 encodes a single string. The resulting string is safe for putting into URLs.

itsdangerous.base64_decode(string)

base64 decodes a single string.

Fork me on GitHub