05 ago 2025·8 min de lectura

Cifrado a nivel de campo: qué cifrar, claves y migraciones

El cifrado a nivel de campo protege campos sensibles sin sacrificar la usabilidad. Aprende qué cifrar, cómo gestionar claves y cómo migrar con seguridad.

Cifrado a nivel de campo: qué cifrar, claves y migraciones

Qué problema resuelve realmente el cifrado a nivel de campo

Si alguien obtiene una copia de tu base de datos, puede leer lo que esté almacenado en texto plano. Eso puede ocurrir por una copia de seguridad mal configurada, un portátil robado con un volcado de la base de datos, una herramienta administrativa expuesta o un bug que filtra datos. Cuando eso pasa, el daño no es solo “nos hackearon”; es “todos los registros de usuarios son legibles”.

El cifrado a nivel de campo reduce el radio de impacto. En lugar de depender únicamente de protecciones de disco o red, proteges valores específicos dentro de la base de datos para que sean ilegibles sin la clave correcta. Si un atacante copia filas de la base de datos, los campos cifrados aparecen como un galimatías.

Ayuda separar las capas:

  • TLS protege los datos mientras viajan entre tu app y la base de datos.
  • El cifrado de disco completo protege el dispositivo de almacenamiento cuando está desconectado.
  • El cifrado a nivel de campo protege columnas concretas incluso si se copia el contenido de la base de datos.

El coste es real: una vez que cifras un campo, la base de datos ya no puede buscar, ordenar o agrupar por él con facilidad. Informes, filtros y funciones de “encontrar usuario por X” pueden necesitar cambios. Muchos equipos mantienen un valor de búsqueda limitado (como un hash con clave del correo) para conservar las consultas comunes sin almacenar el valor subyacente en texto plano.

El cifrado a nivel de campo suele perseguir objetivos concretos: limitar lo que revela un volcado filtrado, cumplir expectativas de privacidad para datos sensibles, reducir daños posteriores (robo de identidad, estafas dirigidas) y bajar el riesgo de acceso interno, especialmente en bases de código compartidas o desordenadas.

Decidir qué cifrar (y qué no)

El cifrado a nivel de campo merece la pena solo para datos que causarían daño real si se exponían. Una definición sencilla de “sensible” es: si alguien obtiene una copia de tu base de datos, ¿qué les ayudaría a robar dinero, tomar control de cuentas, extorsionar usuarios o romper promesas de privacidad?

Empieza listando los pocos campos que son verdaderamente peligrosos en texto plano. Ejemplos comunes incluyen identificaciones gubernamentales (como SSNs), fecha de nacimiento, tokens de API, códigos de restablecimiento o recuperación, notas privadas y cualquier cosa que permita a un atacante actuar como un usuario o acceder a un servicio de terceros.

Muchos campos normalmente no necesitan cifrado porque son imprescindibles para el comportamiento básico y tienen bajo impacto por sí solos: IDs internos y claves foráneas, marcas de tiempo (created_at, last_login), booleanos y flags de estado, campos de perfil público (display name, bio) y metadatos no sensibles usados para ordenar y filtrar.

A continuación, decide quién debe leer el texto plano. Algunos campos deberían ser legibles solo por tu aplicación en tiempo de ejecución (por ejemplo, un token de API usado para llamar a un proveedor). Otros pueden necesitar acceso muy controlado por personal de soporte (un patrón de “ver últimos 4 dígitos”). Y algunos deberían ser legibles solo por el usuario, lo que a menudo implica cifrar en reposo y limitar dónde puede ocurrir el descifrado.

Comprométete a empezar pequeño. Cifrar “todo por si acaso” rompe búsquedas, informes e integraciones, y hace las migraciones más difíciles después.

Elegir un enfoque que encaje con las necesidades de tu app

No existe una única “mejor” forma de hacer cifrado a nivel de campo. La elección depende de lo que tu app necesite seguir haciendo con los datos una vez protegidos: búsquedas, exportaciones, herramientas de soporte y auditorías.

Si solo necesitas leer el valor más tarde (por ejemplo, mostrar al usuario su NIF guardado), usa cifrado aleatorio (no determinista). Es más seguro porque la misma entrada no produce el mismo texto cifrado cada vez. La desventaja es que no puedes hacer consultas de igualdad exacta sobre la columna cifrada.

Si necesitas búsquedas de igualdad (“buscar usuario por SSN” o “detectar cuentas bancarias duplicadas”), el cifrado determinista es tentador porque soporta búsquedas por igualdad. Pero filtra patrones (el mismo valor produce el mismo ciphertext). Una opción más segura en muchas apps es mantener el valor cifrado y almacenar un hash con clave separado para las búsquedas.

Usa cifrado autenticado, no “solo cifrado”. Sin autenticación, la app puede no detectar manipulaciones. Con un modo autenticado (a menudo llamado AEAD), la app puede saber si el ciphertext fue cambiado.

Para el manejo de claves, el envelope encryption suele ser el punto medio práctico. Cifras el campo con una data key, y luego envuelves esa data key con una master key. Puedes hacerlo por registro o por tenant. Las claves por tenant limitan el radio de impacto en apps multi-tenant y facilitan el offboarding.

Cuando no necesitas recuperar el texto plano, no cifres: haz hash. Las contraseñas son el caso clásico: almacena un hash de contraseña lento, no una contraseña cifrada.

La tokenización ayuda cuando partes de tu flujo no soportan ciphertext (herramientas legacy, dashboards de soporte, exportaciones a terceros). Reemplazas el valor sensible por un token y guardas el valor real en un almacén separado y restringido.

Una guía rápida para elegir:

  • Necesitas mostrar el valor más tarde: cifrado aleatorio con autenticación.
  • Necesitas búsqueda exacta: valor cifrado más un índice con hash con clave (o cifrado determinista con precaución).
  • Necesitas aislamiento por tenant: envelope encryption con claves envueltas por tenant.
  • Nunca necesitas texto plano: hashing.
  • Los flujos se rompen con ciphertext: tokenización.

Conceptos básicos de gestión de claves sin jerga

El cifrado a nivel de campo solo es tan seguro como tus claves. El objetivo es simple: tu app puede descifrar cuando realmente lo necesita, pero las claves están protegidas en otra parte, con reglas de acceso estrictas y buenos registros.

Un modelo mental útil es “trabajos separados”. Tu base de datos almacena ciphertext (datos bloqueados). Tu app solicita cifrar/descifrar cuando está autorizada. Un sistema de claves guarda las claves y decide quién puede usarlas.

Dónde deberían vivir las claves

Para la mayoría de equipos, la opción más segura por defecto es un servicio de claves gestionado, no una solución casera. Opciones comunes son un KMS en la nube, un servicio respaldado por HSM o un gestor de secretos que pueda proporcionar la clave en tiempo de ejecución.

Evita fallos comunes:

  • No almacenes claves de cifrado en la base de datos.
  • No comprometas claves en tu repositorio.
  • No mantengas claves en archivos de entorno compartidos que muchas personas o sistemas puedan leer.
  • No reutilices la misma clave en dev, staging y producción.

Un ejemplo de acceso práctico: si una herramienta de soporte puede ver perfiles de usuario enmascarados, no debería poder descifrar los valores completos. Solo la API principal que sirve a usuarios autenticados debería tener permiso de descifrado en producción.

Acceso y registros (para que puedas probar qué pasó)

El acceso a claves debe ser explícito y mínimo. Define qué servicios pueden descifrar qué campos, en qué entornos y bajo qué identidad (cuenta de servicio o rol). Si un job en background solo necesita cifrar al escribir, quizá no necesite permisos de descifrado.

Planifica auditorías desde el principio. Querrás registros de uso de claves que respondan “quién usó qué clave, cuándo y desde dónde”. Eso facilita las investigaciones y te ayuda a detectar errores como un servicio de pruebas llamando claves de producción.

Rotación y versionado de claves que necesitarás más adelante

El cifrado a nivel de campo no es “configúralo y olvídalo”. Planifica la rotación de claves desde el inicio, o acabarás atascado con claves arriesgadas que no puedes cambiar sin tiempo de inactividad.

Primero, decide la unidad de tus claves de cifrado. Una única clave para toda la app es lo más sencillo, pero amplifica cualquier incidente. Claves por tenant limitan el radio de impacto en SaaS. Claves por usuario pueden clarificar reglas de acceso, pero añaden complejidad cuando los usuarios comparten datos. Claves por registro son raras salvo que tengas una razón fuerte.

Sea lo que sea, almacena un identificador de clave con cada valor cifrado. Puede ser una etiqueta de versión corta (como v3) o un ID de clave. Lo importante es que el descifrado pueda mirar el ciphertext, ver qué versión de clave se usó y seleccionar la clave correcta sin adivinar.

Una configuración práctica de rotación suele tener dos capas:

  • Una data-encryption key (DEK) usada para cifrar campos.
  • Una master key (KEK) usada para envolver (cifrar) la DEK.

Con esta configuración puedes rotar la master key sin reescribir todos tus datos cifrados. Vuelves a envolver la DEK, lo cual es rápido.

A veces sí necesitas volver a cifrar los datos reales: si una DEK se expone, si cambias algoritmos o parámetros, o si una política requiere un alcance de clave distinto (por ejemplo, pasar de una clave app-wide a claves por tenant).

No te saltes backups y recuperación para las claves. Perder claves significa perder datos. Mantén copias cifradas de las claves, restringe el acceso y prueba las restauraciones con regularidad. Un modo de fallo común es “hicimos backup de la base de datos, pero no de las claves”.

Ejemplo: una startup rota de v1 a v2. Los nuevos escritos usan v2, las filas antiguas mantienen v1 y un job en background las re-encripta gradualmente.

Cómo añadir cifrado a nivel de campo paso a paso

Get ready for production
We’ll make your app production-ready with safer config, deployment checks, and clean environments.

Trátalo como un cambio en tu modelo de datos, no como un “parche de seguridad” rápido. Un despliegue cuidadoso evita que filtres texto plano en logs, exportaciones o scripts administrativos puntuales.

Empieza mapeando qué es realmente sensible y por dónde viaja. No mires solo la base de datos. Traza create, read, update, jobs en background, analytics y exportaciones. Un fallo común es cifrar una columna y olvidar el job que exporta CSV, que se convierte en la nueva fuga.

Elige una librería criptográfica probada para tu stack y un esquema de cifrado que puedas explicar a tu yo del futuro. Para la mayoría de apps, el cifrado autenticado es el valor por defecto correcto. Mantén las claves fuera de la base de datos y planifica el versionado desde el día uno.

Un despliegue que suele funcionar:

  • Añade nuevas columnas cifradas junto a las antiguas en texto plano y despliega ese cambio de esquema primero.
  • Añade un wrapper pequeño que haga encrypt-on-write y decrypt-on-read, y haz que el resto de la app llame solo a ese wrapper.
  • Deja de escribir en texto plano tan pronto como sea seguro, pero mantén las lecturas en texto plano brevemente durante la transición.
  • Backfill de las filas existentes en lotes, con monitorización, limitación de tasa y un plan de rollback.
  • Verifica con consultas y exportaciones reales, luego elimina los campos en texto plano en una migración posterior.

Durante el backfill evita imprimir valores descifrados en logs, reportes de errores o dashboards administrativos. Registra IDs de registro y contadores de estado en su lugar.

Mantener funciones funcionando: búsqueda, informes y rendimiento

El cifrado a nivel de campo protege valores sensibles, pero puede romper funciones diarias si no lo planeas. Antes de cifrar columnas, anota qué pantallas y jobs dependen de esos campos: cajas de búsqueda, tablas de admin, exportaciones y reportes programados.

Búsqueda y filtrado

Con cifrado aleatorio (no determinista), la misma entrada se cifra de forma distinta cada vez. Las búsquedas de igualdad y la deduplicación dejan de funcionar porque la base de datos no puede comparar ciphertext. Si necesitas búsqueda exacta (como encontrar un usuario por SSN), almacena un token de búsqueda separado junto al valor cifrado, como un hash con clave.

La búsqueda por coincidencia parcial (contains, starts-with) generalmente no puede soportarse de forma segura sobre texto cifrado sin sistemas especiales, por lo que la mayoría de equipos la elimina para campos sensibles.

Ordenar y consultas por rango también suelen romper. El ciphertext no tiene un orden significativo, así que no puedes ordenar por “salario” ni filtrar “fecha de nacimiento entre X y Y” directamente. Una solución común es almacenar un derivado grueso (como solo mes y año) o buckets precomputados.

Informes, indexado, caché y velocidad

Para analytics, planifica un dataset separado: agregados, contadores o una copia redactada que excluya campos sensibles.

Algunas reglas que funcionan en la práctica:

  • Indexa hashes o hashes con clave, no valores descifrados.
  • No caches datos descifrados en cachés compartidos ni en logs.
  • Descifra lo más tarde posible (justo antes de usarlo).
  • Mide los caminos críticos, porque descifrar en bucles cerrados puede añadir latencia.

Migraciones sin exponer texto plano

Fast turnaround, verified fixes
Most FixMyMess projects are completed in 48 to 72 hours after the audit and scope.

Asume que tu base de datos estará en un estado mixto durante un tiempo. Algunas filas tendrán texto plano, otras ciphertext y otras pueden usar distintas versiones de clave. Tu código debe manejar todos esos casos sin que nadie necesite un script puntual que vuelque texto plano en logs o archivos temporales.

Un patrón común es dual-read: cuando tu app carga un valor, intenta leer el campo cifrado primero. Si está vacío, recurre al campo legacy en texto plano. Esto mantiene los datos antiguos funcionando mientras migras en background.

Combínalo con dual-write: cuando la app guarda un valor, escribe la forma cifrada y, por un corto periodo de transición, mantiene el campo antiguo en texto plano actualizado. Esto evita que nuevos registros se creen en el formato antiguo mientras todavía estás cifrando filas viejas.

Para el backfill, ejecuta un job en background que cifre filas legacy en pequeños lotes. Trátalo como un sistema de producción: limita la tasa de actualizaciones, usa reintentos e idempotencia (seguro de ejecutar dos veces), registra progreso, espera fallos parciales y almacena la versión de clave junto al ciphertext.

Ejemplo: una tabla de signups tiene phone_plain y añades phone_enc más phone_key_version. Los nuevos signups escriben phone_enc. El job recorre usuarios antiguos, cifra phone_plain, establece la versión y deja el texto plano hasta verificar lecturas, exportaciones y herramientas de soporte.

Solo elimina el texto plano legacy después de un corte claro: métricas muestran cobertura cifrada casi al 100%, dual-read ha funcionado en producción durante suficiente tiempo y tienes un plan de rollback.

Errores comunes y trampas a evitar

El cifrado a nivel de campo es fácil de demostrar en el camino feliz. Los problemas aparecen más tarde: durante caídas, migraciones, exportaciones o flujos de soporte.

Las trampas que filtran datos (incluso si cifras)

La mayoría de las filtraciones no provienen de la base de datos. Vienen de todo lo que la rodea: logs, exportaciones, dashboards, endpoints de depuración y herramientas de terceros.

Modos de fallo comunes incluyen texto plano en logs (prints de depuración, volcado de requests, trazas de excepciones), claves hardcodeadas o claves del lado cliente (apps móviles, bundles de navegador, secretos comprometidos en git), falta de detección de manipulación (sin cifrado autenticado), salidas olvidadas (exportaciones CSV, recibos por email, payloads de webhooks, pantallas de admin) y restauraciones no probadas (hay backups, pero faltan claves o la versión de clave es desconocida).

Un ejemplo concreto: una app cifra SSNs, pero un error 500 registra el cuerpo completo de la petición para depuración. La base de datos está segura, pero los logs se convierten en una base de datos sombra en texto plano.

Cifrar los campos equivocados

Un error común es cifrar campos de los que depende la app para joins, comprobaciones de unicidad o flujos de soporte. Si cifras una dirección de correo, puedes romper búsquedas de login, deduplicación y “encontrar este cliente” en herramientas administrativas. Si aún necesitas comparaciones de igualdad, probablemente necesites un valor derivado separado (como un hash con clave) o un diseño distinto.

Antes de lanzar, haz un repaso rápido de “a dónde va este valor”: consultas de base de datos, logs, exportaciones, correos, analytics y herramientas de soporte.

Por último, trata la recuperación de claves como una funcionalidad. Datos cifrados sin claves recuperables es pérdida de datos permanente.

Lista de verificación rápida antes de lanzar

El cifrado a nivel de campo suele fallar por razones aburridas: un campo se copia en algún sitio o un job escribe texto plano “solo esta vez”. Antes del lanzamiento, haz una última pasada centrada en dónde pueden filtrarse los datos.

  • Mapea cada lugar donde el valor sensible puede aparecer: columnas de BD, logs de la app, eventos de analytics, reportes de errores, cachés, índices de búsqueda y backups.
  • Confirma que nunca se escribe texto plano “temporalmente”: sin logs de depuración, sin exportaciones a archivos y sin archivos temporales en disco.
  • Almacena información de versión con el ciphertext: una versión de clave (y, idealmente, una etiqueta de algoritmo/version) para poder descifrar registros antiguos tras cambios.
  • Prueba que puedes rotar claves sin tiempo de inactividad: lee datos antiguos, escribe datos nuevos con la nueva clave y luego re-encripta en background.
  • Aplica el principio de menor privilegio para el descifrado: solo el pequeño conjunto de servicios y roles que realmente necesitan texto plano deberían tener acceso para descifrar.

Verifica que tienes un procedimiento probado de backup y restauración tanto para datos como para claves, y que funciona bajo presión (nuevo entorno, nueva máquina, nueva persona ejecutándolo).

Ejemplo: cifrar unos pocos campos en una app real

Close common security holes
We find and fix SQL injection, exposed secrets, and unsafe patterns common in AI-built codebases.

Una pequeña startup guarda NIFs de clientes y notas internas de soporte en una tabla customers. La app empezó como prototipo y tiene una mala costumbre: cuando ocurre un error, registra el registro completo “para depuración”. Eso significa que NIFs y notas privadas pueden acabar en logs, dashboards o herramientas de error de terceros.

Eligen cifrado a nivel de campo para dos columnas: tax_id y support_notes. Todo lo demás queda en texto plano para que la app siga filtrando, ordenando e informando sin trabajo extra.

Para mantener el soporte ágil, añaden una columna separada como tax_id_hash (un hash unidireccional con clave). Soporte puede hacer búsquedas por igualdad (un cliente llama y lee su NIF), pero la base de datos nunca almacena ese ID en texto plano buscable. La app compara hasheando la entrada y buscando el hash coincidente.

Su plan de despliegue mantiene la app funcionando mientras los datos se convierten:

  • Añadir nuevas columnas cifradas (o nuevas versiones “_enc”) y un campo key_version.
  • Dual-write: guardar tanto el texto plano antiguo como el valor cifrado por un corto periodo.
  • Dual-read: preferir el valor cifrado; recurrir al texto plano si falta.
  • Backfill en lotes con alertas si falla el descifrado o un registro parece malformado.
  • Cuando la cobertura esté casi al 100%, dejar de escribir texto plano y luego eliminarlo en una migración posterior.

Tras el cambio, los logs de errores contienen marcadores redacted en lugar de secretos completos. El personal de soporte solo ve notas descifradas si su rol lo permite.

Próximos pasos si vas a actualizar una base de código existente

Actualizar una app existente es donde el cifrado a nivel de campo se complica. El objetivo es avanzar sin crear una ventana larga donde los datos sensibles estén expuestos, copiados en logs o escritos de vuelta en texto plano sin querer.

Empieza con un breve registro de decisiones que tu equipo pueda compartir. Guárdalo en una página: qué campos se cifran, por qué (legal, riesgo, confianza del cliente) y qué sistemas o roles pueden descifrar. Esto evita cambios aleatorios de “cifrar todo” que rompan funciones más tarde.

Comienza con un piloto pequeño que controles por completo: una tabla, un flujo de usuario, una ruta de migración. Cifra algo como SSN o número de cuenta bancaria en una sola tabla de clientes y actualiza solo las pantallas de “ver perfil” y “actualizar perfil”. Encontrarás rápido dónde se filtra texto plano (logs de depuración, exportaciones, trackers de errores) antes de escalar a más campos.

Añade guardarraíles antes del despliegue:

  • Deja de registrar secretos (una regla simple: nunca loggear cuerpos de petición).
  • Haz errores seguros (no mostrar stack traces ni valores descifrados en mensajes visibles para el usuario).
  • Revisa accesos (quién puede ejecutar exportaciones, quién puede consultar producción, qué va a analytics).
  • Añade tests que fallen si se almacena o devuelve texto plano.

Si heredaste una base de código generada por IA, haz un pase de seguridad enfocado antes de migrar. Estos proyectos suelen tener secretos expuestos, permisos demasiado amplios y logging “útil” que imprime todo.

Si necesitas ayuda externa para limpiar una app generada por IA antes de añadir cifrado, FixMyMess (fixmymess.ai) se centra en diagnosticar y reparar bases de código así, incluida la endurecimiento de seguridad y migraciones más seguras, para que no implementes cifrado sobre fugas existentes.

Preguntas Frecuentes

What does field-level encryption actually protect me from?

Protege valores concretos dentro de tus tablas para que un volcado de la base de datos copiado no revele esos campos en texto plano. Está pensado principalmente para escenarios de “alguien consiguió las filas”, no para detener ataques contra tu aplicación en vivo.

Which fields should I encrypt first?

Empieza con los campos que causarían daño real si se expusieran, como identificaciones gubernamentales, fecha de nacimiento, códigos de recuperación, tokens de API o notas privadas. Deja metadatos rutinarios (IDs, marcas de tiempo, flags de estado) sin cifrar para que la app pueda seguir consultando e informando con normalidad.

Should I use random (non-deterministic) encryption or deterministic encryption?

El cifrado aleatorio (no determinista) es la opción más segura por defecto cuando solo necesitas leer el valor más tarde, porque entradas idénticas no crean el mismo texto cifrado. El coste es que normalmente no puedes hacer búsquedas de igualdad sobre esa columna cifrada.

How can I still find a user by a sensitive value if it’s encrypted?

Mantén el valor sensible cifrado y añade un valor de búsqueda separado, como un hash con clave, para búsquedas de igualdad. Eso te permite buscar coincidencias sin almacenar el valor original en texto plano buscable.

Do I need authenticated encryption, or is encryption alone enough?

Usa cifrado autenticado (a menudo llamado AEAD) para que tu app pueda detectar si el texto cifrado fue modificado. Los enfoques “solo cifrar” pueden dejar pasar datos manipulados y provocar errores o problemas de seguridad al descifrar.

Where should encryption keys live?

Mantén las claves fuera de la base de datos y del repositorio, y prefiere un sistema de claves gestionado (como un KMS en la nube o un gestor de secretos) para controlar el acceso. Un buen valor por defecto es que solo el servicio principal en producción que realmente necesita texto plano pueda descifrar.

How do I handle key rotation without downtime?

Almacena un identificador de clave (una versión o ID de clave) junto a cada valor cifrado para que puedas descifrar datos antiguos tras los cambios. Un enfoque común es el cifrado por envoltura (envelope encryption) para poder rotar la clave maestra sin reescribir todos los campos cifrados.

What’s the safest way to migrate existing plaintext data to encrypted fields?

Trátalo como un cambio de modelo de datos: añade nuevas columnas cifradas, cambia a encriptar al escribir y rellena las filas antiguas en lotes. Durante la transición, haz que las lecturas prefieran el valor cifrado y solo recurran al texto plano cuando sea necesario; elimina el texto plano cuando hayas verificado exportaciones y herramientas.

What are the most common ways teams leak plaintext even after encrypting fields?

La mayoría de las filtraciones no vienen de la base de datos, sino de su entorno: logs, informes de errores, exportaciones, pantallas de administración y eventos de análisis. También evita impresiones de depuración “temporales” durante los backfills, porque eso puede crear una copia sombra de los datos sensibles.

What if my codebase is messy (or AI-generated) and I’m worried encryption will break things?

Si heredaste una base de código generada por IA o desordenada, arregla el registro de secretos y los controles de acceso antes de añadir cifrado, porque el cifrado no servirá si el texto plano ya fluye hacia logs y exportaciones. FixMyMess puede realizar una auditoría de código gratuita y luego reparar y reforzar la app para que los cambios de cifrado no se apoyen sobre fugas existentes, con la mayoría de las correcciones completadas en 48–72 horas.