Saltar al contenido principal

Estrategia de Cifrado y Almacenamiento Sensible — Finnova

Finnova almacena datos financieros personales (saldos, movimientos, inversiones) que requieren protección en todas las capas del sistema. Este documento define qué se cifra, cómo, dónde se almacena y quién puede acceder.


1. Principios base

PrincipioDescripción
Cifrado por defectoTodo dato sensible se cifra en tránsito y en reposo, sin excepciones
Mínimo privilegioCada módulo accede únicamente a los datos que necesita
Separación de secretosClaves, tokens y credenciales nunca viven en el código fuente ni en la DB
Defense in depthCifrado a nivel de disco, de base de datos y de campo para datos críticos
Rotación periódicaLas claves criptográficas tienen ciclo de vida definido y se rotan

2. Mapa de datos por nivel de sensibilidad

NivelDatosAlmacenamientoCifrado requerido
CríticoTokens de acceso bancario, refresh tokens OAuth, secrets de integraciónVariables de entorno / AWS Secrets ManagerEn reposo (Secrets Manager) + en memoria solamente
AltoMovimientos bancarios, saldos, deudas, inversionesPostgreSQL — Finnova DBCifrado de disco (EBS) + cifrado de campo (columnas sensibles)
MedioNombre, email, teléfono, CURP, fecha de nacimientoPostgreSQL — Finnova DBCifrado de disco (EBS)
BajoLogs de auditoría, metadatos de dispositivo, contenido de cursosPostgreSQL / S3Cifrado de disco
No almacenarNúmeros de tarjeta, CVV, datos PANStripe los tokeniza; Finnova nunca los persiste

3. Cifrado en tránsito

3.1 Comunicación cliente ↔ servidor

  • Protocolo: TLS 1.2 mínimo, TLS 1.3 preferido
  • Terminación: En el HTTPS Listener del Load Balancer (AWS ALB)
  • Certificado: Gestionado vía AWS Certificate Manager (ACM) con renovación automática
  • HSTS: Habilitar Strict-Transport-Security en Nginx con max-age=31536000; includeSubDomains
  • Cipher suites: Deshabilitar suites débiles (RC4, 3DES, export ciphers); usar AES-GCM y ChaCha20-Poly1305

3.2 Comunicación interna (API ↔ DB)

  • La comunicación entre el API Server y PostgreSQL ocurre dentro de la misma VPC privada
  • Habilitar SSL en la conexión PostgreSQL (sslmode=require en el connection string)
  • La cadena de conexión se inyecta como variable de entorno, nunca hardcodeada

3.3 Diagrama de cifrado en tránsito


4. Cifrado en reposo

4.1 Disco — AWS EBS Encryption

Todos los volúmenes EBS donde corren PostgreSQL (principal y réplica) deben tener cifrado habilitado:

  • Algoritmo: AES-256
  • Gestión de claves: AWS KMS (Key Management Service) con CMK (Customer Managed Key)
  • Alcance: Volumen principal, volumen de réplica y snapshots de backup
  • Configuración: Habilitar al crear las instancias EC2; no se puede activar retroactivamente en un volumen existente (requiere snapshot + nuevo volumen)

Tarea pendiente: Verificar que los volúmenes EBS actuales tienen cifrado activado. Si no, planificar migración con downtime controlado.

4.2 Base de datos — Cifrado de campos sensibles

Para datos financieros de especial criticidad, aplicar cifrado a nivel de columna además del cifrado de disco. Esto protege contra accesos directos a la DB por parte de administradores no autorizados.

Columnas candidatas a cifrado de campo:

Tabla (referencial)ColumnaEstrategia
accountsbalance, account_numberAES-256-GCM, clave por usuario
transactionsamount, description, merchantAES-256-GCM, clave por usuario
investmentsportfolio_value, holdingsAES-256-GCM, clave por usuario
userscurp, phoneAES-256-GCM, clave global de aplicación

Implementación recomendada (Node.js / Express):

import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';

const ALGORITHM = 'aes-256-gcm';
const KEY = Buffer.from(process.env.FIELD_ENCRYPTION_KEY!, 'hex'); // 32 bytes

export function encrypt(plaintext: string): string {
const iv = randomBytes(12);
const cipher = createCipheriv(ALGORITHM, KEY, iv);
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
const authTag = cipher.getAuthTag();
// Formato: iv(12) + authTag(16) + ciphertext — todo en hex
return Buffer.concat([iv, authTag, encrypted]).toString('hex');
}

export function decrypt(ciphertext: string): string {
const buf = Buffer.from(ciphertext, 'hex');
const iv = buf.subarray(0, 12);
const authTag = buf.subarray(12, 28);
const encrypted = buf.subarray(28);
const decipher = createDecipheriv(ALGORITHM, KEY, iv);
decipher.setAuthTag(authTag);
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString('utf8');
}

AES-256-GCM provee cifrado autenticado: detecta si el ciphertext fue manipulado, lo que previene ataques de alteración de datos.

4.3 S3 — Contenido de cursos

  • Server-Side Encryption: SSE-S3 (AES-256 gestionado por AWS) activado por defecto en el bucket
  • Acceso: Bucket privado; acceso solo vía URLs prefirmadas (signed URLs) con expiración corta (ej. 15 min)
  • Bloqueo de acceso público: Habilitar "Block all public access" en la configuración del bucket

5. Gestión de secretos y claves

5.1 Qué se considera un secreto

  • Claves de cifrado de campo (FIELD_ENCRYPTION_KEY)
  • Cadenas de conexión a la base de datos
  • JWT secret / claves privadas RSA para firma de tokens
  • API keys de Stripe y otros terceros
  • Credenciales de AWS (si no se usa IAM Role)
  • Tokens OAuth de conexión bancaria (si aplica)

5.2 Regla fundamental

Ningún secreto debe estar en el código fuente, en archivos .env commiteados, ni en logs.

5.3 Gestión con AWS Secrets Manager

Configuración recomendada:

SecretoRotaciónAlmacenamiento
DB_CONNECTION_STRING30 díasAWS Secrets Manager
FIELD_ENCRYPTION_KEY90 días (con re-cifrado de datos)AWS Secrets Manager
JWT_SECRET30 díasAWS Secrets Manager
STRIPE_SECRET_KEYAl comprometerse / anualmenteAWS Secrets Manager
Variables no sensiblesN/AVariables de entorno en EC2

5.4 Variables de entorno en desarrollo local

Los desarrolladores usan un archivo .env.local (en .gitignore) obtenido de forma segura:

# .env.local — NUNCA committear este archivo
DB_CONNECTION_STRING=postgresql://...
FIELD_ENCRYPTION_KEY=<hex-32-bytes>
JWT_SECRET=<string-largo>
STRIPE_SECRET_KEY=sk_test_...

El repositorio incluye un .env.example con los nombres de las variables pero sin valores reales.


6. Autenticación y tokens de sesión

6.1 JWT — Configuración segura

ParámetroValor recomendadoRazón
AlgoritmoRS256 (asimétrico)La clave privada para firmar permanece solo en el servidor
Expiración access token15 minutosMinimiza ventana de uso si es robado
Expiración refresh token7 díasBalance entre UX y seguridad
Almacenamiento en appSecureStorage (Keychain/Keystore)Evita acceso por otras apps en el dispositivo
TransmisiónHeader Authorization: BearerNunca en query params ni body
RevocaciónLista negra de jti en Redis/DBPermite invalidar tokens antes de expirar

6.2 Almacenamiento de contraseñas

import * as argon2 from 'argon2';

// Hash al registrar
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 65536, // 64 MB
timeCost: 3,
parallelism: 1,
});

// Verificar al hacer login
const valid = await argon2.verify(hash, password);
  • Algoritmo: Argon2id (ganador de Password Hashing Competition, resistente a ataques GPU)
  • Alternativa aceptable: bcrypt con cost factor ≥ 12 (si Argon2 no está disponible)
  • Prohibido: MD5, SHA-1, SHA-256 sin salt para contraseñas

7. Almacenamiento en el dispositivo móvil (React Native)

DatoAlmacenamientoLibrería
JWT access tokenSecureStore (Expo) / Keychain (iOS) / Keystore (Android)expo-secure-store
JWT refresh tokenSecureStoreexpo-secure-store
Preferencias de UI no sensiblesAsyncStorage@react-native-async-storage/async-storage
Datos financieros cacheadosNo cachear en discoSiempre consultar la API

Regla: Ningún dato financiero (saldos, movimientos, inversiones) se persiste en el dispositivo. Solo vive en memoria durante la sesión activa. Al cerrar la app o expirar la sesión, se descarta.


8. Backups y recuperación

AspectoConfiguración
Frecuencia de backupDiario (automático via réplica PostgreSQL)
Retención30 días de backups rotatorios
Cifrado de backupsAES-256 (hereda cifrado del volumen EBS)
Prueba de restauraciónMensual — restaurar backup en entorno staging y verificar integridad
RTO objetivo< 4 horas
RPO objetivo< 24 horas (backup diario)

Los backups cifrados son inútiles si no se prueba la restauración. Establecer ritual mensual de drill.


9. Checklist de implementación

Antes del MVP

  • Verificar / habilitar cifrado EBS en volúmenes de producción
  • Migrar todos los secretos a AWS Secrets Manager (eliminar .env hardcodeados en servidor)
  • Configurar sslmode=require en conexión PostgreSQL
  • Implementar SecureStore para tokens en la app mobile
  • Habilitar SSE-S3 y bloqueo de acceso público en bucket de cursos
  • Configurar HSTS en Nginx

Post-MVP (primer mes)

  • Implementar cifrado de campo (AES-256-GCM) para columnas de datos financieros
  • Migrar firma de JWT de HS256 a RS256
  • Implementar mecanismo de revocación de refresh tokens
  • Configurar rotación automática de secretos en AWS Secrets Manager
  • Documentar procedimiento de rotación de FIELD_ENCRYPTION_KEY con re-cifrado de datos

Primer trimestre post-MVP

  • Establecer drill mensual de restauración de backups
  • Auditoría externa de configuración criptográfica
  • Implementar alertas de acceso inusual a datos sensibles (AWS CloudTrail + CloudWatch)

Referencias