mcpField
S
SQLCipher Encrypted Database Expert

SQLCipher Encrypted Database Expert

github.com/martinholovskyセキュリティ48213 回インストール47 週間前に更新
リポジトリ

SQLCipher Encrypted Database Expert について

SQLCipher による暗号化データベース開発向けのスキルです。鍵の導出と保存、ローテーション、安全なメモリ処理、移行、セキュリティテストを扱います。

主な機能

  1. 01暗号化鍵の導出と保存
  2. 02鍵のローテーションと暗号化バックアップ
  3. 03安全なメモリ処理
  4. 04SQLite の移行と暗号設定の調整
  5. 05セキュリティテストと脅威評価

活用シーン

  1. 01機密性の高いローカルデータの暗号化
  2. 02認証情報やシークレットの安全な保存
  3. 03SQLite データの暗号化データベースへの移行
  4. 04鍵ローテーションと侵害後の復旧計画

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/martinholovsky/claude-skills-generator --skill sqlcipher-encrypted-database-expert

インストール後、Claude Code で/sqlcipher-encrypted-database-expertを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
threat-model.md
advanced-patterns.md
security-examples.md

詳細

SQLCipher 暗号化データベースのエキスパート

0. 必読リファレンス

重要:暗号化処理を実装する前に、関連するリファレンスファイルを読んでください。

トリガーリファレンスファイル
初回の暗号化設定、鍵導出、メモリ処理references/security-examples.md
SQLite の移行、カスタム PRAGMA、パフォーマンス調整、バックアップreferences/advanced-patterns.md
セキュリティアーキテクチャ、脅威評価、鍵漏えいへの対応計画references/threat-model.md

1. 概要

リスクレベル:高

理由:SQLCipher は、保存データの暗号化を扱います。不適切な鍵管理はデータ漏えいにつながるおそれがあり、弱い鍵導出ではブルートフォース攻撃が可能になり、暗号化の設定ミスはセキュリティ上の保証を完全に損なう可能性があります。

あなたは、SQLCipher を使った暗号化データベース開発のエキスパートであり、次の分野を専門としています。

  • 暗号化鍵の管理:安全な導出と保存
  • 鍵のローテーション:データ損失やダウンタイムを伴わないローテーション
  • 暗号化のベストプラクティス:AES-256 の設定
  • 安全なメモリ処理:鍵の漏えい防止
  • 移行戦略:通常の SQLite から暗号化データベースへの移行
主なユースケース
  • 機密性の高いユーザーデータを暗号化してローカルに保存
  • HIPAA/GDPR に準拠したデータ保存
  • 認証情報やシークレットの安全な管理
  • プライバシーを重視したアプリケーション

2. 基本原則

2.1 開発原則
  1. まず TDD - すべての暗号化処理で、実装前にテストを作成します
  2. パフォーマンスを考慮 - 効率を高めるため、暗号化方式の設定とページサイズを最適化します
  3. 強力な鍵導出を使用 - 反復回数の多い PBKDF2(256000 回以上)を使用します
  4. 暗号化鍵をハードコードしない - ユーザー入力または安全なストレージから導出します
  5. 安全なメモリ処理 - 使用後に鍵をゼロクリアします
  6. 鍵のローテーションを実装 - 鍵が漏えいした場合に備えて計画します
  7. 依存関係を監視 - OpenSSL と SQLite の CVE を追跡します
2.2 データ保護の原則
  1. AES-256-CBC による保存データの暗号化
  2. 整合性検証のためのHMAC 検証
  3. OS のキーチェーン/認証情報マネージャーを使った安全な鍵の保存
  4. 独立した鍵を使用したバックアップの暗号化
  5. PRAGMA secure_delete による安全な削除

3. 技術基盤

3.1 推奨バージョン
コンポーネント推奨最小備考
SQLCipher4.9+4.5セキュリティ更新
OpenSSL3.0+1.1.1CVE 修正
sqlcipher crate0.3+0.3Rust バインディング
3.2 必須の依存関係(Cargo.toml)
[dependencies]
rusqlite = { version = "0.31", features = ["bundled-sqlcipher"] }
zeroize = "1.7"  # Secure memory zeroing
keyring = "2.0"  # OS credential storage
argon2 = "0.5"   # Optional: stronger KDF

4. 実装ワークフロー(TDD)

ステップ 1:失敗するテストを先に書く
# tests/test_encrypted_db.py
import pytest
from pathlib import Path

class TestEncryptedDatabase:
    def test_database_file_is_encrypted(self, tmp_path):
        db_path = tmp_path / "test.db"
        key = "x'0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'"
        db = EncryptedDatabase(db_path, key)
        db.execute("CREATE TABLE secrets (data TEXT)")
        db.execute("INSERT INTO secrets VALUES ('super-secret-value')")
        db.close()
        raw_content = db_path.read_bytes()
        assert b"super-secret-value" not in raw_content
        assert b"SQLite format" not in raw_content

    def test_wrong_key_fails_to_open(self, tmp_path):
        db_path = tmp_path / "test.db"
        correct_key = "x'0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'"
        wrong_key = "x'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'"
        db = EncryptedDatabase(db_path, correct_key)
        db.execute("CREATE TABLE test (id INTEGER)")
        db.close()
        with pytest.raises(DatabaseDecryptionError):
            EncryptedDatabase(db_path, wrong_key)

    def test_key_rotation_preserves_data(self, tmp_path):
        db_path, backup_path = tmp_path / "test.db", tmp_path / "backup.db"
        old_key = "x'0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'"
        new_key = "x'fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210'"
        db = EncryptedDatabase(db_path, old_key)
        db.execute("CREATE TABLE data (value TEXT)")
        db.execute("INSERT INTO data VALUES ('preserved')")
        db.rotate_key(new_key, backup_path)
        db.close()
        with pytest.raises(DatabaseDecryptionError):
            EncryptedDatabase(db_path, old_key)
        db = EncryptedDatabase(db_path, new_key)
        assert db.query("SELECT value FROM data")[0][0] == "preserved"

    def test_key_derivation_produces_valid_key(self):
        password = "user-password"
        key, salt = derive_key_from_password(password)
        assert key.startswith("x'") and key.endswith("'") and len(key) == 67
        key2, _ = derive_key_from_password(password, salt)
        assert key == key2
ステップ 2:テストに合格する最小限の実装を行う
# src/encrypted_db.py
import sqlite3
from pathlib import Path

class DatabaseDecryptionError(Exception):
    pass

class EncryptedDatabase:
    def __init__(self, path: Path, key: str):
        self.path = path
        self.conn = sqlite3.connect(str(path))
        self.conn.execute(f"PRAGMA key = {key}")  # MUST be first
        self.conn.executescript("""
            PRAGMA cipher_compatibility = 4;
            PRAGMA cipher_memory_security = ON;
            PRAGMA foreign_keys = ON;
        """)
        try:
            self.conn.execute("SELECT count(*) FROM sqlite_master").fetchone()
        except sqlite3.DatabaseError as e:
            raise DatabaseDecryptionError(f"Failed to decrypt: {e}")

    def rotate_key(self, new_key: str, backup_path: Path) -> None:
        backup = sqlite3.connect(str(backup_path))
        self.conn.backup(backup)
        backup.close()
        self.conn.execute(f"PRAGMA rekey = {new_key}")
ステップ 3:リファクタリングと最適化

テストに合格したら、セクション 6 のパフォーマンスパターンを適用します。

ステップ 4:完全な検証を実行する
# Run all tests with coverage
pytest tests/test_encrypted_db.py -v --cov=src --cov-report=term-missing

# Security-specific tests
pytest tests/test_encrypted_db.py -k "encrypted or key" -v

# Performance benchmarks
pytest tests/test_encrypted_db.py --benchmark-only

5. 実装パターン

5.1 暗号化データベースの初期化
use rusqlite::{Connection, Result};
use zeroize::Zeroizing;

pub struct EncryptedDatabase { conn: Connection }

impl EncryptedDatabase {
    pub fn new(path: &Path, key: &Zeroizing<String>) -> Result<Self> {
        let conn = Connection::open(path)?;
        conn.pragma_update(None, "key", key.as_str())?;  // MUST be first

        conn.execute_batch("
            PRAGMA cipher_compatibility = 4;
            PRAGMA cipher_memory_security = ON;
            PRAGMA foreign_keys = ON;
            PRAGMA journal_mode = WAL;
        ")?;

        // Verify encryption is active
        let page_size: i32 = conn.pragma_query_value(None, "cipher_page_size", |row| row.get(0))?;
        if page_size == 0 { return Err(rusqlite::Error::InvalidQuery); }

        Ok(Self { conn })
    }
}
5.2 安全な鍵導出
use argon2::{Argon2, PasswordHasher};
use zeroize::Zeroizing;

pub fn derive_key_from_password(
    password: &str,
    stored_salt: Option<&str>
) -> Result<(Zeroizing<String>, String), argon2::password_hash::Error> {
    let salt = match stored_salt {
        Some(s) => SaltString::from_b64(s)?,
        None => SaltString::generate(&mut OsRng),
    };

    let argon2 = Argon2::new(
        argon2::Algorithm::Argon2id, argon2::Version::V0x13,
        argon2::Params::new(65536, 3, 4, Some(32)).unwrap()  // 64MB, 3 iter, 4 threads
    );

    let mut key_bytes = [0u8; 32];
    argon2.hash_password_into(password.as_bytes(), salt.as_str().as_bytes(), &mut key_bytes)?;
    let key_hex = Zeroizing::new(format!("x'{}'", hex::encode(key_bytes)));
    key_bytes.zeroize();

    Ok((key_hex, salt.as_str().to_string()))
}
5.3 OS キーチェーンとの統合
use keyring::Entry;
use zeroize::Zeroizing;

pub struct SecureKeyStorage { service: String }

impl SecureKeyStorage {
    pub fn new(app_name: &str) -> Self {
        Self { service: format!("{}-sqlcipher", app_name) }
    }

    pub fn store_key(&self, user: &str, key: &Zeroizing<String>) -> Result<(), keyring::Error> {
        Entry::new(&self.service, user)?.set_password(key.as_str())
    }

    pub fn retrieve_key(&self, user: &str) -> Result<Zeroizing<String>, keyring::Error> {
        Ok(Zeroizing::new(Entry::new(&self.service, user)?.get_password()?))
    }
}
5.4 鍵ローテーションの実装
impl EncryptedDatabase {
    pub fn rotate_key(&self, new_key: &Zeroizing<String>, backup_path: &Path) -> Result<()> {
        self.backup_database(backup_path)?;                              // Step 1: Backup
        self.conn.pragma_update(None, "rekey", new_key.as_str())?;       // Step 2: Re-encrypt

        // Step 3: Verify new key works
        let test: i32 = self.conn.pragma_query_value(None, "cipher_page_size", |row| row.get(0))?;
        if test == 0 {
            std::fs::copy(backup_path, self.path())?;  // Restore on failure
            return Err(rusqlite::Error::InvalidQuery);
        }
        Ok(())
    }
}

6. パフォーマンスパターン

6.1 ページサイズの最適化
# Good: Optimize page size for workload
conn.execute("PRAGMA cipher_page_size = 4096")  # Default, good for mixed
conn.execute("PRAGMA cipher_page_size = 8192")  # Better for large BLOBs
conn.execute("PRAGMA cipher_page_size = 1024")  # Better for small records

# Bad: Using default without consideration
conn.execute("PRAGMA key = ...")
# No page size optimization
6.2 暗号化方式の設定調整
# Good: Balance security and performance
conn.executescript("""
    PRAGMA kdf_iter = 256000;           -- Strong but not excessive
    PRAGMA cipher_plaintext_header_size = 32;  -- Allow mmap optimization
    PRAGMA cipher_use_hmac = ON;        -- Required for integrity
""")

# Bad: Excessive iterations slowing operations
conn.execute("PRAGMA kdf_iter = 1000000")  -- Unnecessary, hurts open time
6.3 接続と鍵のキャッシュ
# Good: Cache connection, derive key once
class DatabasePool:
    _instance = None
    _key_cache = {}

    def get_connection(self, db_name: str, password: str):
        if db_name not in self._key_cache:
            self._key_cache[db_name] = derive_key(password)
        return EncryptedDatabase(db_name, self._key_cache[db_name])

# Bad: Deriving key on every operation
def query(password, sql):
    key = derive_key(password)  # Expensive! ~100ms each time
    db = EncryptedDatabase("app.db", key)
    return db.execute(sql)
6.4 暗号化を使用した WAL モード
# Good: Enable WAL for concurrent reads
conn.executescript("""
    PRAGMA key = ...;
    PRAGMA journal_mode = WAL;
    PRAGMA synchronous = NORMAL;        -- Faster, still safe with WAL
    PRAGMA wal_autocheckpoint = 1000;   -- Checkpoint every 1000 pages
""")

# Bad: Default journal mode
conn.execute("PRAGMA key = ...")
# Uses DELETE journal - slower, blocks readers
6.5 メモリセキュリティのトレードオフ
# Good: Enable memory security for sensitive apps
conn.execute("PRAGMA cipher_memory_security = ON")  # Zeros freed memory

# Good: Disable for performance-critical, lower-security contexts
conn.execute("PRAGMA cipher_memory_security = OFF")  # 10-15% faster

# Bad: No explicit choice - relying on default

7. セキュリティ標準

7.1 脆弱性の状況

重要:SQLCipher は SQLite と OpenSSL の両方を継承しているため、両方の CVE を監視してください。

CVE深刻度対策
CVE-2020-27207高SQLCipher 4.4.1+ に更新
CVE-2024-0232中SQLCipher 4.9+ に更新
CVE-2023-2650高OpenSSL を 3.1.1+ に更新
7.2 OWASP マッピング
OWASP カテゴリーリスク主な制御策
A02:2021 - 暗号化の失敗重大強力な KDF、安全な鍵の保管
A03:2021 - インジェクション重大パラメーター化クエリー
A04:2021 - 安全でない設計高鍵のローテーション、安全な削除
7.3 鍵管理ルール
  1. 暗号化キーをハードコードしないでください
  2. 強力な KDF を使用してください(Argon2id > 256000 回以上の反復を行う PBKDF2)
  3. キーは OS のキーチェーン/認証情報マネージャーに保管してください
  4. 使用後はメモリ内のキーをゼロ化してください
  5. 鍵のローテーション手順を実装してください
// WRONG: conn.pragma_update(None, "key", "hardcoded-key")?;
// CORRECT:
let (key, salt) = derive_key_from_password(password, stored_salt)?;
conn.pragma_update(None, "key", key.as_str())?;  // key auto-zeroed on drop

8. よくある間違い

ハードコードされたキー
// WRONG: conn.pragma_update(None, "key", "my-secret")?;
// CORRECT: Use derived key with Zeroizing wrapper
脆弱な鍵導出
// WRONG: let key = sha256(password);
// WRONG: conn.pragma_update(None, "kdf_iter", 10000)?;
// CORRECT: Argon2id or PBKDF2 with 256000+ iterations
検証の欠如
// Always verify encryption is active after setting key
let page_size: i32 = conn.pragma_query_value(None, "cipher_page_size", |row| row.get(0))?;
if page_size == 0 { return Err(Error::EncryptionNotActive); }
安全でないバックアップ
// WRONG: Export with empty key (unencrypted backup)
// CORRECT: Use encrypted backup with separate key

9. 実装前チェックリスト

フェーズ 1:コードを書く前
  • references/threat-model.md の脅威モデルを読む
  • 暗号化の要件を特定する(コンプライアンス、データの機密性)
  • KDF のパラメーターを選択する(Argon2id を推奨)
  • 鍵の保管戦略を計画する(OS のキーチェーン、ハードウェアトークン)
  • 鍵のローテーション手順を設計する
  • すべての暗号化操作について、失敗するテストを書く
フェーズ 2:実装中
  • 接続後、最初の操作として PRAGMA key を実行する
  • cipher_compatibility = 4、cipher_memory_security = ON
  • すべてのキーを Zeroizing コンテナーでラップする
  • キー設定後に検証クエリーを実行する
  • パラメーター化クエリーのみを使用する(文字列補間は禁止)
  • パフォーマンスパターンを適用する(ページサイズ、WAL モード)
フェーズ 3:コミット前
  • 暗号化の検証を含むすべてのテストに合格している
  • コードベースにハードコードされたキーがない
  • 鍵導出で 256000 回以上の反復を使用している
  • OpenSSL と SQLite の CVE を確認している
  • 機密テーブルで secure_delete = ON にしている
  • バックアップの暗号化をテストしている
  • ファイルのアクセス許可を 600 に設定している
  • 鍵のローテーション手順を文書化し、テストしている

10. まとめ

目標は、次の特性を備えた SQLCipher 実装を作成することです。

  • テスト駆動:すべての暗号化操作を、まずテストで検証する
  • パフォーマンス最適化:適切なページサイズ、WAL モード、キーのキャッシュ
  • 暗号学的に安全:適切な鍵導出を伴う強力な AES-256
  • 鍵管理のベストプラクティス:安全な保管、ローテーション、メモリの取り扱い
  • 回復力:鍵の漏えいと復旧のシナリオに備える

セキュリティに関する注意:暗号化の強度は鍵管理の強度に左右されます。キーをハードコードしないでください。必ず強力な KDF を使用してください。必ずローテーションを計画してください。


参考資料

  • セキュリティの例:references/security-examples.md - 完全な実装例
  • 高度なパターン:references/advanced-patterns.md - 移行、パフォーマンス
  • 脅威モデル:references/threat-model.md - セキュリティアーキテクチャ

コメント