martes, 20 de enero de 2026

Aprendiendo Python de 0 a experto - Criptografía y Tokens

Criptografía y Tokens en Python
Python // Security

Criptografía y Tokens

Hashlib, HMAC, Secrets y JSON Web Tokens en Python

"Tres pueden guardar un secreto, si dos de ellos están muertos."

— Benjamin Franklin, Poor Richard's Almanack

En este capítulo exploraremos los servicios criptográficos que ofrece la biblioteca estándar de Python. También tocaremos los JSON Web Tokens, un estándar interesante para representar claims de forma segura entre dos partes.

Se estima que en 2024, aproximadamente 5.35 a 5.44 mil millones de personas usan Internet a nivel mundial. Todas estas personas esperan que su dinero esté seguro, sus transacciones sean protegidas, y sus conversaciones privadas.

MÓDULO_00

La Necesidad de Criptografía

La seguridad en tecnología de la información se logra empleando varios medios diferentes, pero el más importante es la criptografía. Casi todo lo que haces con tu computadora o teléfono debería incluir una capa donde la criptografía tiene lugar.

PAGOS ONLINE
Asegura transacciones bancarias y compras en línea
MENSAJERÍA
Transferencia segura de mensajes por la red
ALMACENAMIENTO
Encriptación de archivos en la nube
Regla de oro: No intentes crear tus propias funciones de hash o encriptación. Simplemente no lo hagas. Es increíblemente difícil inventar un algoritmo sólido y robusto, así que es mejor dejarlo a los criptógrafos profesionales.
MÓDULO_01

Hashlib

Este módulo proporciona acceso a una variedad de algoritmos de hash criptográficos. Estas son funciones matemáticas que toman un mensaje de cualquier tamaño y producen un resultado de tamaño fijo, conocido como hash o digest.

Propiedades de un Hash Criptográfico

🔄
DETERMINISTA
El mismo mensaje siempre produce el mismo hash
🔒
IRREVERSIBLE
No debe ser factible determinar el mensaje original desde el hash
🛡️
RESISTENTE A COLISIONES
Difícil encontrar dos mensajes diferentes que produzcan el mismo hash

Algoritmos Disponibles

hlib.py
import hashlib

# Ver algoritmos disponibles en tu sistema
print(hashlib.algorithms_available)

# Algoritmos garantizados en todas las plataformas
print(hashlib.algorithms_guaranteed)
# {'sha512', 'sha3_256', 'shake_128', 'sha224', 'blake2b',
#  'shake_256', 'sha384', 'sha1', 'sha3_512', 'sha3_384',
#  'sha256', 'sha3_224', 'md5', 'blake2s'}

Creando un Hash con Blake2b

blake2b_example.py
import hashlib

# Crear objeto hash
h = hashlib.blake2b()

# Actualizar el mensaje en pasos
h.update(b"Hash me")
h.update(b" now!")

# Obtener representación hexadecimal
print(h.hexdigest())

# Propiedades del hash
print(f"Block size: {h.block_size}")   # 128 bytes
print(f"Digest size: {h.digest_size}") # 64 bytes
print(f"Name: {h.name}")               # blake2b
python blake2b_example.py
56441b566db9aafcf8cdad3a4729fa4b...
Block size: 128
Digest size: 64
Name: blake2b

Hash Personalizado con Parámetro Person

personalized_hash.py
import hashlib

# Hashes personalizados producen diferentes digests
h1 = hashlib.blake2b(
    b"Important data", 
    digest_size=16, 
    person=b"part-1"
)
h2 = hashlib.blake2b(
    b"Important data", 
    digest_size=16, 
    person=b"part-2"
)

print(h1.hexdigest())  # c06b9af95d5aa6307e7e3fd025a15646
print(h2.hexdigest())  # 9cb03be8f3114d0f06bddaedce2079c4

Hash Seguro de Contraseñas

Las funciones hash de propósito general como blake2b() o sha512() NO son adecuadas para almacenar contraseñas de forma segura. Son demasiado rápidas, lo que facilita ataques de fuerza bruta.
password_hash.py
import hashlib
import os

# Usar pbkdf2_hmac para contraseñas
# - Diseñado para ser lento (previene fuerza bruta)
# - Usa salt para proteger contra tablas rainbow

dk = hashlib.pbkdf2_hmac(
    "sha256",           # Algoritmo
    b"password123",     # Contraseña
    salt=os.urandom(16), # Salt aleatorio de 16 bytes
    iterations=200000   # Iteraciones (aumentar con el tiempo)
)

print(dk.hex())
El parámetro salt es crucial: es un dato aleatorio que inicializa la función hash, randomizando la salida y protegiendo contra ataques donde los hashes se comparan con tablas de hashes conocidos.
MÓDULO_02

HMAC

Este módulo implementa el algoritmo HMAC (Hash-based Message Authentication Code). Es un mecanismo ampliamente utilizado para autenticar mensajes y verificar que no han sido manipulados.

Flujo de Autenticación HMAC
Mensaje
+
Secret Key
MAC/Signature

Cálculo de MAC

hmc.py
import hmac
import hashlib

def calc_digest(key, message):
    key = bytes(key, "utf-8")
    message = bytes(message, "utf-8")
    dig = hmac.new(key, message, hashlib.sha256)
    return dig.hexdigest()

mac = calc_digest("secret-key", "Important Message")
print(mac)
La función hmac.new() toma una clave secreta, un mensaje, y el algoritmo hash a usar. La clave debe ser un objeto bytes o bytearray.
MÓDULO_03

Secrets

Este pequeño módulo (añadido en Python 3.6) maneja tres cosas: números aleatorios, tokens, y comparación de digests. Usa los generadores de números aleatorios más seguros del sistema operativo.

Objetos Aleatorios

secr_rand.py
import secrets

# Elegir elemento aleatorio de una secuencia
print(secrets.choice("Choose one of these words".split()))

# Entero aleatorio entre 0 y N
print(secrets.randbelow(10**6))

# Entero con N bits aleatorios
print(secrets.randbits(32))
python secr_rand.py
one
133025
1509555468

Generación de Tokens

secr_tokens.py
import secrets

# Token en bytes
print(secrets.token_bytes(16))

# Token en formato hexadecimal
print(secrets.token_hex(32))

# Token seguro para URLs
print(secrets.token_urlsafe(32))
python secr_tokens.py
b'\x0f\x8b\x8f\x0f\xe3\xceJ\xbc\x18\xf2\x1e\xe0i\xee1\x99'
98e80cddf6c371811318045672399b0950b8e3207d18b50d99d724d31d17f0a7
63eNkRalj8dgZqmkezjbEYoGddVcutgvwJthSLf5kho

Generador de Contraseñas Seguras

secr_gen.py
import secrets
from string import digits, ascii_letters

def generate_pwd(length=8):
    chars = digits + ascii_letters
    return "".join(secrets.choice(chars) for c in range(length))

def generate_secure_pwd(length=16, upper=3, digits=3):
    if length < upper + digits + 1:
        raise ValueError("Nice try!")
    while True:
        pwd = generate_pwd(length)
        if (
            any(c.islower() for c in pwd)
            and sum(c.isupper() for c in pwd) >= upper
            and sum(c.isdigit() for c in pwd) >= digits
        ):
            return pwd

print(generate_secure_pwd())  # mgQ3Hj57KjD1LI7M

URL de Reseteo de Contraseña

secr_reset.py
import secrets

def get_reset_pwd_url(token_length=16):
    token = secrets.token_urlsafe(token_length)
    return f"https://example.com/reset-pwd/{token}"

print(get_reset_pwd_url())
python secr_reset.py
https://example.com/reset-pwd/ML_6_2wxDpXmDJLHrDnrRA

Comparación Segura de Digests

El módulo secrets también proporciona compare_digest(a, b), diseñada para prevenir timing attacks. Estos ataques pueden inferir información sobre dónde dos digests empiezan a diferir según el tiempo que toma la comparación en fallar.

La función compare_digest() previene este ataque al eliminar la correlación entre tiempo y fallos. Este es un ejemplo brillante de cuán sofisticados pueden ser los métodos de ataque.
MÓDULO_04

JSON Web Tokens

JSON Web Token (JWT) es un estándar abierto basado en JSON para crear tokens que afirman un número de claims. Los JWTs son frecuentemente usados como tokens de autenticación, donde los claims típicamente son declaraciones sobre la identidad y permisos de un usuario autenticado.

Estructura de un JWT

Formato A.B.C
A: HEADER
Identifica el token y algoritmo
.
B: PAYLOAD
Claims y datos
.
C: SIGNATURE
Verifica la validez
La información que el token lleva siempre es visible (solo necesitas decodificar A y B desde Base64URL). La seguridad está en la parte C, que es una firma HMAC del header y payload.

Ejemplo Básico con PyJWT

jwt/tok.py
import jwt

data = {"payload": "data", "id": 123456789}
algs = ["HS256", "HS512"]

# Codificar el token
token = jwt.encode(data, "secret-key")

# Decodificar el token
data_out = jwt.decode(token, "secret-key", algorithms=algs)

print(token)
print(data_out)
python jwt/tok.py
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXlsb2FkIjoi...
{'payload': 'data', 'id': 123456789}

Claims Registrados

Claim Descripción
iss Emisor del token
sub Sujeto (información sobre la parte)
aud Audiencia del token
exp Tiempo de expiración
nbf No válido antes de (tiempo)
iat Tiempo en que fue emitido
jti ID del token

Claims de Tiempo

jwt/claims_time.py
from datetime import datetime, timedelta, UTC
from time import sleep, time
import jwt

iat = datetime.now(tz=UTC)
nfb = iat + timedelta(seconds=1)
exp = iat + timedelta(seconds=3)

data = {
    "payload": "data", 
    "nbf": nfb,  # No válido antes de 1 segundo
    "exp": exp,  # Expira en 3 segundos
    "iat": iat   # Emitido ahora
}

def decode(token, secret):
    try:
        print(jwt.decode(token, secret, algorithms=["HS256"]))
    except jwt.ImmatureSignatureError:
        print("Token no válido todavía (nbf)")
    except jwt.ExpiredSignatureError:
        print("Token expirado")

secret = "secret-key"
token = jwt.encode(data, secret)

decode(token, secret)  # Token no válido todavía
sleep(2)
decode(token, secret)  # Token válido
sleep(2)
decode(token, secret)  # Token expirado

Algoritmos Asimétricos (Clave Pública)

A veces, usar un secreto compartido no es la mejor opción. Es posible usar un par de claves asimétricas en lugar de HMAC para generar la firma JWT.

Criptografía de Clave Pública
Clave Privada
→ firma →
JWT
→ verifica →
Clave Pública
Generar par de claves RSA
# Usando ssh-keygen de OpenSSH
$ ssh-keygen -t rsa -m PEM
# Nombre: key (guardado en carpeta actual)
# Passphrase: vacío (Enter)
jwt/token_rsa.py
import jwt

data = {"payload": "data"}

def encode(data, priv_filename, algorithm="RS256"):
    with open(priv_filename, "rb") as key:
        private_key = key.read()
    return jwt.encode(data, private_key, algorithm=algorithm)

def decode(data, pub_filename, algorithm="RS256"):
    with open(pub_filename, "rb") as key:
        public_key = key.read()
    return jwt.decode(data, public_key, algorithms=[algorithm])

# Codificar con clave privada
token = encode(data, "jwt/rsa/key")

# Decodificar con clave pública
data_out = decode(token, "jwt/rsa/key.pub")
print(data_out)  # {'payload': 'data'}
Al manejar JWTs, asegúrate de investigar cómo hacerlo de forma segura: no aceptar tokens sin firmar, restringir la lista de algoritmos permitidos, y otras medidas de seguridad son muy importantes.

Resumen del Capítulo

01 // Hashlib

Funciones hash criptográficas para crear digests. Usa pbkdf2_hmac() para contraseñas con salt y múltiples iteraciones.

02 // HMAC

Autenticación de mensajes basada en hash. Combina mensaje + clave secreta para verificar integridad.

03 // Secrets

Generación segura de tokens y números aleatorios para aplicaciones criptográficas.

04 // JWT

Tokens para autenticación con claims. Soporta algoritmos simétricos (HMAC) y asimétricos (RSA).

Basado en "Learn Python Programming" 4th Edition // Fabrizio Romano & Heinrich Kruger

Capítulo 9: Cryptography and Tokens