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."
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.
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.
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
Algoritmos Disponibles
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
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
Block size: 128
Digest size: 64
Name: blake2b
Hash Personalizado con Parámetro Person
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
blake2b() o sha512() NO son adecuadas para almacenar contraseñas de forma segura. Son demasiado rápidas, lo que facilita ataques de fuerza bruta.
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())
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.
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.
Cálculo de MAC
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)
hmac.new() toma una clave secreta, un mensaje, y el algoritmo hash a usar. La clave debe ser un objeto bytes o bytearray.
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
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))
133025
1509555468
Generación de Tokens
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))
98e80cddf6c371811318045672399b0950b8e3207d18b50d99d724d31d17f0a7
63eNkRalj8dgZqmkezjbEYoGddVcutgvwJthSLf5kho
Generador de Contraseñas Seguras
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
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())
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.
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.
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
Identifica el token y algoritmo
Claims y datos
Verifica la validez
Ejemplo Básico con PyJWT
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)
{'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
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.
# Usando ssh-keygen de OpenSSH $ ssh-keygen -t rsa -m PEM # Nombre: key (guardado en carpeta actual) # Passphrase: vacío (Enter)
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'}
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).