itsdangerous

repository·main·Indexed 25 days ago

https://github.com/pallets/itsdangerous

A library for safely passing data to untrusted environments and back using cryptographic signing to ensure data has not been tampered with. It provides tools like URLSafeSerializer, TimestampSigner, and TimedSerializer to handle data serialization, compression, and expiration. Key features include support for secret key rotation, custom serialization, and salt-based context separation to prevent token reuse attacks.

Tokens
5.6K
Snippets
9
Records
44
Agent score
83%

What's inside itsdangerous

  1. Choose between Serializer and Signer

    main

    ItsDangerous provides two levels of data handling:

    1. Signer: The basic system that signs a given bytes value based on signing parameters.
    2. Serializer: A wrapper around a signer that enables serializing and signing data types other than bytes (e.g., dictionaries, integers).

    Recommendation: Typically, you should use a Serializer rather than a Signer. Serializers allow you to configure signing parameters easily and provide fallback signers to upgrade old tokens to new parameters.

  2. Use cases for itsdangerous

    main

    ItsDangerous is designed for safely sending data to untrusted environments (like clients or browsers) and verifying it upon return. Common patterns include:

    • Stateless Authentication/Activation: Sign a user ID in a URL (e.g., for newsletter unsubscribing or account activation) to avoid storing one-time tokens in a database.
    • Client-side Sessions: Store signed objects in cookies or other untrusted sources to pass server-side state to a client and back, reducing the need for server-side session storage and database queries.
    • General Round-tripping: Safely pass information between a server and a client where the client can see the data but cannot modify it without the secret key.
  3. Use ItsDangerous to sign and verify data

    main

    ItsDangerous provides helpers to pass data to untrusted environments (like web clients) and retrieve it safely. It cryptographically signs data to ensure it has not been tampered with. Key features include:

    • Cryptographic Signing: Ensures tokens are authentic.
    • Custom Serialization: You can customize how data is serialized.
    • Compression: Data is compressed as needed.
    • Timestamps: You can add timestamps to tokens and verify them automatically during loading to handle expiration.
  4. Implement Key Rotation

    main

    To mitigate the impact of a compromised secret key, you can implement key rotation. While ItsDangerous does not manage the rotation logic itself, it supports validating tokens against a list of keys.

    When passing a list of keys to a Serializer (ordered from oldest to newest):

    1. Signing: The newest (last) key in the list is used.
    2. Validation: Each key is tried from newest to oldest. This allows tokens signed with older keys to remain valid until they are rotated out of the list.
    from itsdangerous.serializer import Serializer
    
    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
  5. Handle signature failures and inspect tampered payloads

    main

    When a signature check fails, itsdangerous.exc.BadSignature is raised. This exception may contain the payload attribute, which allows you to inspect the data that was tampered with.

    Warning: Inspecting the payload via s.load_payload(e.payload) is an explicit step because unserializing untrusted data can be unsafe (e.g., if using pickle instead of json).

    from itsdangerous.serializer import Serializer, URLSafeSerializer
    from itsdangerous.exc import BadSignature, BadData
    
    s = URLSafeSerializer("secret-key")
    decoded_payload = None
    
    try:
        decoded_payload = s.loads(data)
    except BadSignature as e:
        if e.payload is not None:
            try:
                # Explicitly load the payload from the failed signature
                decoded_payload = s.load_payload(e.payload)
            except BadData:
                pass
  6. Secure the Secret Key

    main

    Signatures are secured by a secret_key. This key must be a long, random string of bytes and must be kept secret. If the secret key is compromised, an attacker can resign data to look valid. Changing the secret key will invalidate all existing tokens.

    Best Practices:

    • Do not save the secret key in source code or commit it to version control.
    • Read the secret key from an environment variable.
    • Generate a key using os.urandom.

    To generate a 16-byte hex key via CLI:

    python3 -c 'import os; print(os.urandom(16).hex())'
    import os
    from itsdangerous.serializer import Serializer
    
    SECRET_KEY = os.environ.get("SECRET_KEY")
    s = Serializer(SECRET_KEY)
  7. Sign and validate with timestamps using TimestampSigner

    main
    Use the TimestampSigner class to create signatures that include timestamp information. This allows you to expire signatures after a certain amount of time by specifying a max_age during the unsigning process. If the signature is older than the allowed age, itsdangerous.exc.SignatureExpired is raised.
  8. Use the Signer class to sign and unsign strings

    main

    The Signer class allows you to attach a cryptographic signature to a string. The signature is appended to the original string, separated by a dot (.).

    To sign a value, use s.sign(value). To validate and retrieve the original value, use s.unsign(signed_value).

    Important details:

    • If you provide unicode strings, they are implicitly encoded to UTF-8. Note that after unsigning, you cannot distinguish whether the original input was a unicode string or a bytestring.
    • If the signed value has been tampered with or the signature does not match, unsign() will raise an itsdangerous.exc.BadSignature exception.
  9. Use Salt to distinguish contexts

    main

    The salt is combined with the secret_key to derive a unique key for different contexts. Unlike the secret key, the salt does not need to be private or random, but it must be unique between different contexts to prevent token reuse attacks.

    Example Scenario: If you use the same salt for both 'account activation' and 'account upgrade' links, a user could potentially use an activation token to perform an upgrade. Using different salts ensures that a token generated for one purpose cannot be used for another.

    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'
    
    # s2.loads(s1.dumps(42)) will raise BadSignature because salts differ
    # s2.loads(s2.dumps(42)) will return 42
  10. Configure fallback signers for algorithm upgrades

    main

    To upgrade signing parameters (like changing the digest method) without immediately invalidating old signatures, use the fallback_signers argument. This allows the serializer to try alternative signing methods if the primary one fails.

    Each item in fallback_signers can be:

    • A dict of signer_kwargs to instantiate the original signer class.
    • A Signer class to be instantiated with the serializer's secret_key, salt, and signer_kwargs.
    • A tuple of (signer_class, signer_kwargs).
  11. Perform key rotation with Signer

    main

    To support key rotation, pass a list of keys to the secret_key parameter of the Signer constructor. The Signer will use the last key in the list to sign new values, but it will attempt to verify incoming signatures using all keys in the list, starting from the newest (the last one) and moving to the oldest.

    This allows you to introduce a new key and gradually phase out old ones without breaking existing signed values.