Volver a la wiki

Rotación de claves de cifrado de credenciales

Descripción

Soporte nativo para rotar la clave de cifrado de credenciales (CREDENTIAL_ENCRYPTION_KEY) sin que ninguna credencial almacenada deje de ser legible durante la transición.

Contexto: Las claves iniciales del hito E se generaron en una consola interactiva durante sesión 101, quedando registradas en transcripts y backups. Se consideran expuestas. Este cambio implementa el mecanismo para rotarlas por unas nuevas generadas de forma segura.

Mecanismo

Cadena de descifrado extendida

La clase CredentialManager pasa de una cadena binaria:

activa → SECRET_KEY (legacy) → DEV_KEY (rescue)

A una cadena que incluye la clave anterior:

activa → CREDENTIAL_ENCRYPTION_KEY_OLD → SECRET_KEY (legacy) → DEV_KEY (rescue)

Durante la rotación:

  1. Se establece CREDENTIAL_ENCRYPTION_KEY a la nueva clave (activa)
  2. Se establece CREDENTIAL_ENCRYPTION_KEY_OLD a la clave anterior
  3. Se ejecuta manage.py reencrypt_credentials — migra todo ciphertext a la clave nueva
  4. Se limpia CREDENTIAL_ENCRYPTION_KEY_OLD (vacío)
  5. A partir de ese momento, el ciphertext antiguo ya no es legible (migrado)

Implementación

Config (config/settings/base.py):

CREDENTIAL_ENCRYPTION_KEY_OLD = os.getenv("CREDENTIAL_ENCRYPTION_KEY_OLD", "")

Vacía si no hay rotación en curso.

Descifrado (core/security/credential_manager.py, método _decryption_chain):

_add(cls._active_key_material())
_add((getattr(settings, "CREDENTIAL_ENCRYPTION_KEY_OLD", "") or "").strip() or None)
_add(getattr(settings, "SECRET_KEY", None))
if include_dev_rescue:
    _add(cls.DEV_KEY)

Solo añade OLD si está definida y no vacía.

Variables de entorno propagadas en:

Test de rotación

tests/test_credential_key_rotation.py::test_rotation_via_old_key:

Impacto operativo

Notas de seguridad

Véase también

Subir