# Cómo usar una base SQLite cifrada

> Abrir con clave, elegir el cifrado, cambiar/quitar la clave y detectar clave incorrecta.

Source: https://crossfly.dev/es/docs/cookbook/data/banco-criptografado — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# Cómo usar una base SQLite cifrada

**Propósito.** Proteger datos sensibles en el disco con una base cifrada (vía SQLite3MC). El cifrado es **opt-in**: sin clave la base es un SQLite común, sin costo de cifrado. Con clave, CrossFly aplica el cifrado y valida la clave **en la apertura** (falla temprano, no en la primera consulta).

## APIs usadas
- `SqliteDb.open(String filename, byte[] key)` — abre/crea cifrado con el **cifrado por defecto** `Sqlite.Cipher.CHACHA20` (ChaCha20-Poly1305).
- `SqliteDb.open(String filename, byte[] key, Sqlite.Cipher cipher)` — abre con cifrado explícito. Opciones: `CHACHA20`, `SQLCIPHER` (AES-256-CBC + HMAC), `AES128CBC`, `AES256CBC`, `RC4`, `ASCON128`.
- `SqliteDb.rekey(byte[] newKey)` — cambia la clave vía `sqlite3_rekey`. Pasar `null` o un array **vacío** (`new byte[0]`) **quita** el cifrado ("rekey to no key").
- `SqliteException.code` — código numérico del error. Compare con constantes de `Sqlite`. **Clave incorrecta / el archivo no es una base** ⇒ `code == Sqlite.NOTADB` (**26**). Otras útiles: `Sqlite.BUSY` (5), `Sqlite.CONSTRAINT` (19).

> **Cómo aparece la clave incorrecta.** En el `open` con clave, CrossFly selecciona el cifrado, aplica la clave y hace una lectura forzada (`SELECT count(*) FROM sqlite_master`) para fallar **inmediatamente** con clave incorrecta, cerrando la conexión. Usted captura `SqliteException` con `code == Sqlite.NOTADB`.

> **Cuidado con el cifrado al reabrir.** Para reabrir una base existente, use el **mismo cifrado** con el que fue creada. `CHACHA20` (por defecto) solo descifra bases `CHACHA20`; una base `SQLCIPHER` necesita `open(file, key, Sqlite.Cipher.SQLCIPHER)`.

## Ejemplo completo (copiar, pegar y ejecutar)

```java
package com.exemplo;

import java.nio.charset.StandardCharsets;

import dev.crossfly.ui.*;                     // Window
import dev.crossfly.ui.layout.*;             // VerticalLayout
import dev.crossfly.sqlite.*;                // SqliteDb, Sqlite, SqliteException

/** Pantalla principal ejecutable. Apunta el launcher (classname) y corre: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      byte[] key = "minha-senha-forte".getBytes(StandardCharsets.UTF_8);

      // Abrir/crear cifrado (cifra por defecto CHACHA20)
      try (SqliteDb db = SqliteDb.open("secure.db", key))
      {
         db.exec("CREATE TABLE IF NOT EXISTS secret(id INTEGER PRIMARY KEY, v TEXT)");
         db.execute("INSERT INTO secret(v) VALUES(?)", "dado sensível");

         // Cambiar la clave
         byte[] novaKey = "senha-ainda-mais-forte".getBytes(StandardCharsets.UTF_8);
         db.rekey(novaKey);
         // db.rekey(new byte[0]);  // <- removería el cifrado
      }

      // Cifra explícita (compatible con SQLCipher)
      try (SqliteDb db = SqliteDb.open("sqlcipher.db", key, Sqlite.Cipher.SQLCIPHER))
      {
         db.exec("SELECT count(*) FROM sqlite_master");
      }

      // Detectar clave errada
      try (SqliteDb db = SqliteDb.open("secure.db", "chave-errada".getBytes(StandardCharsets.UTF_8)))
      {
         // no llega aquí: la validación ya falló en el open
      }
      catch (SqliteException e)
      {
         if (e.code == Sqlite.NOTADB)          // 26 = clave errada / no es una base de datos
            System.out.println("Chave incorreta");
         else
            throw e;
      }
   }
}
```

> El fragmento de abajo muestra **solo el núcleo**. Para ejecutarlo, colóquelo en el esqueleto `MainWindow` de arriba.

## Fragmento mínimo
```java
// verificado en la fuente: open(file,key), open(file,key,cipher), rekey, SqliteException.code == Sqlite.NOTADB
import dev.crossfly.sqlite.*;
import java.nio.charset.StandardCharsets;

byte[] key = "minha-senha-forte".getBytes(StandardCharsets.UTF_8);

// Abrir/crear cifrado (cifra por defecto CHACHA20)
try (SqliteDb db = SqliteDb.open("secure.db", key))
{
   db.exec("CREATE TABLE IF NOT EXISTS secret(id INTEGER PRIMARY KEY, v TEXT)");
   db.execute("INSERT INTO secret(v) VALUES(?)", "dado sensível");

   // Cambiar la clave
   byte[] novaKey = "senha-ainda-mais-forte".getBytes(StandardCharsets.UTF_8);
   db.rekey(novaKey);
   // db.rekey(new byte[0]);  // <- removería el cifrado
}

// Cifra explícita (compatible con SQLCipher)
try (SqliteDb db = SqliteDb.open("sqlcipher.db", key, Sqlite.Cipher.SQLCIPHER))
{
   db.exec("SELECT count(*) FROM sqlite_master");
}

// Detectar clave errada
try (SqliteDb db = SqliteDb.open("secure.db", "chave-errada".getBytes(StandardCharsets.UTF_8)))
{
   // no llega aquí: la validación ya falló en el open
}
catch (SqliteException e)
{
   if (e.code == Sqlite.NOTADB)          // 26 = clave errada / no es una base de datos
      System.out.println("Chave incorreta");
   else
      throw e;
}
```

## Profundice
- [Buenas prácticas de seguridad](/docs/best-practices/seguranca) · [Guía de storage SQLite](/docs/data-and-services/storage-sqlite)
- Recetas hermanas: [Abrir/crear base](/docs/cookbook/data/abrir-banco) · [CRUD parametrizado](/docs/cookbook/data/crud)
