Cifrar una columna en MySQL puede reducir el impacto si una tabla, respaldo o exportación queda expuesta. También puede romper búsquedas, reportes, integraciones y procesos de soporte si se implementa como un cambio aislado.

La pregunta correcta no es solo “cómo cifro este campo”. La pregunta completa es: qué dato quieres proteger, quién necesita leerlo, dónde vivirán las llaves, cómo se restauran respaldos y qué consultas dejarán de funcionar igual.

Esta guía toma un ejemplo común, una columna con un identificador sensible, y lo convierte en un plan práctico para implementar cifrado reversible con menor riesgo.

Ilustración con iconos de bases de datos para explicar el cifrado de una columna en MySQL, llaves protegidas, auditoría y respaldos

Antes de empezar: cifrado, hash y enmascaramiento no son lo mismo

No todos los datos sensibles deben cifrarse de forma reversible.

  • Usa hashing cuando no necesitas recuperar el valor original, por ejemplo contraseñas o comparaciones de igualdad controladas.
  • Usa cifrado reversible cuando la aplicación sí necesita recuperar el dato completo.
  • Usa enmascaramiento cuando basta con mostrar una parte del dato, por ejemplo los últimos 4 caracteres.
  • Usa controles de acceso cuando el problema principal es quién puede ver o modificar la información.

Si eliges cifrado reversible para un dato que nunca debe recuperarse, aumentas el riesgo operativo sin ganar mucho. Si eliges hash para un dato que sí debe leerse, rompes el flujo de negocio.

1. Clasifica la columna que quieres proteger

Empieza por documentar qué contiene la columna y cómo se usa.

Preguntas útiles:

  • ¿es dato personal, token, secreto, identificador interno o dato financiero?;
  • ¿se usa para buscar, ordenar, filtrar o unir tablas?;
  • ¿lo consumen reportes, APIs, jobs o integraciones externas?;
  • ¿soporte necesita verlo completo o solo parcialmente?;
  • ¿qué pasa si el dato se pierde, se altera o no puede descifrarse?;
  • ¿qué regulación, contrato o política interna aplica?

Esta clasificación define el diseño. Cifrar una columna usada en búsquedas frecuentes no tiene el mismo impacto que cifrar un token que solo se recupera de forma puntual.

2. Define dónde se hará el cifrado

Tienes dos enfoques comunes.

Cifrado en la aplicación: la app cifra antes de enviar el dato a MySQL. Suele dar mejor separación porque la base no recibe texto claro, pero exige manejo cuidadoso de llaves, errores, rotación y compatibilidad entre servicios.

Cifrado dentro de MySQL: usas funciones como AES_ENCRYPT() y AES_DECRYPT(). Es más directo para migraciones o sistemas existentes, pero el dato y la llave pueden viajar hacia el servidor si no controlas la conexión, los permisos y los logs.

Para sistemas nuevos o datos de alto impacto, evalúa cifrado en la aplicación con bibliotecas modernas y autenticadas. Para bases existentes, MySQL puede ser una opción viable si documentas sus límites y operas las llaves correctamente.

3. Usa un tipo de dato binario

AES_ENCRYPT() devuelve datos binarios, no texto normal. Por eso la columna cifrada debe ser VARBINARY o BLOB, no VARCHAR ni TEXT.

Ejemplo de estructura para un identificador sensible:

CREATE TABLE empleados (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  nombre VARCHAR(120) NOT NULL,
  ssn_cipher VARBINARY(255) NOT NULL,
  ssn_iv VARBINARY(16) NOT NULL,
  ssn_salt VARBINARY(16) NOT NULL,
  ssn_last4 CHAR(4) NOT NULL,
  crypto_version TINYINT UNSIGNED NOT NULL DEFAULT 1,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

En este diseño:

  • ssn_cipher guarda el valor cifrado;
  • ssn_iv guarda el vector de inicialización usado en esa fila;
  • ssn_salt guarda la sal usada para derivación de llave;
  • ssn_last4 permite mostrar o buscar parcialmente sin descifrar toda la columna;
  • crypto_version ayuda si después cambias algoritmo, modo o estrategia de llaves.

No guardes la llave maestra en esta tabla.

4. Protege la llave fuera del repositorio

La llave no debe vivir en el código, en un archivo dentro del servidor web ni en el historial de Git.

Opciones razonables:

  • gestor de secretos;
  • KMS del proveedor cloud;
  • variable de entorno con permisos restringidos;
  • servicio interno que entregue material de llave a procesos autorizados;
  • separación de llaves por ambiente.

También define rotación. Si una llave se filtra, necesitas saber qué datos se recifran, en qué orden, con qué ventana y cómo verificas que el proceso terminó.

5. Ejemplo con AES_ENCRYPT() y derivación de llave

La documentación actual de MySQL recomienda usar una función de derivación de llave, como HKDF o PBKDF2, en lugar de pasar una frase simple directamente como llave. También recomienda almacenar resultados binarios en columnas binarias.

Ejemplo de inserción:

SET SESSION block_encryption_mode = 'aes-256-cbc';

SET @ssn = '123-45-6789';
SET @iv = RANDOM_BYTES(16);
SET @salt = RANDOM_BYTES(16);

INSERT INTO empleados (
  nombre,
  ssn_cipher,
  ssn_iv,
  ssn_salt,
  ssn_last4
) VALUES (
  'Juan Perez',
  AES_ENCRYPT(
    @ssn,
    @app_key_material,
    @iv,
    'hkdf',
    @salt,
    'empleados.ssn.v1'
  ),
  @iv,
  @salt,
  RIGHT(@ssn, 4)
);

@app_key_material representa material de llave entregado por tu aplicación o gestor de secretos. No lo escribas como literal fijo dentro de scripts versionados.

Para leer el dato:

SET SESSION block_encryption_mode = 'aes-256-cbc';

SELECT
  id,
  nombre,
  CONCAT('***-**-', ssn_last4) AS ssn_mascarado,
  CAST(
    AES_DECRYPT(
      ssn_cipher,
      @app_key_material,
      ssn_iv,
      'hkdf',
      ssn_salt,
      'empleados.ssn.v1'
    ) AS CHAR(20)
  ) AS ssn_completo
FROM empleados
WHERE id = 1;

En producción, evita consultar el valor completo por defecto. Devuélvelo solo en flujos autorizados, auditados y necesarios.

6. No dependas del valor por defecto sin revisarlo

MySQL controla el modo de cifrado de bloque con block_encryption_mode. No conviene depender de un valor por defecto sin documentarlo, porque el modo usado para cifrar y descifrar debe ser consistente.

Buenas prácticas:

  • fija el modo esperado en la sesión o configuración controlada;
  • guarda una versión de estrategia criptográfica en la fila o en la configuración;
  • prueba descifrado después de cambios de servidor, versión o conexión;
  • documenta cómo se comporta la replicación y el respaldo.

MySQL advierte que las sentencias con AES_ENCRYPT(), AES_DECRYPT() y RANDOM_BYTES() no son seguras para replicación basada en sentencias. Si usas replicación, revisa el formato y prueba el flujo completo.

7. Planea migración de datos existentes

Si la columna ya existe en texto claro, no ejecutes un UPDATE masivo directo en producción sin plan.

Checklist mínimo:

  • respaldo completo y restauración probada;
  • script ejecutado primero en una copia no productiva;
  • conteos antes y después;
  • validación por lotes;
  • manejo de valores nulos o inválidos;
  • tiempo estimado por tamaño de tabla;
  • monitoreo de bloqueos y rendimiento;
  • plan de rollback.

Para tablas grandes, migra por lotes y conserva temporalmente la columna original hasta verificar que la aplicación lee y escribe correctamente el nuevo formato.

8. Ajusta consultas, índices y reportes

Una columna cifrada no se comporta como una columna de texto común. No puedes esperar que filtros parciales, ordenamientos, búsquedas con LIKE o índices normales funcionen igual.

Opciones para reducir impacto:

  • guardar un valor enmascarado o parcial cuando el caso de uso lo permita;
  • guardar un hash separado para búsquedas exactas controladas;
  • rediseñar reportes para no depender del valor completo;
  • descifrar solo en consultas puntuales y autorizadas;
  • medir rendimiento con datos similares a producción.

No agregues una copia “temporal” en texto claro para resolver búsquedas. Esa copia suele quedarse y elimina buena parte del beneficio del cifrado.

9. Cuida logs, respaldos y exportaciones

El cifrado de una columna no protege todo el ciclo de vida del dato.

Revisa:

  • logs de aplicación y base de datos;
  • errores SQL que incluyan parámetros;
  • exports CSV o Excel;
  • respaldos y snapshots;
  • ambientes de prueba;
  • herramientas de BI;
  • accesos de soporte.

Si el dato descifrado aparece en logs o exportaciones, solo cambiaste de lugar la exposición.

10. Audita el acceso al dato descifrado

Registra eventos relevantes sin guardar el valor sensible:

  • usuario o servicio que solicitó descifrado;
  • fecha y origen;
  • operación realizada;
  • registro afectado;
  • motivo o flujo de negocio;
  • resultado de autorización.

La auditoría debe servir para investigar accesos indebidos, no para crear otra base de datos con información sensible.

Errores comunes al cifrar columnas en MySQL

  • Usar VARCHAR para guardar el resultado de AES_ENCRYPT().
  • Pasar una contraseña literal como llave en consultas versionadas.
  • Usar el mismo secreto en desarrollo y producción.
  • No guardar IV, sal o versión cuando el diseño los necesita.
  • Cifrar datos usados por reportes sin rediseñar esos reportes.
  • Registrar el valor descifrado en logs.
  • No probar restauración antes de migrar.
  • Confundir cifrado reversible con hashing de contraseñas.
  • Descifrar por defecto en listados administrativos.

Checklist antes de pasar a producción

Antes de activar el cambio, confirma:

  • el dato realmente requiere cifrado reversible;
  • la llave está fuera del repositorio y con permisos restringidos;
  • el tipo de columna es binario;
  • el modo de cifrado está documentado;
  • IV, sal y versión se guardan cuando aplica;
  • las consultas críticas siguen funcionando;
  • los respaldos se pueden restaurar;
  • la aplicación no escribe texto claro en logs;
  • soporte sabe qué puede ver y qué no;
  • existe plan de rotación de llaves.

Referencias útiles

Cifrar una columna puede reducir exposición, pero solo si forma parte de una estrategia completa: permisos, llaves, respaldos, pruebas, auditoría y monitoreo.