メタデータ駆動の暗号化管理システムを実装しました。暗号化管理テーブル(ENCRYPTED_FIELDS)に登録された情報を元に、パスワード変更時の自動再暗号化を実現します。
暗号化が必要なフィールドをENCRYPTED_FIELDSテーブルで管理することで、以下のメリットがあります:
- 柔軟性: 新しい暗号化フィールドをコード変更なしで追加可能
- 保守性: 暗号化対象フィールドが一元管理され、把握しやすい
- 拡張性: フィールドのアクティブ/非アクティブ切り替えが容易
パスワード変更要求
↓
旧パスワードで認証
↓
ENCRYPTED_FIELDSテーブルから暗号化対象を取得
↓
各テーブル・フィールドを順次処理:
- 旧パスワードで復号
- 新パスワードで再暗号化
- データベース更新(トランザクション)
↓
パスワードハッシュ更新
CREATE TABLE IF NOT EXISTS ENCRYPTED_FIELDS (
FIELD_ID INTEGER NOT NULL,
TABLE_NAME VARCHAR(128) NOT NULL, -- 暗号化フィールドを持つテーブル名
COLUMN_NAME VARCHAR(128) NOT NULL, -- 暗号化フィールドのカラム名
DESCRIPTION VARCHAR(256), -- フィールドの説明
IS_ACTIVE INTEGER NOT NULL DEFAULT 1, -- アクティブフラグ (1=有効, 0=無効)
ENTRY_DT DATETIME NOT NULL,
PRIMARY KEY(FIELD_ID),
UNIQUE(TABLE_NAME, COLUMN_NAME)
);-- 例: メモテーブルの暗号化フィールド登録
INSERT INTO ENCRYPTED_FIELDS (FIELD_ID, TABLE_NAME, COLUMN_NAME, DESCRIPTION, IS_ACTIVE, ENTRY_DT)
VALUES (1, 'MEMOS', 'CONTENT', 'メモ本文(暗号化)', 1, datetime('now'));
INSERT INTO ENCRYPTED_FIELDS (FIELD_ID, TABLE_NAME, COLUMN_NAME, DESCRIPTION, IS_ACTIVE, ENTRY_DT)
VALUES (2, 'NOTES', 'SECRET_TEXT', '秘密メモ(暗号化)', 1, datetime('now'));暗号化管理サービスの実装
EncryptedField: 暗号化フィールド情報EncryptionError: エラー型EncryptionService: 暗号化管理サービス
- アクティブな暗号化フィールド一覧を取得
- 戻り値:
Vec<EncryptedField>
- 新しい暗号化フィールドを登録
- 戻り値: フィールドID
- ユーザの全暗号化データを再暗号化
- トランザクション処理で一括更新
- 手順:
- ENCRYPTED_FIELDSから対象フィールド取得
- テーブルごとにグループ化
- 各レコードを旧パスワードで復号
- 新パスワードで再暗号化
- データベース更新(コミット)
- データを暗号化
- ユーザIDをソルトとして使用
- AES-256-GCMで暗号化
- データを復号
- ユーザIDをソルトとして使用
- 旧パスワード検証付きの一般ユーザ更新
- パスワード変更時に自動的に再暗号化実行
- 旧パスワードが間違っている場合はエラー
- 旧パスワード検証付きの管理者ユーザ更新
- パスワード変更時に自動的に再暗号化実行
- 旧パスワードが間違っている場合はエラー
update_user_internal(): 再暗号化を含む内部更新処理re_encrypt_user_data(): EncryptionServiceへの委譲
- 再暗号化付きの一般ユーザ更新
- フロントエンド呼び出し例:
await invoke('update_general_user_with_reencryption', {
user_id: 2,
old_password: 'current_password',
username: null,
new_password: 'new_secure_password'
});- 再暗号化付きの管理者ユーザ更新
- フロントエンド呼び出し例:
await invoke('update_admin_user_with_reencryption', {
user_id: 1,
old_password: 'admin_current_password',
username: 'superadmin',
new_password: 'new_admin_password'
});- 登録済み暗号化フィールド一覧を取得
- フロントエンド呼び出し:
invoke('list_encrypted_fields')
- 新しい暗号化フィールドを登録
- フロントエンド呼び出し例:
await invoke('register_encrypted_field', {
table_name: 'MEMOS',
column_name: 'CONTENT',
description: 'メモ本文(暗号化)'
});- アルゴリズム: AES-256-GCM
- 鍵導出: Argon2id (パスワード + ユーザID)
- ソルト: ユーザIDをLE bytes形式で使用
- 再暗号化前に旧パスワードを必ず検証
- 検証失敗時は処理を中断
- 再暗号化処理は全てトランザクション内で実行
- 一つでも失敗した場合はロールバック
- データ整合性を保証
test_register_encrypted_field: 暗号化フィールド登録test_encrypt_decrypt_field: 暗号化・復号化test_re_encrypt_user_data: 再暗号化処理test_decrypt_with_wrong_password_fails: 誤パスワードでの復号失敗
# 暗号化サービスのテスト
cargo test services::encryption::tests --lib
# 全テスト
cargo test --lib// バックエンド(またはマイグレーション)
let encryption_service = EncryptionService::new(pool);
encryption_service.register_encrypted_field(
"MEMOS",
"CONTENT",
Some("メモ本文")
).await?;let user_id = 1;
let password = "user_password";
let plaintext = "機密情報";
let encrypted = encryption_service.encrypt_field(
user_id,
password,
plaintext
).await?;
// データベースに保存
sqlx::query("INSERT INTO MEMOS (USER_ID, CONTENT) VALUES (?, ?)")
.bind(user_id)
.bind(encrypted)
.execute(&pool)
.await?;// ユーザがパスワードを変更
user_mgmt_service.update_general_user_with_password(
user_id,
"old_password", // 旧パスワード
None, // ユーザ名変更なし
Some("new_password") // 新パスワード
).await?;
// 内部で自動的に:
// 1. 旧パスワード検証
// 2. ENCRYPTED_FIELDSから対象フィールド取得
// 3. 全暗号化データを再暗号化
// 4. パスワードハッシュ更新let encrypted = "..."; // DBから取得
let decrypted = encryption_service.decrypt_field(
user_id,
password,
encrypted
).await?;DatabaseError: データベースエラーSecurityError: セキュリティエラーDecryptionFailed: 復号失敗(パスワード間違いなど)EncryptionFailed: 暗号化失敗NoEncryptedFields: 暗号化フィールド未登録
- 再暗号化は単一トランザクションで実行
- 大量データの場合はバッチ処理を検討
- 同じテーブルのフィールドをまとめて処理
- 不要なSELECT/UPDATEを削減
CREATE INDEX idx_encrypted_fields_active
ON ENCRYPTED_FIELDS(IS_ACTIVE)
WHERE IS_ACTIVE = 1;大量データの場合、バッチ処理で段階的に再暗号化
暗号化アルゴリズムのバージョン管理とマイグレーション
再暗号化処理の実行履歴を記録
再暗号化をバックグラウンドジョブとして実行
✅ 実装完了機能:
- 暗号化管理テーブル(ENCRYPTED_FIELDS)
- メタデータ駆動の暗号化フィールド管理
- パスワード変更時の自動再暗号化
- トランザクション処理による整合性保証
- 旧パスワード検証機能
- AES-256-GCM暗号化
- Argon2id鍵導出
この設計により、将来的に暗号化フィールドを追加する際も、データベースに登録するだけでシステムが自動的に再暗号化処理に組み込まれます。