LESSON 40分

ストーリー

佐藤CTO
GDPR違反で数百億円の制裁金を課された企業を知っているか?
佐藤CTO
データプライバシーは”後からつけるもの”ではない。Privacy by Designで設計段階から組み込む。GDPR、個人情報保護法、匿名化技術、同意管理 — データアーキテクトとして必須の知識だ

主要なプライバシー規制

規制地域主な要件制裁金
GDPREU/EEA同意取得、データポータビリティ、忘れられる権利、DPO設置最大2,000万EUR or 年間売上4%
個人情報保護法日本利用目的の特定、第三者提供の制限、安全管理措置1億円以下(法人)
CCPA/CPRAカリフォルニアオプトアウト権、販売禁止要求権、データ開示要求1件あたり最大$7,500
PIPL中国データのローカライゼーション、同意取得、越境移転の制限年間売上5%以下

Privacy by Design の原則

// Privacy by Design の7原則をアーキテクチャに組み込む

interface PrivacyByDesignPrinciple {
  principle: string;
  architecturalImplementation: string;
  technicalControl: string;
}

const privacyPrinciples: PrivacyByDesignPrinciple[] = [
  {
    principle: '1. 事後対応ではなく予防',
    architecturalImplementation: 'データパイプラインにPII検出を組み込み、分類前のデータ利用を禁止',
    technicalControl: 'Ingestion時の自動PII検出 + ブロッキングルール',
  },
  {
    principle: '2. デフォルトでプライバシー保護',
    architecturalImplementation: '新規テーブルはデフォルトで最小権限、PIIカラムはデフォルト暗号化',
    technicalControl: 'Column-level encryption + RBAC デフォルトポリシー',
  },
  {
    principle: '3. 設計に組み込む',
    architecturalImplementation: 'データモデル設計時にPII分類カラムを必須化',
    technicalControl: 'スキーマバリデーションでpii_classificationフィールドを要求',
  },
  {
    principle: '4. 全機能の確保(ゼロサムではない)',
    architecturalImplementation: 'プライバシーと分析を両立する匿名化パイプラインの設計',
    technicalControl: 'k-匿名化、差分プライバシーによる分析可能な匿名データの生成',
  },
  {
    principle: '5. エンドツーエンドのセキュリティ',
    architecturalImplementation: 'データのライフサイクル全体で暗号化と監査',
    technicalControl: '転送中(TLS) + 保存時(AES-256) + 処理中(Confidential Computing)',
  },
  {
    principle: '6. 透明性',
    architecturalImplementation: 'データリネージュで個人データの流れを完全に追跡可能',
    technicalControl: 'DataHubリネージュ + 同意管理ダッシュボード',
  },
  {
    principle: '7. ユーザー中心',
    architecturalImplementation: '本人からのデータ開示・削除要求にAPIで自動対応',
    technicalControl: 'DSAR(Data Subject Access Request)自動処理パイプライン',
  },
];

匿名化・仮名化技術

// 匿名化手法の実装

interface AnonymizationTechnique {
  name: string;
  method: string;
  reversible: boolean;
  useCase: string;
}

const techniques: AnonymizationTechnique[] = [
  {
    name: 'ハッシュ化(SHA-256)',
    method: '一方向関数で変換',
    reversible: false,
    useCase: 'ユーザーIDの匿名化(結合分析は可能)',
  },
  {
    name: '仮名化(Pseudonymization)',
    method: '鍵付きでIDを別の識別子に置換',
    reversible: true,
    useCase: 'GDPR準拠の分析(必要時に再特定可能)',
  },
  {
    name: 'k-匿名化',
    method: '同一の準識別子を持つレコードがk件以上存在するよう一般化',
    reversible: false,
    useCase: '統計データの公開(再特定リスクの低減)',
  },
  {
    name: '差分プライバシー',
    method: '統計結果にノイズを追加',
    reversible: false,
    useCase: '集計値の公開(個別レコードの特定を防止)',
  },
  {
    name: 'データマスキング',
    method: '元データの一部を置換(例: メール → t***@***.com)',
    reversible: false,
    useCase: '開発・テスト環境でのデータ利用',
  },
];
-- PostgreSQL: カラムレベルの匿名化パイプライン

-- 仮名化関数: 鍵付きHMAC
CREATE OR REPLACE FUNCTION pseudonymize(
    original_value TEXT,
    secret_key TEXT DEFAULT current_setting('app.pseudonymization_key')
)
RETURNS TEXT AS $$
BEGIN
    RETURN encode(
        hmac(original_value, secret_key, 'sha256'),
        'hex'
    );
END;
$$ LANGUAGE plpgsql IMMUTABLE;

-- k-匿名化: 年齢の一般化
CREATE OR REPLACE FUNCTION generalize_age(age INT)
RETURNS TEXT AS $$
BEGIN
    RETURN CASE
        WHEN age < 20 THEN '10代以下'
        WHEN age < 30 THEN '20代'
        WHEN age < 40 THEN '30代'
        WHEN age < 50 THEN '40代'
        WHEN age < 60 THEN '50代'
        ELSE '60代以上'
    END;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

-- 匿名化ビュー: 分析チームに提供
CREATE VIEW anonymized_customers AS
SELECT
    pseudonymize(customer_id) AS customer_pseudo_id,
    generalize_age(
        EXTRACT(YEAR FROM AGE(CURRENT_DATE, date_of_birth))::INT
    ) AS age_group,
    prefecture AS region,  -- 市区町村は非表示
    tier,
    registration_date,
    -- 直接的な個人情報は除外
    -- full_name, email, phone, address は含めない
    total_transaction_count,
    total_amount
FROM customers;

同意管理(Consent Management)

// 同意管理システムの設計

interface ConsentRecord {
  consentId: string;
  userId: string;
  purposes: ConsentPurpose[];
  collectedAt: string;
  expiresAt: string;
  source: 'web' | 'app' | 'api' | 'paper';
  version: string;          // 同意規約のバージョン
  ipAddress: string;
  userAgent: string;
}

interface ConsentPurpose {
  purposeId: string;
  name: string;
  granted: boolean;
  legalBasis: 'consent' | 'legitimate_interest' | 'contract' | 'legal_obligation';
}

// 同意目的の定義
const consentPurposes: ConsentPurpose[] = [
  {
    purposeId: 'CORE_SERVICE',
    name: 'サービス提供に必要なデータ処理',
    granted: true,  // 契約履行のため同意不要(法的根拠: contract)
    legalBasis: 'contract',
  },
  {
    purposeId: 'ANALYTICS',
    name: 'サービス改善のための匿名化分析',
    granted: true,
    legalBasis: 'legitimate_interest',
  },
  {
    purposeId: 'MARKETING',
    name: 'マーケティングメール・プッシュ通知',
    granted: false,  // 明示的な同意が必要
    legalBasis: 'consent',
  },
  {
    purposeId: 'THIRD_PARTY',
    name: '第三者へのデータ提供',
    granted: false,  // 明示的な同意が必要
    legalBasis: 'consent',
  },
];

// DSAR(Data Subject Access Request)自動処理
interface DSARRequest {
  requestId: string;
  userId: string;
  requestType: 'access' | 'rectification' | 'erasure' | 'portability' | 'restriction';
  status: 'received' | 'processing' | 'completed' | 'rejected';
  slaDeadline: string;   // GDPR: 30日以内
}

class DSARProcessor {
  async processAccessRequest(userId: string): Promise<DataExport> {
    // 全データソースからユーザーデータを収集
    const userData = await Promise.all([
      this.queryDB('SELECT * FROM customers WHERE id = $1', [userId]),
      this.queryDB('SELECT * FROM transactions WHERE customer_id = $1', [userId]),
      this.queryDB('SELECT * FROM consent_records WHERE user_id = $1', [userId]),
      this.queryObjectStorage(`user-data/${userId}/`),
    ]);

    return {
      format: 'JSON',
      data: this.mergeAndFormat(userData),
      generatedAt: new Date().toISOString(),
    };
  }

  async processErasureRequest(userId: string): Promise<ErasureReport> {
    // 1. 法的保持義務のあるデータを特定(削除不可)
    const retentionCheck = await this.checkLegalRetention(userId);

    // 2. 削除可能なデータを物理削除
    const deletedSources = await this.deleteFromAllSources(userId, retentionCheck.exempt);

    // 3. 法的保持対象データは匿名化
    const anonymizedSources = await this.anonymizeRetainedData(userId, retentionCheck.retained);

    // 4. バックアップからの削除をスケジュール
    await this.scheduleBackupPurge(userId);

    return {
      deletedSources,
      anonymizedSources,
      retainedWithReason: retentionCheck.retained,
      completedAt: new Date().toISOString(),
    };
  }
}

コンプライアンスチェックの自動化

// パイプラインに組み込むコンプライアンスチェック

interface ComplianceCheck {
  checkId: string;
  name: string;
  regulation: string;
  automated: boolean;
  implementation: string;
}

const complianceChecks: ComplianceCheck[] = [
  {
    checkId: 'CC-001',
    name: 'PII自動検出',
    regulation: 'GDPR / 個人情報保護法',
    automated: true,
    implementation: 'Ingestion時にカラム名 + サンプルデータで自動分類、未分類PIIはブロック',
  },
  {
    checkId: 'CC-002',
    name: '同意チェック',
    regulation: 'GDPR Article 6',
    automated: true,
    implementation: 'マーケティング用データは同意済みユーザーのみ抽出するフィルタをパイプラインに組み込み',
  },
  {
    checkId: 'CC-003',
    name: 'データ保持期限チェック',
    regulation: 'GDPR Article 5(1)(e)',
    automated: true,
    implementation: 'TTLポリシーの自動適用 + 期限超過データの月次削除ジョブ',
  },
  {
    checkId: 'CC-004',
    name: '越境移転チェック',
    regulation: 'GDPR Chapter 5 / PIPL',
    automated: false,
    implementation: 'データフローマップで国境を越える転送を可視化、SCC/BCRの有無を確認',
  },
  {
    checkId: 'CC-005',
    name: '匿名化品質検証',
    regulation: 'GDPR Recital 26',
    automated: true,
    implementation: 'k-匿名化後のデータに対して再特定リスクを統計的に評価(k≥5を保証)',
  },
];
GDPRデータ処理の法的根拠の選択フロー
データ処理の目的を特定

  ├─ サービス提供に不可欠?
  │   └─ Yes → 「契約履行」(Article 6(1)(b))

  ├─ 法的義務(規制報告等)?
  │   └─ Yes → 「法的義務」(Article 6(1)(c))

  ├─ 組織の正当な利益があり、本人の権利を侵害しない?
  │   └─ Yes → 「正当な利益」(Article 6(1)(f))
  │           ※ LIA(Legitimate Interest Assessment)を実施・記録

  └─ 上記いずれにも該当しない?
      └─ 「明示的な同意」(Article 6(1)(a))を取得
          ※ 同意は自由に撤回可能であること

まとめ

ポイント内容
主要規制GDPR、個人情報保護法、CCPA — 地域ごとの要件を把握
Privacy by Design設計段階からプライバシーを組み込む7原則
匿名化技術ハッシュ / 仮名化 / k-匿名化 / 差分プライバシー / マスキング
同意管理目的別の法的根拠、DSARの自動処理

チェックリスト

  • GDPR・個人情報保護法の主要要件を説明できる
  • Privacy by Designの原則をアーキテクチャに組み込める
  • 匿名化・仮名化の手法を使い分けられる
  • 同意管理とDSAR処理のシステムを設計できる

次のステップへ

次はデータリネージュと監査証跡について学びます。


推定読了時間: 40分