← Todos los artículos

Privacidad por diseño, no por retrofit

En una plataforma de salud no añadimos la seguridad al final. Moldeó la arquitectura desde el día uno — y ese orden de operaciones es todo el punto.

Hay un momento en muchos proyectos donde alguien dice “probablemente deberíamos cifrar eso”, y normalmente ya es demasiado tarde para que la respuesta sea buena. Trabajé en una plataforma de salud segura donde esa conversación ocurrió antes de la primera línea de código de backend — y la diferencia que eso hizo cambió por completo cómo pienso la seguridad.

El cifrado de base de datos es donde empieza el análisis, no donde termina

La postura por defecto — cifrar la base de datos y dar por terminado — protege los datos en reposo. Pero deja el texto plano expuesto a cualquier cosa que pueda llegar a la base de datos: bugs de aplicación, consultas demasiado amplias, herramientas internas. Para una plataforma que maneja información de salud altamente sensible, esa exposición residual era inaceptable.

Así que el diseño fue más lejos: múltiples categorías de datos personales se cifraron en la capa de aplicación, antes de llegar siquiera a la base de datos. Defensa en profundidad, más allá del límite de la base de datos. Incluso algo con acceso directo a la base de datos ve solo texto cifrado.

La decisión tiene un precio, y lo pagas en todas partes

El cifrado a nivel de aplicación no es una mejora gratuita. Lo difícil fue llevar el límite de seguridad por toda la interacción, no solo cifrar una columna de base de datos.

En el intake, la PWA Seeker cifraba la carga útil con una llave pública X25519 del proveedor. La API entregaba el sobre a un worker de BullMQ y respondía de inmediato, para que el descifrado y la persistencia ocurrieran de forma asíncrona. Después de autenticarse, el cliente derivaba una llave de cifrado de llaves con Argon2id y mantenía la llave privada descifrada del paciente en memoria volátil de ejecución — nunca en localStorage ni sessionStorage. La mensajería en tiempo real entre paciente y proveedor usaba el mismo intercambio asimétrico, mientras una llave maestra protegía los registros persistidos.

La configuración raíz también debía ser explícita: un mnemónico BIP39 inicializaba el par de llaves del proveedor, y el password maestro derivaba la KEK usada para proteger la llave privada del proveedor. El onboarding de cada tenant aprovisionaba aplicaciones aisladas para proveedores y pacientes, mientras la base de datos central retenía metadatos de despliegue y no PHI de pacientes.

El otro costo — y el que sorprende a la gente — es que pierdes la capacidad de consultar lo que cifraste. No puedes hacer WHERE email = ? sobre una columna cifrada. La respuesta fue el hashing determinístico SHA-256: aplicar hash al valor sensible al entrar, y como entradas iguales producen el mismo hash, puedes hacer búsquedas indexables por igualdad sin exponer nunca el texto plano. El trade-off honesto: el hashing determinístico filtra igualdad — valores idénticos producen hashes idénticos. Lo aceptamos donde las búsquedas eran genuinamente necesarias, con la exposición entendida.

La seguridad deja de ser un módulo

Una vez que te comprometes con esto, los requisitos de seguridad se propagan hacia lugares que no parecen “trabajo de seguridad”: contratos de API, el modelo de datos, los flujos de validación, las colas asíncronas, las reglas de logging (nada de datos sensibles en los logs — nunca), el acceso de mínimo privilegio y el ciclo de vida de las llaves en el cliente. La seguridad se convirtió en un motor de diseño de todo el backend y frontend, no en una checklist al final.

Participé en implementar y mantener estos mecanismos como parte del esfuerzo de ingeniería backend y full-stack, y el cambio duradero para mí es simple: la seguridad es un requisito arquitectónico, decidido desde el inicio — no una funcionalidad añadida al final. Retroadaptar privacidad en un sistema maduro es una crisis. Diseñarla desde el día uno es manejable.

También redefinió “terminado” para mí. En trabajo con datos sensibles, una funcionalidad no está terminada cuando funciona. Está terminada cuando funciona y los datos que toca están protegidos por defecto.

↑↓ navegar · abrir · esc cerrar