Mejores prácticas para proteger SQLite

Fri Nov 21 2025

Esta guía es una lista de comprobación concisa y práctica para reforzar SQLite en aplicaciones y servicios. Se centra en pasos del mundo real que puedes aplicar hoy, con fragmentos listos para copiar y pegar.

Lista de comprobación TL;DR

  • Cifra los datos en reposo con SQLCipher (o SEE); nunca almacenes bases de datos en texto plano
  • Mantén las claves en un secrets manager; rótalas y evita codificarlas directamente
  • Permisos exclusivos para el propietario en los archivos de DB/journal/WAL (chmod 600), y directorios privados
  • Usa consultas parametrizadas para evitar la inyección SQL
  • Prefiere journal_mode=WAL, synchronous=FULL, foreign_keys=ON
  • Considera secure_delete=ON para eliminaciones sensibles; usa temp_store=MEMORY para reducir rastros en disco
  • Desactiva la carga de extensiones; usa conexiones de solo lectura/query-only cuando sea posible
  • Realiza copias de seguridad de forma segura (cifradas), prueba las restauraciones y elimina las copias en texto plano
  • Ejecuta la aplicación como un usuario que no sea root; restringe el acceso al sistema de archivos

1) Cifra la base de datos en reposo (SQLCipher)

SQLite por sí mismo no tiene cifrado integrado. Usa SQLCipher (de código abierto) o SEE (comercial) para cifrar toda la base de datos, incluidas las páginas, WAL y journals.

Instalación (ejemplo en macOS):

brew install sqlcipher

Inicializa o abre una DB cifrada:

PRAGMA key = 'your strong passphrase here';
-- Optional: set KDF iteration count and cipher settings per SQLCipher docs
-- PRAGMA kdf_iter = 256000;
-- PRAGMA cipher_page_size = 4096;

Ejemplo en Python:

# pip install pysqlcipher3
import sqlite3
from pysqlcipher3 import dbapi2 as sqlcipher

conn = sqlcipher.connect("app.db")
cur = conn.cursor()
cur.execute("PRAGMA key = ?;", ("env_or_secret_manager_key",))
cur.execute("CREATE TABLE IF NOT EXISTS notes(id INTEGER PRIMARY KEY, body TEXT);")
conn.commit()

Consejos sobre las claves:

  • La frase de contraseña debe proceder de un secrets manager o del almacén de claves del sistema operativo (no del código)
  • Rota las claves con PRAGMA rekey de SQLCipher durante una ventana de mantenimiento

2) Gestiona las claves de forma segura

  • Almacena las claves en un secrets manager (AWS Secrets Manager, GCP Secret Manager, Vault)
  • Inyéctalas mediante variables de entorno o archivos seguros cuyo propietario sea el usuario de la aplicación
  • Rótalas según un calendario y cuando el personal deje la organización; supervisa el uso indebido de las claves

.env (solo desarrollo):

SQLCIPHER_KEY="change-me"

3) Restringe los permisos y la ubicación de los archivos

SQLite escribe varios archivos: el .db principal, -wal y -shm cuando WAL está habilitado. Todos deben estar protegidos.

# Run your app as its own user; restrict ownership and permissions
chown appuser:appuser app.db*
chmod 600 app.db*
umask 077

Recomendaciones:

  • Coloca los archivos de DB en un directorio privado y no compartido
  • Evita rutas legibles por todo el mundo (por ejemplo, /tmp o volúmenes compartidos)
  • En dispositivos móviles, desactiva las copias de seguridad del sistema para los datos de la aplicación si la DB es sensible

4) Evita la inyección SQL con consultas parametrizadas

Nunca concatenes la entrada del usuario en SQL. Usa parámetros en cada consulta.

Python:

cur.execute("SELECT * FROM users WHERE email = ?", (email,))

Node (better-sqlite3):

db.prepare("INSERT INTO notes (body) VALUES (?)").run(body);

5) Valores predeterminados de PRAGMA más seguros para la seguridad y la durabilidad

Configura estos valores después de abrir cada conexión (o mediante los hooks de tu ORM/driver):

PRAGMA foreign_keys = ON;      -- enforce referential integrity
PRAGMA journal_mode = WAL;     -- better durability and concurrency
PRAGMA synchronous = FULL;     -- stronger crash safety for WAL
PRAGMA temp_store = MEMORY;    -- reduce sensitive temp files on disk
PRAGMA secure_delete = ON;     -- overwrite deleted content (perf trade-off)

Notas:

  • secure_delete=ON aumenta la sobrecarga de escritura; habilítalo cuando importen los restos de datos
  • temp_store=MEMORY aumenta el uso de RAM; asegúrate de planificar la capacidad

Las conexiones de solo lectura o query-only reducen el riesgo en servicios que solo necesitan leer:

PRAGMA query_only = ON;        -- disallow writes for this connection

6) Desactiva la carga de extensiones

Bloquea la carga de extensiones arbitrarias para reducir la superficie de ataque.

Python:

conn.enable_load_extension(False)

C:

sqlite3_enable_load_extension(db, 0);

7) Copias de seguridad y migraciones sin filtraciones

  • Mantén las copias de seguridad cifradas; no vuelques datos en texto plano
  • Usa VACUUM INTO para exportar atómicamente una copia (SQLCipher admite copias cifradas cuando se establece una clave)
-- Plain SQLite
VACUUM INTO 'backup.sqlite';

-- SQLCipher: set key on the new file first (see SQLCipher docs)
-- PRAGMA key = 'current_key';
-- PRAGMA cipher_export = 'encrypted.sqlite';
-- SELECT sqlcipher_export('main');

Consejos operativos:

  • Asegúrate de que el almacenamiento de copias de seguridad tenga el acceso controlado y esté cifrado
  • Prueba periódicamente los procedimientos de restauración
  • Elimina las copias antiguas en texto plano y los archivos temporales de exportación

8) Refuerzo del sistema operativo y los contenedores

  • Ejecuta la aplicación como un usuario que no sea root; aplica el principio de mínimo privilegio al acceso al sistema de archivos
  • Usa cifrado de disco completo en servidores/portátiles; prefiere volúmenes cifrados
  • Restringe el proceso con AppArmor/SELinux cuando estén disponibles
  • Supervisa las lecturas inesperadas del directorio de la DB

9) Observabilidad y señales de integridad

  • Registra los eventos de apertura/cierre de la DB, los fallos de autenticación y las operaciones de migración (sin secretos)
  • Rastrea los hashes y tamaños de los archivos para detectar cambios inesperados
  • Genera alertas ante desviaciones de permisos (por ejemplo, archivos que ya no tengan 0600)

Lista de comprobación final

  • SQLCipher (o SEE) en uso; claves procedentes de un secrets manager/almacén de claves del sistema operativo
  • Permisos exclusivos para el propietario en los archivos .db, -wal, -shm
  • Consultas parametrizadas; no se construye SQL mediante strings
  • foreign_keys=ON, journal_mode=WAL, synchronous=FULL, temp_store=MEMORY
  • secure_delete=ON si importan las eliminaciones de datos sensibles
  • Carga de extensiones desactivada; solo lectura/query-only cuando corresponda
  • Copias de seguridad cifradas; restauraciones probadas; copias antiguas en texto plano eliminadas
  • La aplicación se ejecuta como un usuario que no sea root, con acceso al sistema de archivos basado en el mínimo privilegio