Back to flin
flin

Cifrado de base de datos y configuración

Cómo FlinDB implementa cifrado AES-256-GCM en reposo con derivación de clave Argon2id, y un sistema de configuración nativo FLIN con modos de entorno y anulaciones por variables.

Thales & Claude | March 30, 2026 10 min flin
EN/ FR/ ES
flinrust

La Sesión 171 completó dos funcionalidades que cierran la brecha entre "funciona en mi portátil" y "listo para producción": cifrado de base de datos y gestión de configuración. El cifrado asegura que los datos en reposo sean ilegibles sin la contraseña correcta -- crítico para aplicaciones que manejan información sensible. La gestión de configuración asegura que la misma aplicación FLIN se comporte correctamente en entornos de desarrollo, prueba y producción.

Treinta y nueve pruebas. Seiscientas líneas de código de configuración. Trescientas líneas de código de cifrado. Tres nuevas dependencias de Rust. Al final de la Sesión 171, FlinDB estaba al 97% completo.

Parte 1: Cifrado de respaldos

Los requisitos de seguridad

Un archivo de respaldo sin cifrar es un pasivo. Si alguien obtiene acceso al archivo -- a través de un bucket S3 mal configurado, un portátil robado o un servidor comprometido -- puede leer cada registro de la base de datos. Para aplicaciones que manejan datos personales (salud, finanzas, educación), esto es una violación de cumplimiento.

El cifrado de FlinDB usa algoritmos estándar de la industria con parámetros recomendados por OWASP:

ParámetroValorRazón
AlgoritmoAES-256-GCMCifrado autenticado, estándar de la industria
Derivación de claveArgon2idResistente a memoria, resistente a GPU, ganador de PHC
Costo de memoria64 MiBMínimo recomendado por OWASP
Costo de tiempo3 iteracionesEquilibrio entre seguridad y velocidad
Paralelismo4 hilosUtilizar CPUs multi-core
Tamaño de nonce12 bytesEstándar AES-GCM

AES-256-GCM

AES-256-GCM (Advanced Encryption Standard con clave de 256 bits en modo Galois/Counter) proporciona tanto confidencialidad como integridad. La parte "GCM" es crítica -- produce una etiqueta de autenticación junto con el texto cifrado. Si incluso un bit de los datos cifrados se modifica (accidental o maliciosamente), el descifrado falla. Esto previene tanto corrupción como manipulación.

Derivación de clave Argon2id

Una contraseña no es una clave criptográfica. "my-secure-password" es 19 bytes de texto de baja entropía. AES-256 necesita exactamente 32 bytes de material de clave de alta entropía. Las funciones de derivación de clave (KDFs) cierran esta brecha.

Elegimos Argon2id porque es el ganador del Password Hashing Competition y el KDF recomendado por OWASP para 2024+. A diferencia de PBKDF2 (que puede paralelizarse en GPUs) o bcrypt (que tiene un requisito de memoria fijo), Argon2id requiere memoria significativa (64 MiB por derivación), haciendo que los ataques de fuerza bruta sean costosos incluso en hardware especializado.

La API de cifrado

Crear un respaldo cifrado:

rustlet options = BackupOptions::encrypted("my-secure-password");
Backup::full(&db, "backup.flindb.bak", options)?;

Restaurar un respaldo cifrado:

rustlet options = RestoreOptions::with_password("my-secure-password");
let db = Backup::restore("backup.flindb.bak", options)?;

Verificar si un respaldo está cifrado:

rustlet is_encrypted = Backup::is_encrypted("backup.flindb.bak")?;

El flujo de cifrado

El proceso de cifrado tiene cinco pasos:

  1. Serializar: Convertir el estado de la base de datos a JSON
  2. Comprimir: Aplicar compresión Zstd (opcional, habilitada por defecto)
  3. Derivar clave: Usar Argon2id para derivar una clave de 256 bits de la contraseña
  4. Generar nonce: Crear un nonce aleatorio de 12 bytes (único por respaldo)
  5. Cifrar: Aplicar AES-256-GCM con la clave derivada y el nonce

El descifrado invierte el proceso:

  1. Leer metadatos: Extraer la sal y el nonce del encabezado del respaldo
  2. Derivar clave: Usar Argon2id con la misma sal para derivar la misma clave
  3. Descifrar: Aplicar descifrado AES-256-GCM
  4. Descomprimir: Aplicar descompresión Zstd (si está comprimido)
  5. Deserializar: Parsear el JSON de vuelta al estado de la base de datos

La sal se almacena junto a los datos cifrados en el archivo de respaldo. El nonce también se almacena. Ninguno es secreto -- existen para asegurar que la misma contraseña produzca claves diferentes para diferentes respaldos (sal) y que la misma clave produzca texto cifrado diferente para diferentes cifrados (nonce).

Detección de contraseña incorrecta

Si se proporciona la contraseña incorrecta, la derivación de clave produce una clave diferente, y el descifrado AES-GCM falla inmediatamente (la etiqueta de autenticación no coincide). El error es claro: "Decryption failed: invalid password or corrupted backup." No hay descifrado parcial. No hay salida ilegible. La etiqueta de autenticación garantiza todo o nada.

Las dependencias

Se agregaron tres crates nuevos a Cargo.toml:

tomlaes-gcm = "0.10"    # AES-256-GCM encryption
argon2 = "0.5"       # Argon2id key derivation
rand = "0.8"         # Random nonce generation

Estas son implementaciones puras en Rust sin bindings de C. Compilan en cada plataforma que Rust soporta, incluyendo WebAssembly (para cifrado futuro del lado del navegador).

Parte 2: Sistema de configuración

El problema

La promesa de configuración cero de FlinDB funciona para desarrollo. Pero las aplicaciones de producción necesitan ajustar el comportamiento: umbrales del WAL, niveles de logging, rutas de base de datos, programaciones de respaldo. Y los entornos de prueba necesitan un comportamiento completamente diferente -- bases de datos en memoria, logging detallado, reinicio entre pruebas.

El formato de configuración FLIN

La configuración de FlinDB usa la propia sintaxis de FLIN -- no JSON, no YAML, no TOML:

flin// flin.config
app {
    name: "MyApp"
    mode: "prod"
}

database {
    path: "./.flindb"
    wal: true
    compaction_interval: "1h"
}

backup {
    enabled: true
    interval: "1h"
    retention: 24
}

ai {
    embeddings: "local"
}

Esta fue una elección deliberada. Los desarrolladores FLIN ya conocen la sintaxis FLIN. Requerir que aprendan TOML o YAML para configuración agrega carga cognitiva innecesaria. El parser de configuración es una versión simplificada del propio parser de FLIN, manejando valores de cadena, números, booleanos y bloques anidados.

Modos de entorno

Tres modos con diferentes valores predeterminados:

rustpub enum AppMode {
    Dev,
    Test,
    Prod,
}
ConfiguraciónDevTestProd
Ruta de base de datos.flindb/:memory:.flindb/
Nivel de logDebugWarnWarn
WAL habilitadotruefalsetrue
Auto-recargatruefalsefalse

El modo Test es el más interesante. La base de datos es completamente en memoria -- sin E/S de disco, sin limpieza de archivos entre pruebas, sin problemas de aislamiento de pruebas. Cuando el proceso de prueba termina, la base de datos se desvanece. Esto hace que la suite de pruebas de FLIN sea rápida y determinista.

Parseo de duraciones

Los valores de configuración que representan duraciones de tiempo soportan formatos legibles para humanos:

rustfn parse_duration(s: &str) -> Option<Duration> {
    if s.ends_with("ms") {
        s[..s.len()-2].parse::<u64>().ok().map(Duration::from_millis)
    } else if s.ends_with("s") {
        s[..s.len()-1].parse::<u64>().ok().map(Duration::from_secs)
    } else if s.ends_with("m") {
        s[..s.len()-1].parse::<u64>().ok().map(|m| Duration::from_secs(m * 60))
    } else if s.ends_with("h") {
        s[..s.len()-1].parse::<u64>().ok().map(|h| Duration::from_secs(h * 3600))
    } else if s.ends_with("d") {
        s[..s.len()-1].parse::<u64>().ok().map(|d| Duration::from_secs(d * 86400))
    } else if s.ends_with("w") {
        s[..s.len()-1].parse::<u64>().ok().map(|w| Duration::from_secs(w * 604800))
    } else {
        None
    }
}

Así que "1h" significa una hora, "30m" significa treinta minutos, "5000ms" significa cinco segundos, y "1w" significa una semana. Se acabó contar segundos ("¿3600 es una hora o un día?").

Anulaciones por variables de entorno

Cada valor de configuración puede anularse con una variable de entorno:

Variable de entornoEquivalente de configuración
FLIN_MODEapp.mode
FLIN_DB_PATHdatabase.path
FLIN_DB_WALdatabase.wal
FLIN_LOG_LEVELapp.log_level

Las variables de entorno tienen precedencia sobre el archivo de configuración. Esto sigue el principio de la twelve-factor app: la configuración que varía entre despliegues (dev vs staging vs producción) debería venir del entorno.

Descubrimiento del archivo de configuración

El sistema de configuración busca archivos de configuración en tres ubicaciones, en orden:

  1. flin.config en la raíz del proyecto
  2. ~/.flin/config en el directorio home del usuario
  3. /etc/flin/config para valores predeterminados a nivel de sistema

Se usa el primer archivo encontrado. Esto soporta tanto configuración específica del proyecto (lo más común) como valores predeterminados a nivel de usuario o sistema (útil para entornos CI/CD donde cada proyecto debería usar la misma ruta de base de datos).

La estructura Config

rustpub struct FlinConfig {
    pub app_name: String,
    pub mode: AppMode,
    pub log_level: LogLevel,
    pub database: DatabaseConfig,
    pub backup: BackupConfig,
    pub ai: AiConfig,
}

pub struct DatabaseConfig {
    pub path: String,
    pub wal: bool,
    pub compaction_interval: Duration,
}

pub struct BackupConfig {
    pub enabled: bool,
    pub interval: Duration,
    pub retention: usize,
    pub compression: bool,
}

La estructura está completamente tipada. Sin hashmaps con claves de cadena. Sin acceso dinámico. El compilador verifica que cada valor de configuración se use correctamente.

Las treinta y nueve pruebas

La suite de pruebas de la Sesión 171 fue completa:

Pruebas de configuración (29): - Valores predeterminados para todas las configuraciones - Parseo de cada sección de configuración (app, database, backup, ai) - Valores predeterminados de los modos Dev/Test/Prod - Conversión de cadenas de AppMode - Ordenamiento y comparación de LogLevel - Parseo de duraciones para todas las unidades (ms, s, m, h, d, w) - Anulaciones por variables de entorno (modo, ruta, WAL, nivel de log) - Validación de tipos de valores de configuración - Descubrimiento de configuración sin archivo presente

Pruebas de cifrado (10): - Ciclo completo cifrar/descifrar - Rechazo de contraseña incorrecta - Cifrado de datos vacíos - Cifrado de datos grandes (prueba de estrés) - Ciclo completo de respaldo con cifrado - Contraseña incorrecta en respaldo cifrado - Sin contraseña en respaldo cifrado (error) - Respaldo cifrado + comprimido - Detección de respaldo no cifrado - Serialización de metadatos de cifrado

La prueba de ciclo completo de cifrado es la más crítica -- verifica que los datos cifrados con una contraseña pueden descifrarse con la misma contraseña, produciendo una salida idéntica. La prueba de contraseña incorrecta es igualmente importante -- verifica que el descifrado con la contraseña incorrecta falla limpiamente en lugar de producir basura.

FlinDB al 97%

Después de la Sesión 171, el estado de implementación de FlinDB era:

CategoríaCompletitud
DB-1 a DB-4 (Core)100%
DB-5 (Búsqueda semántica)70%
DB-6 (Motor de almacenamiento)100%
DB-7 (Índices)67%
DB-8 (Configuración)75%
DB-9 (Pruebas de integración)100%
DB-10 (Tiempo real)88%
DB-11 (Consultas de intención)100%
DB-12 (EAVT)100%
DB-15 (Respaldo)100%
General97%

El 3% restante era principalmente modelos de embedding reales (reemplazando embeddings mock con multilingual-e5-small real), tipos de índice avanzados y las opciones de configuración restantes. El motor de base de datos principal -- CRUD, restricciones, consultas, transacciones, respaldo, cifrado, event sourcing y configuración -- estaba completo.

Conteo total de pruebas: 2.404 (1.787 de biblioteca + 617 de integración).

La brecha entre "proyecto interesante" y "base de datos de producción" se mide en algoritmos de cifrado, gestión de configuración y miles de pruebas. La Sesión 171 cerró esa brecha.


Esta es la Parte 11 de la serie "Cómo construimos FlinDB", documentando cómo construimos un motor de base de datos embebido completo para el lenguaje de programación FLIN.

Navegación de la serie: - [064] Graph Queries and Semantic Search - [065] The EAVT Storage Model - [066] Database Encryption and Configuration (estás aquí) - [067] Tree Traversal and Integration Testing - [068] FlinDB Hardening for Production

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles