🔐
概念 #データ設計 #プライバシー #GDPR #Crypto-Shredding #イベントソーシング #DDIA 📚 データ志向アプリケーション設計(DDIA)

データプライバシーとCrypto-Shredding

GDPRの「忘れられる権利」をイベントソーシングで実現するCrypto-Shreddingパターン。データの匿名化・仮名化・暗号化の使い分けと、削除できない不変ログへの対処法を理解する

定義

Crypto-Shredding(暗号シュレッディング):データを暗号化した状態で保存し、削除が必要になったとき暗号鍵を破棄することでデータを「論理的に削除」するパターン。物理的なデータは残るが、鍵なしでは復元不可能になる。

なぜプライバシーがデータ設計に関わるか

GDPR(EU一般データ保護規則)の要件:
  忘れられる権利(Right to Erasure):
    ユーザーが削除を要求したとき、そのユーザーのデータを消去する義務
    
  問題:
    イベントソーシング: イベントログは不変(削除できない)
    バックアップ: 古いバックアップのデータも消す必要がある
    レプリケーション: 全レプリカのデータを消す必要がある
    BigQuery等のDWH: 大量データから特定ユーザーを削除するのが困難
    
    「データを消したい」vs「不変ログを維持したい」の矛盾

匿名化・仮名化・暗号化の違い

匿名化(Anonymization):
  個人を特定できないように不可逆的に変換
  氏名・メールを削除、生年月日を年だけに、郵便番号を上3桁に
  → 再特定が不可能 → GDPRの対象外になる
  → 一度やったら元に戻せない

仮名化(Pseudonymization):
  実際の識別子を別のIDに置き換え
  user_id=1234 → pseudo_id=X9K2M
  マッピングテーブルを持つので再特定可能(GDPRの対象のまま)
  → 漏洩時のリスク低減
  → セキュリティの多層防御

暗号化(Encryption):
  鍵があれば復元できる
  保存中(at rest)と転送中(in transit)の両方に適用
  Crypto-Shreddingの基盤

Crypto-Shreddingの仕組み

通常のイベントソーシング:
  イベントログに生データを保存
  user_id=1234, email=alice@example.com, action=purchase

Crypto-Shredding:
  ユーザーごとに専用の暗号鍵を生成
  個人情報を暗号化してからイベントログに保存
  
イベントログ:
  user_id=1234,
  email=ENC(alice@example.com, key_1234),  ← 暗号化済み
  action=purchase

鍵管理テーブル(削除可能なDB):
  user_id=1234 → key_1234

削除要求:
  1. 鍵管理テーブルから key_1234 を削除
  2. イベントログは変更しない
  3. 以降、ENC(...)の復号が不可能 → 事実上削除

実装パターン

// 鍵管理サービス
class UserKeyService {
  async getOrCreateKey(userId: string): Promise<Buffer> {
    const existing = await db.query(
      'SELECT encryption_key FROM user_keys WHERE user_id = $1',
      [userId]
    );
    if (existing.rows[0]) {
      return Buffer.from(existing.rows[0].encryption_key, 'base64');
    }
    // 新しい鍵を生成
    const key = crypto.randomBytes(32);
    await db.query(
      'INSERT INTO user_keys (user_id, encryption_key) VALUES ($1, $2)',
      [userId, key.toString('base64')]
    );
    return key;
  }

  async deleteKey(userId: string): Promise<void> {
    // これだけでそのユーザーの全個人情報が「消える」
    await db.query('DELETE FROM user_keys WHERE user_id = $1', [userId]);
  }
}

// イベントの保存
async function saveEvent(userId: string, eventData: EventData) {
  const key = await userKeyService.getOrCreateKey(userId);

  // 個人情報のみ暗号化
  const encryptedPayload = encrypt(
    JSON.stringify({ email: eventData.email, name: eventData.name }),
    key
  );

  await kafka.send({
    topic: 'user-events',
    messages: [{
      key: userId,
      value: JSON.stringify({
        userId,
        action: eventData.action,        // 非個人情報はそのまま
        encryptedPii: encryptedPayload,  // 個人情報は暗号化
        timestamp: new Date().toISOString(),
      }),
    }],
  });
}

フィールドレベル暗号化

特定のカラムだけ暗号化する方法。

-- PostgreSQLのpgcryptoを使う
CREATE EXTENSION pgcrypto;

-- 保存時に暗号化
INSERT INTO users (id, name, email_encrypted)
VALUES (
  1,
  'Alice',
  pgp_sym_encrypt('alice@example.com', 'encryption-key-from-env')
);

-- 読み取り時に復号
SELECT
  id,
  name,
  pgp_sym_decrypt(email_encrypted, 'encryption-key-from-env') AS email
FROM users
WHERE id = 1;

注意:暗号化したカラムにはインデックスが効かない。メールアドレスで検索する場合は、ハッシュ値(HMAC)を別カラムに持つ。

-- 検索用のHMACカラム(暗号化とは別に保持)
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  email_encrypted BYTEA,     -- 復号可能な暗号化(表示用)
  email_hmac BYTEA           -- HMAC(検索用、元値に戻せない)
);

CREATE INDEX ON users (email_hmac);  -- インデックス可能

-- 検索
SELECT * FROM users
WHERE email_hmac = hmac('alice@example.com', 'hmac-secret', 'sha256');

データプライバシーの設計チェックリスト

収集フェーズ:
  □ 収集する個人情報は本当に必要か(データ最小化原則)
  □ 取得目的を明示しているか(目的制限原則)
  □ 同意を記録しているか

保存フェーズ:
  □ 保存期間を設定しているか(ストレージ制限原則)
  □ at-rest暗号化を有効にしているか
  □ フィールドレベル暗号化が必要なカラムはあるか

削除フェーズ:
  □ 削除要求のワークフローが存在するか
  □ バックアップからも削除できるか(Crypto-Shreddingの採用)
  □ 連携している下流サービスへの削除通知はあるか

アクセス制御:
  □ Row Level Securityを設定しているか
  □ 個人情報へのアクセスを監査ログに記録しているか

Kafkaでのデータ削除

Kafkaのトピックは不変のログが原則

対策1: Compactionトピック + トゥームストーン
  key=user_id, value=null のメッセージを送信
  → Compaction時に過去の同キーのメッセージが削除される
  → 個人情報の論理削除

対策2: Crypto-Shredding(推奨)
  個人情報を暗号化してKafkaに保存
  鍵を削除すれば過去のイベントも事実上削除

関連概念

出典・参考文献

  • Martin Kleppmann, Designing Data-Intensive Applications (2017) Chapter 12
  • GDPR Article 17, “Right to Erasure” — gdpr-info.eu
  • Michiel Rook, “Event Sourcing and the GDPR: a Primer” (2017)
  • AWS, “Implementing GDPR Compliant Architectures on AWS” — aws.amazon.com
  1. 1. 🗄️データ志向アプリケーション設計:概要
  2. 2. 🧩データモデルとクエリ言語
  3. 3. 💾ストレージエンジンとインデックス
  4. 4. 🔁レプリケーション
  5. 5. 🍕パーティショニング(シャーディング)
  6. 6. 🔒トランザクションとACID
  7. 7. 分散システムの本質的な問題
  8. 8. 🤝一貫性と分散合意
  9. 9. 📦バッチ処理
  10. 10. 🌊ストリーム処理
  11. 11. 📋エンコーディングとスキーマ進化
  12. 12. 🔗Sagaパターンと分散トランザクション
  13. 13. 🏗️データシステムの統合設計
  14. 14. 📸MVCC(多版型同時実行制御)
  15. 15. 📊列指向ストレージとOLAP設計
  16. 16. 🕰️ベクタークロックと因果順序
  17. 17. 🔀CRDT(競合なし複製データ型)
  18. 18. 🔍クエリオプティマイザーと実行計画
  19. 19. キャッシュ戦略とRedis設計
  20. 20. 🔎全文検索と転置インデックス
  21. 21. 🌐NewSQL(分散ACIDデータベース)
  22. 22. 📝WALと論理レプリケーション
  23. 23. 🔌コネクションプーリング
  24. 24. 🚧ゼロダウンタイムマイグレーション
  25. 25. 🆔分散ID生成
  26. 26. 🔄N+1問題とDataLoaderパターン
  27. 27. 📈タイムシリーズDB
  28. 28. 🛡️Row Level Security(行レベルセキュリティ)
  29. 29. 📤Outboxパターン(トランザクショナルアウトボックス)
  30. 30. 💾DBバックアップとPITR
  31. 31. ⚠️データベース設計アンチパターン
  32. 32. 🕸️グラフDB深掘り
  33. 33. 🔋バックプレッシャーとサーキットブレーカー
  34. 34. 🔵コンシステントハッシング
  35. 35. 📋マテリアライズドビュー
  36. 36. 📡DBモニタリングとオブザーバビリティ
  37. 37. 🔐データプライバシーとCrypto-Shredding
  38. 38. ✂️垂直分割(Vertical Partitioning)
  39. 39. 🪟ウィンドウ関数
  40. 40. 🧲ベクトルDBとpgvector
  41. 41. 🔧dbtとデータ変換パイプライン
  42. 42. 📬ジョブキューの設計
  43. 43. 📐正規化理論(1NF〜BCNF)
  44. 44. ☁️クラウドDBサービスの設計思想と選択基準
  45. 45. 🗺️地理空間データとPostGIS
  46. 46. 🔑DBセキュリティと権限管理
  47. 47. 🏔️Lakehouse(Apache Iceberg / Delta Lake)
  48. 48. 📜データコントラクト
  49. 49. 🔭OpenTelemetryとDBトレーシング

出典: Martin Kleppmann, 'Designing Data-Intensive Applications' (2017) Chapter 12