暗号化ストレージ (Stronghold) の導入と利用

Recipe ID: db-005

パスワードや API キーなどの機密情報を安全に保存するために、Tauri の plugin-stronghold を使用して暗号化ストレージを導入・利用する方法を解説します。
Stronghold は IOTA 財団が開発したセキュアなデータベースエンジンを使用しており、データはファイルシステム上で暗号化されて保存されます。

1. セットアップ

インストール手順

Rust 側と JavaScript 側の両方に依存関係を追加し、プラグインを初期化します。

1. コマンドで追加

Tauri の CLI を使用して追加します。

npm run tauri add stronghold
補足: このレシピのコード例では argon2 クレートを使用しています。src-tauri ディレクトリで cargo add argon2 を実行して依存関係を追加してください。
2. プラグインの初期化 (Rust)

src-tauri/src/lib.rs でハッシュ化ロジックを含めてプラグインを登録します。
以下は rust-argon2 クレートを使用してパスワードのハッシュ値を生成する公式サンプルの実装例です。

// src-tauri/src/lib.rs

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(
            tauri_plugin_stronghold::Builder::new(|password| {
                // Argon2 v0.5+ を使用したハッシュ化の実装例
                // Cargo.toml に argon2 = "0.5" (またはそれ以降) が必要です
                use argon2::{
                    password_hash::{PasswordHasher, SaltString},
                    Argon2,
                };

                // 本番環境では、アプリごとに一意で固定のソルトを使用する必要があります。
                // ランダムに生成してしまうと、再起動後に復号できなくなります。
                // ここでは例として固定のバイト列からソルトを生成します。
                let salt = SaltString::encode_b64(b"random_salt_value_16_bytes").expect("Failed to create salt");

                let argon2 = Argon2::default();
                let password_hash = argon2
                    .hash_password(password.as_bytes(), &salt)
                    .expect("Failed to hash password");

                // 生のハッシュ値 (32バイト) を取り出して返す
                password_hash.hash.expect("Hash calculation failed").as_bytes().to_vec()
            })
            .build(),
        )
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

権限設定 (Capabilities)

src-tauri/capabilities/default.json で Stronghold へのアクセスを許可します。

{
    "permissions": [
        ...,
        "stronghold:default"
    ]
}

2. データの操作

Stronghold の操作は基本的に「ロード」→「クライアント作成」→「ストア操作」→「保存」の流れになります。

コード例 (TypeScript)

以下は、公式ドキュメントに基づいたデータの保存と読み出しの実装例です。

import { Client, Stronghold } from '@tauri-apps/plugin-stronghold';
import { appDataDir } from '@tauri-apps/api/path';

// Stronghold の初期化とクライアント取得
const initStronghold = async () => {
  const vaultPath = `${await appDataDir()}/vault.hold`;
  const vaultPassword = 'vault password'; // 本番ではユーザー入力などを使用してください
  const stronghold = await Stronghold.load(vaultPath, vaultPassword);
  
  let client: Client;
  const clientName = 'name your client';
  
  try {
    client = await stronghold.loadClient(clientName);
  } catch {
    client = await stronghold.createClient(clientName);
  }
  
  return {
    stronghold,
    client,
  };
};

// 「store」にレコードを挿入
async function insertRecord(store: any, key: string, value: string) {
  const data = Array.from(new TextEncoder().encode(value));
  await store.insert(key, data);
}

// 「ストア」からレコードを読み取り
async function getRecord(store: any, key: string): Promise<string> {
  const data = await store.get(key);
  return new TextDecoder().decode(new Uint8Array(data));
}

// 使用例
async function main() {
  const { stronghold, client } = await initStronghold();
  const store = client.getStore();
  const key = 'my_key';

  // 「store」にレコードを挿入
  await insertRecord(store, key, 'secret value');

  // 「ストア」からレコードを読み取り
  const value = await getRecord(store, key);
  console.log(value); // 'secret value'

  // 更新内容を保存
  await stronghold.save();

  // 必要であれば削除
  // await store.remove(key);
}

3. 使い分けのヒント

Stronghold はセキュリティ強度が高い反面、初期化手順やパスワード管理などが複雑です。

* ウィンドウサイズやテーマ設定など → plugin-store (JSON)
* アクセストークンや秘密鍵など → plugin-stronghold (暗号化)

というように使い分けるのが賢明です。