サイト内検索

Electron 向け暗号化 SQLite ライブラリを実測する — better-sqlite3-multiple-ciphers と @journeyapps/sqlcipher を macOS・Windows で動かす

重岡 正 · Fri, August 28, 2026

前回の記事「Electron 向け暗号化 SQLite ライブラリを比較する」では、better-sqlite3-multiple-ciphers を第一候補、@signalapp/sqlcipherSQLite Encryption Extension(SEE) を条件付きの第二・第三候補、@journeyapps/sqlcipher は v6.0.0 で Windows サポートを廃止したためクロスプラットフォーム Electron では選びづらい、という推奨を机上で組み立てました。

机上の比較には、実運用ターゲットで再ビルドなしに動くか・electron-builderasar unpack が期待どおり効くか・Windows の署名前段で .node バイナリが載るかといった、実測でしか確定しない部分が残ります。今回は codenote-net/app-distribution-recipes に Electron Todo アプリのレシピを 3 本追加し、macOSWindows の両方で CRUD と暗号化ヘッダの検証まで通す形でプロトタイピングしました。結論から言うと、前記事の推奨は実測でも同じ順序で成り立ちました。

プロトタイピングの枠組み

3 本のレシピはいずれも、同じ最小の Todo CRUD アプリを別のライブラリ・別のビルド構成で動かすことに絞っています。UI・IPC の境界・暗号鍵の扱いといった Electron 側のパターンは共通にし、ライブラリ差分だけを浮き彫りにしたい意図です。

対応する PR は次の 4 本です。

各レシピの共通仕様は次のとおりです。

  • ランタイム: Electron 44.0.0、electron-builder 26.15.3、Node.js 22 以上(執筆時点の LTS 系列以上)
  • Renderer は contextIsolationsandbox を有効化し、DB へは Main プロセスに閉じた狭い IPC 経由でしか触れません
  • 暗号鍵はデモ用にハードコードした固定値です。プロダクション用途では前記事で触れたとおり safeStorageOWASP Password Storage Cheat Sheet 準拠の鍵導出関数と組み合わせます
  • スモークテストは CRUD のあとに DB ファイル先頭 16 バイトを読み、SQLite format 3\0 になっていないことを確認します。これで「本当に暗号化ヘッダで書き出されているか」までを機械的にチェックできます
const header = fs.readFileSync(databasePath).subarray(0, 16).toString("utf8");
assert.notEqual(header, "SQLite format 3 ");

better-sqlite3-multiple-ciphers を macOS と Windows で動かす

第一レシピは、前記事で本命に据えた better-sqlite3-multiple-ciphers 13.0.3 をそのまま使います。DB 実装は同期 API そのままで、ライブラリの N-API プリビルドを electron-builder の install-app-deps が Electron 44 の ABI に合わせて置き換えてくれます。

DB ラッパの中核は非常に素直で、Database#key に生バッファでキーを渡し、WAL モードprepare をそのまま使うだけです。

const Database = require("better-sqlite3-multiple-ciphers");
 
class TodoDatabase {
  constructor(filePath, encryptionKey) {
    this.database = new Database(filePath);
    this.database.key(Buffer.from(encryptionKey, "utf8"));
    this.database.pragma("journal_mode = WAL");
    this.database.exec(`
      CREATE TABLE IF NOT EXISTS todos (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL CHECK (length(title) BETWEEN 1 AND 200),
        completed INTEGER NOT NULL DEFAULT 0 CHECK (completed IN (0, 1)),
        created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
        updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
      )
    `);
    // ... prepare 済みステートメントを準備
  }
}

electron-builder 側の設定も最小限で済みます。ネイティブモジュールを asar から unpack する指定と、DMG・ZIP・NSIS・portable のターゲットを列挙するだけです。

{
  "build": {
    "asarUnpack": [
      "node_modules/better-sqlite3-multiple-ciphers/**/*"
    ],
    "mac": { "target": ["dmg", "zip"] },
    "win": { "target": ["nsis", "portable"] }
  }
}

実測結果は以下のとおりです。

  • macOS arm64 で npm run build:mac を実行し、DMG と ZIP が通常時間内で生成されました
  • 生成された .app を Finder から起動し、Todo の作成・更新・完了トグル・削除まで手動で動作確認しました
  • npm test の CRUD+暗号化ヘッダのスモークテストは両 OS で成功しました
  • Windows x64 では npm run build:win で NSIS インストーラと portable exe が生成され、インストール後のアプリが同じ CRUD 挙動を示しました
  • どのターゲットでも electron-builder install-app-deps が N-API プリビルドを配置しただけで、追加の再ビルド設定・追加パッケージ・afterPack フックは一切必要ありませんでした

前記事では「理論上(theoretically)動作するはず、というのは上流 README の表現である」と保留を書いていましたが、今回の実測でその保留は取れました。少なくとも本レシピが対象にした macOS arm64 と Windows x64 では、electron-builder install-app-deps に任せるだけで N-API プリビルドが Electron 44 の ABI に載っています。

@journeyapps/sqlcipher を macOS で動かす

第二レシピは @journeyapps/sqlcipher 6.0.0 を使います。前記事で書いたとおり、このバージョンは node-pre-gyp を廃止してソースビルド専用になり、Windows サポートも公式に外れています。macOS では SQLCipherOpenSSL のソースビルドが自前で走るため、追加準備はほぼ Xcode Command Line Tools だけで済みます。

このライブラリは伝統的な node-sqlite3 系のコールバック API を露出するため、DB ラッパで Promise ラップを挟む形になります。ここは前記事でも触れた同期・非同期の API 差分がそのまま出てくる部分です。

const sqlite3 = require("@journeyapps/sqlcipher").verbose();
 
class TodoDatabase {
  async initialize() {
    const escapedKey = this.encryptionKey.replaceAll("'", "''");
    await this.run("PRAGMA cipher_compatibility = 4");
    await this.run(`PRAGMA key = '${escapedKey}'`);
    await this.run("PRAGMA journal_mode = WAL");
    // ... CREATE TABLE
  }
}

macOS 側の実測結果は次のとおりです。

  • npm install で SQLCipher / OpenSSL のソースビルドが走り、そのあと postinstallelectron-builder install-app-deps が Electron 44 用に再ビルドしました
  • npm test の CRUD+暗号化ヘッダのスモークテストは成功しました
  • npm run build:mac で DMG と ZIP が生成され、packaged アプリケーションから起動できました
  • パッケージ内の .nodefile コマンドで Mach-O arm64 と確認できました

一方で Windows 側は「レシピを走らせるとどこで失敗するか」を機械的に再現できる状態を残しています。npm run build:win は SQLCipher の native addon 再ビルド段階で落ちます。ここで無理に npmRebuild=false を渡してパッケージだけ作ってしまうと、ホスト OS の Mach-O .node が Windows パッケージに紛れ込み、実行時にだけ落ちる「動くように見える壊れたインストーラ」が生まれてしまいます。今回はそれを避け、CI 側で「意図された未サポート」であることを明示するに留めました。

@journeyapps/sqlcipher を Windows でも動かす(回避策)

第三レシピは、公式には Windows サポートが外れた @journeyapps/sqlcipher 6.0.0 を、それでも Windows x64 で動かすための最小限の回避策です。用途としては「既存プロジェクトが @journeyapps/sqlcipher に依存しているが、better-sqlite3-multiple-ciphers への完全移行前にどうしても Windows でも配布したい」という一時凌ぎに近い位置付けとして書きました。

このレシピが追加でやっているのは次の 4 点です。

  • vcpkg の固定リビジョンから openssl:x64-windows を取得し、stage-windows-openssl.ps1@journeyapps/sqlcipherbinding.gyp が期待するパスにヘッダとインポートライブラリを配置します
  • npm メタデータの os フィールドが Windows を拒否するため、npm ci --ignore-scripts --force で lifecycle スクリプトを止めた状態で依存を入れ、そのあと Electron だけ npm rebuild electron で復帰させ、最後に自前のビルド手順に流します
  • electron-builder --config.npmRebuild=false で二重再ビルドを回避しつつ、afterPack フックで libcrypto-3-x64.dlllibssl-3-x64.dll を packaged app 直下にコピーします
  • GitHub Actionswindows-2022 ランナーで、ネイティブ addon の存在チェック・DLL の存在チェック・packaged exe の起動確認までを CI で走らせています

Windows 用の追加ビルドスクリプトはこのぐらいの規模です。

{
  "scripts": {
    "stage:win": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/stage-windows-openssl.ps1",
    "build:win": "npm run stage:win && electron-builder install-app-deps && electron-builder --win --x64 --config.npmRebuild=false"
  }
}

回避策とはいえ、CI で毎回再現するところまで固めておけば、初回セットアップに必要な手順の抜けを検出できます。逆にここまで手を掛けないと Windows で動かないという事実は、そのまま「Windows もターゲットに含む新規 Electron アプリで @journeyapps/sqlcipher を選ぶ理由はない」という前記事の判断を強化する材料になりました。

依存フローの俯瞰

3 本のレシピが実際に動くまでの前提を並べると次のようになります。同じ Electron アプリでも、選ぶライブラリで「何を追加で用意しないといけないか」が段違いに変わってきます。

flowchart LR
    A["npm install"] --> B{"ライブラリ"}
    B -- "better-sqlite3-multiple-ciphers" --> C["N-API プリビルド取得"]
    B -- "@journeyapps/sqlcipher (macOS)" --> D["Xcode CLT でソースビルド"]
    B -- "@journeyapps/sqlcipher (Windows)" --> E["vcpkg で OpenSSL 用意 + install --force"]
    C --> F["electron-builder install-app-deps"]
    D --> F
    E --> G["自前 stage スクリプト"]
    G --> H["electron-builder --config.npmRebuild=false"]
    F --> I["パッケージ生成"]
    H --> I

better-sqlite3-multiple-ciphers 側は「npm install すればプリビルドが降ってきて、install-app-deps に任せれば Electron ABI に載る」という最短ルートを取れます。@journeyapps/sqlcipher は macOS でもソースビルドが必須で、Windows では vcpkgbinding.gyp に対する手当てまで含む「自前ビルド」を要求されます。

実測でわかったこと

3 本のレシピを通して、前記事の推奨には 3 つの実測裏付けが取れました。

  • N-API プリビルドの汎用性: better-sqlite3-multiple-ciphers 13.0.3 の Electron 44 対応は、macOS arm64・Windows x64 のどちらも install-app-deps だけで完結しました。前記事で保留していた「実測で必ず検証したい」の部分は、少なくとも本レシピのターゲットでは追加設定なしにグリーンでした
  • @journeyapps/sqlcipher v6.0.0 の Windows 非対応: 「公式に外れた」だけでなく、npm メタデータ・binding.gyp の前提・OpenSSL の入手経路まで含めて、真面目に Windows で動かそうとするとレシピ 1 本分の追加コードが必要になります。「一時的な回避策で乗り切れる」レベルではありますが、better-sqlite3-multiple-ciphers との労力差は明白です
  • 暗号化ヘッダの機械的検証: ライブラリ選定と別に、SQLite format 3\0 が書き出されていないことをスモークテストに入れておくと、鍵設定漏れや PRAGMA key の順序ミスが CI で必ず落ちます。ライブラリを乗り換えたときの回帰も同じテストで拾えます

前記事の推奨順(better-sqlite3-multiple-ciphers@signalapp/sqlcipher → SEE、@journeyapps/sqlcipher は Windows を含むターゲットなら候補外)は、macOS arm64 と Windows x64 に限れば実測でもそのまま維持できます。次のプロトタイピング候補は、AGPL の適合性が問題にならないケースで @signalapp/sqlcipher を同じフレームで動かすところと、Notarizing macOS software before distribution の署名・公証まで含めた end-to-end 検証あたりになる予定です。

以上、Electron 向け暗号化 SQLite ライブラリの候補を実際にプロトタイピングし、macOS と Windows での挙動差と @journeyapps/sqlcipher を Windows で動かすための回避策までを実測ベースで整理した、現場からお送りしました。

参考情報