El bytecode Python genera un COMPARE_OP == que se implementa como comparación byte a byte con salida anticipada (short-circuit) en el primer byte diferente. Un atacante puede medir el tiempo de respuesta de la API para determinar cuántos bytes del HMAC esperado coinciden con su intento, permitiendo recuperar la firma completa mediante un ataque de oracle de timing (O(N·256) en lugar de fuerza bruta O(256ⁿ)).
Este es exactamente el vector explotado en Lucky Thirteen (2013) y variantes modernas contra APIs REST que validan tokens con ==.
def verify_signature(payload: bytes, key: bytes, sig: str) -> bool: computed = hmac.new(key, payload, hashlib.sha256) expected = computed.hexdigest() return expected == sig # ← COMPARE_OP == # bytecode offset 94: COMPARE_OP == # Early-exit: tiempo varía según primer byte diferente
import hmac as _hmac # stdlib def verify_signature(payload: bytes, key: bytes, sig: str) -> bool: computed = _hmac.new(key, payload, hashlib.sha256) expected = computed.hexdigest() return _hmac.compare_digest(expected, sig) # compare_digest: tiempo constante O(n) siempre, # implementado en C (cpython Objects/bytesobject.c)
El token de API se compara con == sobre cadenas Python (str). El intérprete CPython aplica una optimización de identidad (is check) seguida de comparación byte a byte con short-circuit. Dado que los tokens tienen una longitud fija de 32 bytes hexadecimales, un atacante puede confirmar tokens parcialmente correctos y avanzar carácter a carácter, necesitando solo ~32 × 16 = 512 peticiones en lugar de 16³² ≈ 10³⁸.
def validate_token(stored: str, provided: str) -> bool: return stored == provided # str == en CPython: compara byte a byte # con salida anticipada en primer carácter distinto
import hmac as _hmac def validate_token(stored: str, provided: str) -> bool: return _hmac.compare_digest(stored, provided) # Acepta str o bytes; tiempo constante en ambos casos. # Requiere que ambas cadenas sean del mismo tipo.
Después de la comparación (resuelta en CVA-001), el flujo bifurca con POP_JUMP_IF_FALSE. Aunque corregir CVA-001 elimina la principal filtración, la rama condicional sobre el booleano resultante puede generar diferencias de tiempo a nivel de predicción de salto de CPU. En sistemas con alta resolución de temporizador (rdtsc accesible), es aconsejable que la función devuelva siempre el mismo número de instrucciones.
Estado: Advertencia informativa. Corregir CVA-001 es prioritario; este ítem requiere validación en producción con medición de latencia antes de actuar.
La generación de tokens utiliza os.urandom(32), que en Linux/macOS se resuelve en getrandom(2) con GRND_RANDOM, considerado criptográficamente seguro. No se detectaron instrucciones de tiempo variable ni uso de random.random(), time.time() u otras fuentes débiles.
def generate_token() -> str: return os.urandom(32).hex() # ✓ Correcto
expected == sig por hmac.compare_digest(expected, sig) en verify_signature()stored == provided por hmac.compare_digest(stored, provided) en validate_token()stored y provided sean ambos str o ambos bytes antes de la llamada.
os.urandom() — sin cambios requeridosuv run ct_analyzer/analyzer.py --func 'verify_signature|validate_token' vault_sign/crypto.py
.github/workflows/security.yml para prevenir regresiones futuras.
hmac.compare_digest() (stdlib, sin dependencias). Tiempo estimado de corrección: < 30 minutos. Prioridad: Inmediata antes del siguiente despliegue.