Pular para o conteúdo
CrossFly é 100% open sourceSiga no GitHub
CrossFlyCrossFly
Começar

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)

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

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

Esta página foi útil?