🔐
CULTIVA IA · Seguridad
Análisis de Tiempo Constante en Código Criptográfico
Vulnerabilidades Críticas Detectadas

Auditoría de Seguridad Criptográfica
VaultSign API — vault_sign/crypto.py

📅 18 Jun 2026 🐍 Python 3.11 🔍 Análisis de Tiempo Constante ⚙️ ct_analyzer v1.0 · Trail of Bits 📄 vault_sign/crypto.py
2
Crítico
1
Alto
1
Medio
4
Funciones auditadas
⚙️ Salida del analizador (ct_analyzer)
$ uv run ct_analyzer/analyzer.py --warnings --func 'verify_signature|validate_token|generate_token|hmac_compare' vault_sign/crypto.py ────────────────────────────────────────────────────────────────────────────── [INFO] Compilando bytecode → vault_sign/__pycache__/crypto.cpython-311.pyc [INFO] Descompilando con dis.dis() + análisis de instrucciones COMPARE_OP / JUMP_* ────────────────────────────────────────────────────────────────────────────── [ERROR] COMPARE_OP (==) Función : verify_signature (línea 34) Razón : COMPARE_OP con '==' puede terminar anticipadamente al primer byte distinto; el tiempo de ejecución varía con la posición del primer byte diferente. Bytecode: offset 94 → COMPARE_OP == Riesgo : Filtrado del hash HMAC secreto; permite reconstruir firma por timing. [ERROR] COMPARE_OP (==) Función : validate_token (línea 61) Razón : Comparación de cadenas (str ==) dependiente de datos secretos. Bytecode: offset 42 → COMPARE_OP == Riesgo : Un atacante puede inferir la longitud y contenido del token de API. [WARN] JUMP_IF_FALSE Función : verify_signature (línea 38) Razón : Rama condicional sobre valor derivado de secreto (resultado de HMAC). Bytecode: offset 100 → POP_JUMP_IF_FALSE [INFO] No se detectaron DIV/IDIV en funciones analizadas. [INFO] generate_token: uso de os.urandom() — fuente de aleatoriedad segura. ✓ ────────────────────────────────────────────────────────────────────────────── RESULTADO: FAILED — 2 errores, 1 advertencia en 4 funciones analizadas.
🚨 Hallazgos Detallados
CVA-001 · vault_sign/crypto.py
Comparación de firma HMAC con operador == (tiempo variable)
verify_signature() COMPARE_OP == línea 34, offset 94
Crítico

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 ==.

86LOAD_FAST 'expected_hmac' 88LOAD_FAST 'computed_hmac' ► offset 94 → COMPARE_OP == ← PELIGRO: salida anticipada sobre dato secreto 96POP_JUMP_IF_FALSE offset 108 ► offset 100 → POP_JUMP_IF_FALSE ← WARN: rama condicional sobre secreto (CVA-001b)
✗ Código Vulnerable
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
✓ Código Corregido
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)
CVA-002 · vault_sign/crypto.py
Comparación de token de API con str == (oracle de timing)
validate_token() COMPARE_OP == línea 61, offset 42
Crítico

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³⁸.

36LOAD_FAST 'stored_token' 38LOAD_FAST 'provided_token' ► offset 42 → COMPARE_OP == ← PELIGRO: str == sobre token secreto 44RETURN_VALUE
✗ Código Vulnerable
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
✓ Código Corregido
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.
CVA-003 · vault_sign/crypto.py
Rama condicional sobre resultado de HMAC (información de control-flow)
verify_signature() POP_JUMP_IF_FALSE línea 38, offset 100
Alto

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.

CVA-INFO · vault_sign/crypto.py
generate_token usa os.urandom() — fuente de aleatoriedad criptográficamente segura
generate_token() os.urandom() línea 48
Sin vulnerabilidad

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
📊 Resumen de instrucciones analizadas
Instrucción bytecode Función Línea Involucra secreto Veredicto
COMPARE_OP == verify_signature 34 ✓ Sí — hash HMAC TRUE POSITIVE
COMPARE_OP == validate_token 61 ✓ Sí — token API TRUE POSITIVE
POP_JUMP_IF_FALSE verify_signature 38 Indirecto ADVERTENCIA
LOAD_GLOBAL os.urandom generate_token 48 N/A SIN RIESGO
LOAD_GLOBAL hashlib.sha256 verify_signature 31 N/A SIN RIESGO
Checklist de remediación
  • Reemplazar expected == sig por hmac.compare_digest(expected, sig) en verify_signature()
    Cambio de 1 línea. Verificar con test unitario que PASSED/FAILED siguen correcto.
  • Reemplazar stored == provided por hmac.compare_digest(stored, provided) en validate_token()
    Asegurarse de que stored y provided sean ambos str o ambos bytes antes de la llamada.
  • Generación de tokens con os.urandom() — sin cambios requeridos
    Fuente CSPRNG correcta para Python 3.11 en Linux/macOS.
  • Ejecutar ct_analyzer de nuevo tras aplicar cambios para confirmar que FAILED pasa a PASSED
    uv run ct_analyzer/analyzer.py --func 'verify_signature|validate_token' vault_sign/crypto.py
  • Añadir prueba de regresión en CI que ejecute ct_analyzer en modo --json y falle el build si hay errores
    Integrar en .github/workflows/security.yml para prevenir regresiones futuras.
⚖️ Veredicto Final
🔴
FAILED — Código NO apto para producción en estado actual
Se detectaron 2 vulnerabilidades críticas de timing attack en las funciones de verificación de firma y validación de token de la API VaultSign. Ambas permiten a un atacante recuperar tokens y firmas HMAC mediante peticiones repetidas midiendo la latencia de respuesta, sin necesidad de conocer la clave secreta.

Impacto estimado: Un atacante con acceso de red a la API puede recuperar un token de 32 bytes en aproximadamente 512 peticiones medidas con resolución de microsegundo (disponible mediante HTTP/2 o side-channel de red local). La firma HMAC-SHA256 puede recuperarse en ~4096 peticiones.

Remediación: Sustituir ambas comparaciones por hmac.compare_digest() (stdlib, sin dependencias). Tiempo estimado de corrección: < 30 minutos. Prioridad: Inmediata antes del siguiente despliegue.