LESSON 40分

ストーリー

佐藤CTO
ゼロトラストの最終防衛ラインはデータそのもの

佐藤CTOがデータフロー図に赤い丸をつけました。

佐藤CTO
ネットワークを突破されても、認証を突破されても、データが暗号化されていれば最後の砦になる
あなた
保存時も通信時も暗号化ということですか?
佐藤CTO
At Rest、In Transit、そしてIn Use。さらにGDPRや個人情報保護法の要件も満たさなければならない。データライフサイクル全体を守る方法を学ぼう

暗号化の基本

暗号化の種類

種類説明ユースケース
対称暗号暗号化と復号に同じ鍵データの暗号化AES-256-GCM
非対称暗号公開鍵で暗号化、秘密鍵で復号鍵交換、デジタル署名RSA, ECDSA
ハッシュ不可逆の一方向変換パスワード保存、整合性検証SHA-256, Argon2
エンベロープ暗号化データ鍵でデータを暗号化し、マスター鍵でデータ鍵を暗号化KMS連携AWS KMS

エンベロープ暗号化の仕組み

graph LR
    subgraph Envelope["エンベロープ暗号化"]
        CMK["マスターキー<br/>(CMK/KEK)<br/>KMS内で管理<br/>外部に出ない"] -->|鍵を暗号化/復号| DEK["データキー(DEK)<br/>・データの暗号化に使用<br/>・暗号化されてデータと<br/>一緒に保存"]
    end

    Enc["暗号化: DEKでデータを暗号化<br/>→ CMKでDEKを暗号化"]
    Dec["復号: CMKでDEKを復号<br/>→ DEKでデータを復号"]

    classDef master fill:#e94560,stroke:#c23050,color:#fff
    classDef datakey fill:#0d6efd,stroke:#0a58ca,color:#fff
    classDef info fill:#6c757d,stroke:#495057,color:#fff

    class CMK master
    class DEK datakey
    class Enc,Dec info

Encryption at Rest(保存時暗号化)

データベースの暗号化

// Prismaでのフィールドレベル暗号化
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';

const ALGORITHM = 'aes-256-gcm';

interface EncryptedField {
  ciphertext: string;
  iv: string;
  tag: string;
  keyId: string;  // KMSキーのID
}

async function encryptField(plaintext: string, keyId: string): Promise<EncryptedField> {
  // KMSからデータキーを生成
  const { plainKey, encryptedKey } = await kmsClient.generateDataKey(keyId);

  const iv = randomBytes(12);
  const cipher = createCipheriv(ALGORITHM, plainKey, iv);

  let ciphertext = cipher.update(plaintext, 'utf8', 'base64');
  ciphertext += cipher.final('base64');
  const tag = cipher.getAuthTag();

  // メモリ上の平文鍵を即座に消去
  plainKey.fill(0);

  return {
    ciphertext,
    iv: iv.toString('base64'),
    tag: tag.toString('base64'),
    keyId,
  };
}

async function decryptField(encrypted: EncryptedField): Promise<string> {
  const plainKey = await kmsClient.decrypt(encrypted.keyId);

  const decipher = createDecipheriv(
    ALGORITHM,
    plainKey,
    Buffer.from(encrypted.iv, 'base64')
  );
  decipher.setAuthTag(Buffer.from(encrypted.tag, 'base64'));

  let plaintext = decipher.update(encrypted.ciphertext, 'base64', 'utf8');
  plaintext += decipher.final('utf8');

  plainKey.fill(0);
  return plaintext;
}

// Prismaミドルウェアでの透過的暗号化
prisma.$use(async (params, next) => {
  const sensitiveFields = ['email', 'phoneNumber', 'address'];

  if (params.action === 'create' || params.action === 'update') {
    for (const field of sensitiveFields) {
      if (params.args.data?.[field]) {
        params.args.data[field] = await encryptField(
          params.args.data[field],
          KMS_KEY_ID
        );
      }
    }
  }

  const result = await next(params);

  if (result && params.action === 'findUnique' || params.action === 'findMany') {
    // 復号処理
    for (const field of sensitiveFields) {
      if (result[field]?.ciphertext) {
        result[field] = await decryptField(result[field]);
      }
    }
  }

  return result;
});

AWS KMS / HashiCorp Vault

ツールユースケース特徴
AWS KMSAWSリソースの暗号化マネージド、HSM裏打ち、自動ローテーション
HashiCorp Vaultマルチクラウド秘密管理動的シークレット、リース管理、監査ログ
AWS Secrets ManagerAPI鍵・DB認証情報の管理自動ローテーション、AWS統合
// HashiCorp Vault でのシークレット管理
import Vault from 'node-vault';

class SecretManager {
  private vault: Vault.client;

  constructor() {
    this.vault = Vault({
      apiVersion: 'v1',
      endpoint: process.env.VAULT_ADDR,
      token: process.env.VAULT_TOKEN,
    });
  }

  // 動的データベース認証情報の取得
  async getDatabaseCredentials(role: string): Promise<{ username: string; password: string }> {
    const result = await this.vault.read(`database/creds/${role}`);
    return {
      username: result.data.username,
      password: result.data.password,
      // lease_duration後に自動失効
    };
  }

  // Transit Secret Engine(アプリケーション暗号化)
  async encrypt(keyName: string, plaintext: string): Promise<string> {
    const result = await this.vault.write(`transit/encrypt/${keyName}`, {
      plaintext: Buffer.from(plaintext).toString('base64'),
    });
    return result.data.ciphertext;
  }

  async decrypt(keyName: string, ciphertext: string): Promise<string> {
    const result = await this.vault.write(`transit/decrypt/${keyName}`, {
      ciphertext,
    });
    return Buffer.from(result.data.plaintext, 'base64').toString();
  }
}

Encryption in Transit(通信時暗号化)

通信暗号化の実装ポイント

レイヤー対策設定
クライアント ↔ サーバーTLS 1.3ACM証明書、ALB/CloudFront
サービス間通信mTLSIstio、Linkerd
DB接続TLSRDSのSSL/TLS強制
メッセージキューTLSSQS(デフォルト暗号化)
キャッシュTLSElastiCache in-transit encryption
# RDSでのSSL/TLS強制(パラメータグループ)
# rds.force_ssl = 1

# PostgreSQL: sslmode=verify-full で接続
# DATABASE_URL=postgresql://user:pass@host:5432/db?sslmode=verify-full&sslrootcert=/path/to/rds-ca.pem

PII(個人識別情報)の保護

データ分類

分類説明必要な対策
Restricted最高機密パスワード、カード番号暗号化 + アクセス制御 + 監査
Confidential機密氏名、メールアドレス、住所暗号化 + アクセス制御
Internal内部利用社員ID、組織情報アクセス制御
Public公開商品情報、公開API改ざん防止

PII保護パターン

// PIIマスキングとトークナイゼーション
interface PiiProtection {
  tokenize(pii: string): Promise<string>;   // PII → トークン
  detokenize(token: string): Promise<string>; // トークン → PII
  mask(value: string, type: PiiType): string; // 表示用マスク
}

type PiiType = 'email' | 'phone' | 'creditCard' | 'name' | 'address';

class PiiProtector implements PiiProtection {
  async tokenize(pii: string): Promise<string> {
    // Vaultのtransitエンジンで暗号化し、トークンとして返す
    const encrypted = await vault.encrypt('pii-key', pii);
    const token = generateToken();
    await tokenStore.save(token, encrypted);
    return token;
  }

  async detokenize(token: string): Promise<string> {
    const encrypted = await tokenStore.get(token);
    if (!encrypted) throw new Error('Invalid token');
    return vault.decrypt('pii-key', encrypted);
  }

  mask(value: string, type: PiiType): string {
    switch (type) {
      case 'email': {
        const [local, domain] = value.split('@');
        return `${local[0]}${'*'.repeat(local.length - 1)}@${domain}`;
      }
      case 'phone':
        return value.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
      case 'creditCard':
        return `****-****-****-${value.slice(-4)}`;
      case 'name':
        return `${value[0]}${'*'.repeat(value.length - 1)}`;
      case 'address':
        return '***非表示***';
    }
  }
}

// ログ出力時のPIIサニタイゼーション
function sanitizeForLogging(data: Record<string, any>): Record<string, any> {
  const piiFields = ['email', 'phone', 'creditCard', 'ssn', 'address', 'name'];
  const sanitized = { ...data };

  for (const field of piiFields) {
    if (sanitized[field]) {
      sanitized[field] = '[REDACTED]';
    }
  }

  return sanitized;
}

GDPR / 個人情報保護法への対応

GDPR準拠のための技術的対策

要件GDPR条項技術的対策
データ最小化第5条(1)(c)必要最小限のデータのみ収集
保存期間の制限第5条(1)(e)TTL設定、自動削除バッチ
データポータビリティ第20条エクスポートAPI(JSON形式)
忘れられる権利第17条データ削除API、カスケード削除
同意管理第7条同意の記録と撤回機能
データ保護影響評価(DPIA)第35条リスクアセスメントの実施と文書化
// GDPR準拠のユーザーデータ管理
class GdprCompliantUserService {
  // データポータビリティ(第20条)
  async exportUserData(userId: string): Promise<UserDataExport> {
    const user = await this.userRepo.findById(userId);
    const orders = await this.orderRepo.findByUserId(userId);
    const consents = await this.consentRepo.findByUserId(userId);

    return {
      exportDate: new Date().toISOString(),
      format: 'JSON',
      data: {
        personalInfo: {
          name: user.name,
          email: user.email,
          phone: user.phone,
          address: user.address,
          registeredAt: user.createdAt,
        },
        orders: orders.map(o => ({
          orderId: o.id,
          date: o.createdAt,
          items: o.items,
          total: o.totalAmount,
        })),
        consents: consents.map(c => ({
          purpose: c.purpose,
          granted: c.granted,
          grantedAt: c.grantedAt,
          revokedAt: c.revokedAt,
        })),
      },
    };
  }

  // 忘れられる権利(第17条)
  async deleteUserData(userId: string, reason: string): Promise<DeletionReport> {
    const deletionId = generateUUID();

    // トランザクションで全関連データを削除
    await this.db.transaction(async (tx) => {
      // 1. 注文履歴の匿名化(法的保持義務があるデータ)
      await tx.order.updateMany({
        where: { userId },
        data: {
          userId: 'DELETED_USER',
          shippingAddress: null,
          billingAddress: null,
        },
      });

      // 2. ユーザーの個人情報を削除
      await tx.user.delete({ where: { id: userId } });

      // 3. 同意記録を削除
      await tx.consent.deleteMany({ where: { userId } });

      // 4. セッションとトークンを無効化
      await tx.session.deleteMany({ where: { userId } });

      // 5. 削除記録を監査ログに保存
      await tx.auditLog.create({
        data: {
          action: 'GDPR_DELETION',
          deletionId,
          reason,
          timestamp: new Date(),
          // 注意: userIdは保持しない(削除済みのため)
        },
      });
    });

    return {
      deletionId,
      status: 'completed',
      deletedAt: new Date().toISOString(),
      retainedData: ['anonymized_order_history'],
      reason: 'Legal retention requirement',
    };
  }
}

データ保護のアーキテクチャ

データライフサイクル全体の保護設計

データライフサイクルと保護対策

フェーズ対策ツール
生成入力検証、スキーマ検証Zod、Joi
転送TLS/mTLSACM、Istio
処理メモリ保護、最小権限Container isolation
保存暗号化、アクセス制御KMS、Vault
共有DLP、データマスキングMacie、カスタム
アーカイブ暗号化、アクセスログS3 Glacier + KMS
削除確実な消去、証跡暗号学的消去

まとめ

ポイント内容
エンベロープ暗号化データキーでデータを暗号化し、マスターキーでデータキーを保護
At Rest暗号化DB、ファイル、バックアップ全てを暗号化
In Transit暗号化TLS 1.3、mTLS、DB接続のSSL強制
PII保護トークナイゼーション、マスキング、ログサニタイゼーション
GDPR準拠データポータビリティ、忘れられる権利、同意管理

チェックリスト

  • エンベロープ暗号化の仕組みを説明できる
  • KMS / Vault を使った鍵管理を設計できる
  • フィールドレベル暗号化を実装できる
  • PII保護のパターン(トークナイゼーション、マスキング)を理解した
  • GDPRの技術的要件と実装方法を把握した

次のステップへ

次は「演習:ゼロトラストアーキテクチャを設計しよう」です。Step 2で学んだゼロトラスト、認証・認可、ネットワーク、データ保護の知識を統合して、実際のシステムにゼロトラストアーキテクチャを設計しましょう。


推定読了時間: 40分