~3 min read • Updated Mar 15, 2026
Introduction
One of the core principles of web security is simple: never trust data from untrusted sources. Sometimes, however, you need to pass data through an untrusted channel—URLs, cookies, hidden form fields, or external systems. Django’s cryptographic signing framework allows you to safely transmit data while ensuring that any tampering is immediately detected.
Django provides both:
- a low-level API for signing arbitrary values
- a high-level API for signed cookies
Common use cases include:
- Password recovery URLs
- Hidden form field protection
- One-time access links for paid downloads
- Secure tokens passed through untrusted systems
Protecting SECRET_KEY and SECRET_KEY_FALLBACKS
When you create a Django project, SECRET_KEY is generated automatically.
This key is used to sign data, so it must remain private.
If an attacker obtains it, they can forge signatures.
SECRET_KEY_FALLBACKS allows key rotation:
fallback keys validate old signatures but do not sign new data.
Using the Low-Level Signing API
The signing tools live in django.core.signing.
To sign a value:
from django.core.signing import Signer
signer = Signer()
value = signer.sign("My string")
# 'My string:signature'
To unsign:
original = signer.unsign(value)
Signing Non-String Values
Values are converted to strings before signing:
signed = signer.sign(2.5)
signer.unsign(signed) # '2.5'
Signing Complex Data Structures
Use sign_object() and unsign_object():
signed_obj = signer.sign_object({"message": "Hello!"})
obj = signer.unsign_object(signed_obj)
If tampering occurs, Django raises BadSignature.
Using a Custom Secret Key
You can override the default secret:
signer = Signer(key="my-other-secret")
Using Salt
If you want different signatures for the same value, use salt:
signer = Signer(salt="extra")
signer.sign("My string")
Salt creates separate “namespaces” for signatures. A signature generated with one salt cannot validate in another.
Salt does not need to be secret.
Timestamped Signatures
TimestampSigner adds a timestamp to the signature, allowing expiration checks.
from django.core.signing import TimestampSigner
from datetime import timedelta
signer = TimestampSigner()
value = signer.sign("hello")
signer.unsign(value, max_age=10) # raises SignatureExpired
signer.unsign(value, max_age=20) # OK
You can also sign complex objects with timestamps:
signer.sign_object({"foo": "bar"})
Protecting Complex Data Structures
Django provides dumps() and loads() as shortcuts for timestamped signing:
from django.core import signing
value = signing.dumps({"foo": "bar"})
signing.loads(value)
These use JSON serialization, which avoids the security risks of pickle. Note: tuples become lists when loaded.
Function Reference
- sign(): sign a string
- unsign(): validate and return original value
- sign_object(): sign dict/list/tuple
- unsign_object(): validate and return object
- dumps(): serialize + timestamp + sign
- loads(): validate + deserialize
Conclusion
Django’s cryptographic signing framework provides a powerful and secure way to transmit data through untrusted channels.
With tools like Signer, TimestampSigner, dumps(), and loads(), you can ensure data integrity, detect tampering, and safely handle complex structures—all while keeping your application secure and standards‑compliant.
Written & researched by Dr. Shahin Siami