# Como usar um banco SQLite criptografado

> Abrir com chave, escolher a cifra, trocar/remover a chave e detectar chave errada.

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

# Como usar um banco SQLite criptografado

**Propósito.** Proteger dados sensíveis no disco com um banco cifrado (via SQLite3MC). A criptografia é **opt-in**: sem chave o banco é um SQLite comum, sem custo de cifra. Com chave, o CrossFly aplica a cifra e valida a chave **na abertura** (falha cedo, não na primeira query).

## APIs usadas
- `SqliteDb.open(String filename, byte[] key)` — abre/cria cifrado com a **cifra padrão** `Sqlite.Cipher.CHACHA20` (ChaCha20-Poly1305).
- `SqliteDb.open(String filename, byte[] key, Sqlite.Cipher cipher)` — abre com cifra explícita. Opções: `CHACHA20`, `SQLCIPHER` (AES-256-CBC + HMAC), `AES128CBC`, `AES256CBC`, `RC4`, `ASCON128`.
- `SqliteDb.rekey(byte[] newKey)` — troca a chave via `sqlite3_rekey`. Passar `null` ou array **vazio** (`new byte[0]`) **remove** a criptografia ("rekey to no key").
- `SqliteException.code` — código numérico do erro. Compare com constantes de `Sqlite`. **Chave errada / arquivo não é banco** ⇒ `code == Sqlite.NOTADB` (**26**). Outras úteis: `Sqlite.BUSY` (5), `Sqlite.CONSTRAINT` (19).

> **Como a chave errada aparece.** No `open` com chave, o CrossFly seleciona a cifra, aplica a chave e faz uma leitura forçada (`SELECT count(*) FROM sqlite_master`) para falhar **imediatamente** com chave errada, fechando a conexão. Você captura `SqliteException` com `code == Sqlite.NOTADB`.

> **Cuidado com a cifra na reabertura.** Para reabrir um banco existente, use a **mesma cifra** com que ele foi criado. `CHACHA20` (padrão) só decifra bancos `CHACHA20`; um banco `SQLCIPHER` precisa de `open(file, key, Sqlite.Cipher.SQLCIPHER)`.

## Exemplo completo (copiar, colar e rodar)

```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

/** Tela principal executável. Aponte o launcher (classname) e rode: 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/criar cifrado (cifra padrão 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");

         // Trocar a chave
         byte[] novaKey = "senha-ainda-mais-forte".getBytes(StandardCharsets.UTF_8);
         db.rekey(novaKey);
         // db.rekey(new byte[0]);  // <- removeria a criptografia
      }

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

      // Detectar chave errada
      try (SqliteDb db = SqliteDb.open("secure.db", "chave-errada".getBytes(StandardCharsets.UTF_8)))
      {
         // não chega aqui: a validação já falhou no open
      }
      catch (SqliteException e)
      {
         if (e.code == Sqlite.NOTADB)          // 26 = chave errada / não é um banco
            System.out.println("Chave incorreta");
         else
            throw e;
      }
   }
}
```

> O trecho abaixo mostra **só o miolo**. Para rodar, coloque-o no esqueleto `MainWindow` acima.

## Trecho mínimo
```java
// verificado na fonte: 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/criar cifrado (cifra padrão 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");

   // Trocar a chave
   byte[] novaKey = "senha-ainda-mais-forte".getBytes(StandardCharsets.UTF_8);
   db.rekey(novaKey);
   // db.rekey(new byte[0]);  // <- removeria a criptografia
}

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

// Detectar chave errada
try (SqliteDb db = SqliteDb.open("secure.db", "chave-errada".getBytes(StandardCharsets.UTF_8)))
{
   // não chega aqui: a validação já falhou no open
}
catch (SqliteException e)
{
   if (e.code == Sqlite.NOTADB)          // 26 = chave errada / não é um banco
      System.out.println("Chave incorreta");
   else
      throw e;
}
```

## Aprofunde-se
- [Boas práticas de segurança](/docs/best-practices/seguranca) · [Guia de storage SQLite](/docs/data-and-services/storage-sqlite)
- Receitas irmãs: [Abrir/criar banco](/docs/cookbook/data/abrir-banco) · [CRUD parametrizado](/docs/cookbook/data/crud)
