Al desarrollar en Django, hay ocasiones en las que necesitamos enviar datos al cliente a través de parámetros URL, campos ocultos en formularios o cookies, y luego recibirlos de nuevo. En este caso, surge la pregunta: "¿No habrá sido este dato modificado por un usuario en el medio?" django.core.signing es una poderosa herramienta para resolver exactamente este problema.

Este módulo proporciona firmas criptográficas (Cryptographic Signing) en lugar de cifrado (Encryption) de los datos.

  • Cifrado (X): Oculta el contenido de los datos.

  • Firma (O): El contenido de los datos puede ser expuesto, pero garantiza que los datos no han sido manipulados.


1. Uso básico: dumps() y loads()



El corazón del módulo signing es dumps() y loads(). Convierte un objeto de Python en una cadena firmada, o valida y restaura un objeto desde una cadena firmada.

dumps(): Convertir objeto a cadena firmada

dumps() recibe objetos que pueden ser serializados como diccionarios, listas, etc., y devuelve una cadena firmada que es segura para URL.

from django.core.signing import dumps

# Datos a firmar
user_data = {'user_id': 123, 'role': 'user'}

# Firma los datos y los convierte en una cadena.
signed_data = dumps(user_data)

print(signed_data)
# Ejemplo de salida: eyJ1c2VyX2lkIjoxMjMsInJvbGUiOiJ1c2VyIn0:1q51oH:F... (datos:firma)

El resultado está en forma de [datos codificados]:[firma]. La parte frontal es la versión codificada en Base64 de los datos originales, por lo que cualquiera puede decodificar y ver los originales. La parte posterior es la firma generada con la SECRET_KEY de Django (valor hash).

loads(): Restaurar de cadena firmada a objeto (validación)

loads() recibe la cadena generada por dumps() y valida la firma. Si la firma es válida, devuelve el objeto original; si no, genera una excepción BadSignature.

from django.core.signing import loads, BadSignature

try:
    # Restaurar los datos firmados al objeto original (validar)
    unsigned_data = loads(signed_data)
    print(unsigned_data)
    # Salida: {'user_id': 123, 'role': 'user'}

except BadSignature:
    print("Los datos han sido manipulados o la firma no es válida.")

# ¿Y si se cambia alguna letra de los datos?
tampered_data = signed_data.replace('user', 'admin')

try:
    loads(tampered_data)
except BadSignature:
    print("Los datos falsificados generan una excepción BadSignature.")

Siempre debe usarse dentro de un bloque try...except BadSignature.


2. Característica clave: ¿Dónde se almacenan los datos?

La mayor característica de django.core.signing es que es "Sin estado (Stateless)".

La cadena generada por dumps() no se almacena en ninguna base de datos o caché del servidor. Toda la información (datos y firma) está incluida en la cadena firmada misma.

El servidor solo entrega esta cadena al cliente (por URL, cookies, campos de formulario, etc.), y cuando el cliente vuelve a enviar este valor más tarde, simplemente valida su validez de inmediato usando la SECRET_KEY. Esto significa que no utiliza ningún espacio de almacenamiento en el servidor, siendo muy ligero y eficiente.


3. Configuración de tiempo de validez: El secreto de max_age



"¿Cómo se puede establecer un tiempo de validez si los datos no se almacenan en el servidor?"

La opción max_age incluye una marca de tiempo (timestamp) en la firma de los datos cuando se llama a dumps().

Al llamar a loads(), si se pasa el argumento max_age (en segundos), se verifica la marca de tiempo incluida tras la validación de la firma.

  1. Calcula la diferencia entre el tiempo actual y la marca de tiempo.

  2. Si esta diferencia supera max_age, incluso si la firma no ha sido falsificada, se generará una excepción SignatureExpired.

from django.core.signing import dumps, loads, SignatureExpired, BadSignature
import time

# 1. Generar la firma (se registra el tiempo actual)
signed_data = dumps({'user_id': 456})

# 2. Validación con un tiempo de 10 segundos (inmediata) -> éxito
try:
    data = loads(signed_data, max_age=10)
    print(f"Validación exitosa: {data}")
except SignatureExpired:
    print("La firma ha expirado.")
except BadSignature:
    print("La firma no es válida.")


# 3. Esperar 5 segundos
time.sleep(5)

# 4. Validación con un tiempo de 3 segundos (ya han pasado 5 segundos) -> falla
try:
    data = loads(signed_data, max_age=3)
    print(f"Validación exitosa: {data}")
except SignatureExpired:
    print("La firma ha expirado. (max_age=3)")
except BadSignature:
    print("La firma no es válida.")

Este también es un método sin estado, que utiliza la marca de tiempo incluida en la firma en lugar de almacenar información de expiración en el servidor.


4. Importante! Precauciones y consejos de uso

  1. La SECRET_KEY es vital.

    Todo este mecanismo de firma se basa en la SECRET_KEY de settings.py. Si esta clave se filtra, cualquier persona puede generar una firma válida, así que nunca debe ser expuesta externamente. (¡No la subas a Git!)

  2. No olvides que no es cifrado.

    La parte frontal de los datos firmados (Base64) puede ser decodificada fácilmente por cualquiera para ver su contenido original. Nunca pongas información sensible directa en dumps() (ej: user_id está bien, pero user_password no lo está).

  3. Separar las firmas con sal.

    Utiliza el argumento salt cuando estés usando firmas para propósitos diferentes. Si el salt es diferente, incluso para los mismos datos, el resultado de la firma será completamente distinto.

# Usando diferentes salt según el propósito
pw_reset_token = dumps(user.pk, salt='password-reset')
unsubscribe_link = dumps(user.pk, salt='unsubscribe')
Esto ayuda a prevenir ataques donde una firma de 'enlace de cancelación' es reutilizada para 'restablecimiento de contraseña'.

5. Casos de uso principales

  • URL de restablecimiento de contraseña: Firma el ID del usuario y la marca de tiempo y envíalo por correo electrónico (sin necesidad de almacenar un token temporal en la base de datos)

  • Enlace de verificación de correo electrónico: Enlace de verificación de correo al registrarse

  • URL next segura: Previene que la URL ?next=/private/ sea cambiada a un sitio malicioso después de iniciar sesión

  • Enlace de descarga temporal: Genera un URL de acceso a archivos que solo es válido por un tiempo específico (max_age)

  • Formularios de múltiples pasos (Form Wizard): Previene manipulación de datos al pasar la información del formulario anterior al siguiente