# 如何使用加密的 SQLite 数据库

> 使用密钥打开、选择加密算法、更换/移除密钥并检测错误的密钥。

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

# 如何使用加密的 SQLite 数据库

**目的。** 通过加密数据库（借助 SQLite3MC）在磁盘上保护敏感数据。加密是 **可选启用（opt-in）** 的：没有密钥时数据库就是普通的 SQLite，不产生加密开销。有密钥时，CrossFly 会应用加密算法并 **在打开时** 验证密钥（尽早失败，而不是在第一次查询时才失败）。

## 使用的 API
- `SqliteDb.open(String filename, byte[] key)` — 使用 **默认加密算法** `Sqlite.Cipher.CHACHA20`（ChaCha20-Poly1305）打开/创建加密数据库。
- `SqliteDb.open(String filename, byte[] key, Sqlite.Cipher cipher)` — 使用显式指定的加密算法打开。可选项：`CHACHA20`、`SQLCIPHER`（AES-256-CBC + HMAC）、`AES128CBC`、`AES256CBC`、`RC4`、`ASCON128`。
- `SqliteDb.rekey(byte[] newKey)` — 通过 `sqlite3_rekey` 更换密钥。传入 `null` 或 **空** 数组（`new byte[0]`）会 **移除** 加密（"rekey to no key"）。
- `SqliteException.code` — 错误的数字代码。与 `Sqlite` 中的常量进行比较。**密钥错误 / 文件不是数据库** ⇒ `code == Sqlite.NOTADB`（**26**）。其他有用的常量：`Sqlite.BUSY`（5）、`Sqlite.CONSTRAINT`（19）。

> **错误密钥如何表现。** 在带密钥的 `open` 中，CrossFly 会选择加密算法、应用密钥并执行一次强制读取（`SELECT count(*) FROM sqlite_master`），以便在密钥错误时 **立即** 失败并关闭连接。你会捕获到 `code == Sqlite.NOTADB` 的 `SqliteException`。

> **重新打开时注意加密算法。** 要重新打开一个已存在的数据库，请使用它创建时所用的 **相同加密算法**。`CHACHA20`（默认）只能解密 `CHACHA20` 数据库；一个 `SQLCIPHER` 数据库需要 `open(file, key, Sqlite.Cipher.SQLCIPHER)`。

## 完整示例（复制、粘贴即可运行）

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

/** 可执行的主界面。将 launcher 指向此类（classname）并运行：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);

      // 打开/创建加密库（默认加密 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");

         // 更换密钥
         byte[] novaKey = "senha-ainda-mais-forte".getBytes(StandardCharsets.UTF_8);
         db.rekey(novaKey);
         // db.rekey(new byte[0]);  // <- 会移除加密
      }

      // 显式加密（兼容 SQLCipher）
      try (SqliteDb db = SqliteDb.open("sqlcipher.db", key, Sqlite.Cipher.SQLCIPHER))
      {
         db.exec("SELECT count(*) FROM sqlite_master");
      }

      // 检测错误密钥
      try (SqliteDb db = SqliteDb.open("secure.db", "chave-errada".getBytes(StandardCharsets.UTF_8)))
      {
         // 不会到达这里：验证已在 open 时失败
      }
      catch (SqliteException e)
      {
         if (e.code == Sqlite.NOTADB)          // 26 = 密钥错误 / 不是数据库
            System.out.println("Chave incorreta");
         else
            throw e;
      }
   }
}
```

> 下面的片段只展示 **核心部分**。要运行它，请把它放入上面的 `MainWindow` 骨架中。

## 最小片段
```java
// 已在源码核实：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);

// 打开/创建加密库（默认加密 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");

   // 更换密钥
   byte[] novaKey = "senha-ainda-mais-forte".getBytes(StandardCharsets.UTF_8);
   db.rekey(novaKey);
   // db.rekey(new byte[0]);  // <- 会移除加密
}

// 显式加密（兼容 SQLCipher）
try (SqliteDb db = SqliteDb.open("sqlcipher.db", key, Sqlite.Cipher.SQLCIPHER))
{
   db.exec("SELECT count(*) FROM sqlite_master");
}

// 检测错误密钥
try (SqliteDb db = SqliteDb.open("secure.db", "chave-errada".getBytes(StandardCharsets.UTF_8)))
{
   // 不会到达这里：验证已在 open 时失败
}
catch (SqliteException e)
{
   if (e.code == Sqlite.NOTADB)          // 26 = 密钥错误 / 不是数据库
      System.out.println("Chave incorreta");
   else
      throw e;
}
```

## 深入了解
- [安全最佳实践](/docs/best-practices/seguranca) · [SQLite 存储指南](/docs/data-and-services/storage-sqlite)
- 相关姊妹篇：[打开/创建数据库](/docs/cookbook/data/abrir-banco) · [参数化 CRUD](/docs/cookbook/data/crud)
