Prevención de timing attacks en comparaciones criptográficas
Concepto
Un timing attack es un ataque donde un adversario mide el tiempo de ejecución de una operación para inferir información (p.ej. una contraseña o clave API).
El problema
Comparación ingenua (==)
if token == expected:
return True
return False
Riesgo: La comparación de strings en Python termina cuando encuentra el primer byte diferente:
- Token correcto: “abc123” vs “abc123” → 6 comparaciones byte a byte
- Token incorrecto byte 1: “xbc123” vs “abc123” → 1 comparación (mismatch en posición 0, termina)
- Token incorrecto byte 2: “axc123” vs “abc123” → 2 comparaciones (mismatch en posición 1, termina)
Ataque:
for i in range(256):
for j in range(256):
token = bytes([i, j] + [0] * 30) # Probar todos los valores
t_start = time.perf_counter()
response = request(token=token)
t_end = time.perf_counter()
if t_end - t_start > threshold:
print(f"Byte {i},{j} es correcto (tarda más)")
break
Midiendo el tiempo de respuesta, el atacante puede adivinar byte a byte qué es el token correcto.
La solución: hmac.compare_digest()
import hmac
if hmac.compare_digest(provided, expected):
return True
return False
Cómo funciona:
- Compara todos los bytes sin importar dónde esté la diferencia
- Tarda siempre el mismo tiempo (en el mejor de los casos)
- Implementado en C, resistente a optimizaciones del compilador
RFC 5234 (HMAC) + CWE-208 (Information Exposure Through Timing Discrepancy) recomiendan esto.
En CreaRack Pro
Antes de Etapa 3 (vulnerable)
core/workspace_api.py (_check_key):
def _check_key(request) -> bool:
if not WORKSPACE_API_KEY:
return False
return request.headers.get("X-Workspace-Key") == WORKSPACE_API_KEY # ⚠️ Timing attack
config/urls.py (_InternalToolsAuth.authenticate):
if token == expected: # ⚠️ Timing attack
return token
return None
Después de Etapa 3 (mitigado)
core/workspace_api.py:
import hmac
def _check_key(request) -> bool:
"""Validate workspace API key from header (constant-time compare)."""
if not WORKSPACE_API_KEY:
return False
provided = request.headers.get("X-Workspace-Key", "")
return hmac.compare_digest(provided, WORKSPACE_API_KEY) # ✅ Constant-time
config/urls.py:
import hmac as _hmac
class _InternalToolsAuth(HttpBearer):
def authenticate(self, request, token):
expected = (_os.environ.get("INTERNAL_TOOLS_TOKEN") or "").strip()
if not expected or len(expected) < 32:
return None
if _hmac.compare_digest(token or "", expected): # ✅ Constant-time
return token
return None
Impacto de seguridad
| Aspecto | Antes | Después |
|---|---|---|
| Exposición a timing attacks | ✅ Vulnerable | ✅ Mitigado |
| Tiempo de comparación | Variable (1-N bytes) | Constante |
| Overhead | Ninguno | Mínimo (C-level) |
| Compatibilidad | N/A | Python 3.3+ (stdlib) |
Cuándo usar hmac.compare_digest()
✅ Siempre que compares:
- API keys / tokens
- Passwords (junto con hashing, p.ej. bcrypt)
- HMAC tags
- Signatures
- Cualquier secreto de longitud fija
❌ No necesario para:
- Comparaciones de valores públicos (IDs, timestamps)
- Búsquedas de texto (donde el atacante no controla el input)
Limitaciones
-
Solo mitiga timing attacks
- No protege contra ataques de fuerza bruta (iterate 2^128 claves)
- La defensa complementaria es: tasa límite (rate limit), tiempos de espera exponencial
-
Asume tokens de igual longitud
hmac.compare_digest("abc", "abcdefgh") # Devuelve False rápido (longitud diferente)- Solución: Validar longitud antes
-
Depende del contexto
# ❌ MAL: Timing attack aún posible en la rama principal if not token: return None return hmac.compare_digest(token, expected) # ✅ BIEN: Mismo tiempo de cómputo provided = request.headers.get("X-Workspace-Key", "") return hmac.compare_digest(provided or "", expected)
En CreaRack, se rellena con default "" para evitar esta rama.
Referencias
- RFC 5234: HMAC-based Extract-and-Expand Key Derivation Function
- CWE-208: Observable Timing Discrepancy
- Python
hmac.compare_digest()docs - Auditoría Suprema Etapa 3
Véase también
- [[feature—config-ia—auditoria-suprema-etapa-3]]
- [[concept—security—key-management]]
- [[entity—core—service—anonymize-for-ai]]
- [[decision—20260611—agent-jwt-secret-dedicada]]
- [[concept—security—data-isolation]]