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ãoSqlite.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 viasqlite3_rekey. Passarnullou array vazio (new byte[0]) remove a criptografia (“rekey to no key”).SqliteException.code— código numérico do erro. Compare com constantes deSqlite. Chave errada / arquivo não é banco ⇒code == Sqlite.NOTADB(26). Outras úteis:Sqlite.BUSY(5),Sqlite.CONSTRAINT(19).
Como a chave errada aparece. No
opencom 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ê capturaSqliteExceptioncomcode == 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 bancosCHACHA20; um bancoSQLCIPHERprecisa deopen(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
MainWindowacima.
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
- Boas práticas de segurança · Guia de storage SQLite
- Receitas irmãs: Abrir/criar banco · CRUD parametrizado