ストーリー
佐
佐藤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 KMS | AWSリソースの暗号化 | マネージド、HSM裏打ち、自動ローテーション |
| HashiCorp Vault | マルチクラウド秘密管理 | 動的シークレット、リース管理、監査ログ |
| AWS Secrets Manager | API鍵・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.3 | ACM証明書、ALB/CloudFront |
| サービス間通信 | mTLS | Istio、Linkerd |
| DB接続 | TLS | RDSのSSL/TLS強制 |
| メッセージキュー | TLS | SQS(デフォルト暗号化) |
| キャッシュ | TLS | ElastiCache 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/mTLS | ACM、Istio |
| 処理 | メモリ保護、最小権限 | Container isolation |
| 保存 | 暗号化、アクセス制御 | KMS、Vault |
| 共有 | DLP、データマスキング | Macie、カスタム |
| アーカイブ | 暗号化、アクセスログ | S3 Glacier + KMS |
| 削除 | 確実な消去、証跡 | 暗号学的消去 |
まとめ
| ポイント | 内容 |
|---|
| エンベロープ暗号化 | データキーでデータを暗号化し、マスターキーでデータキーを保護 |
| At Rest暗号化 | DB、ファイル、バックアップ全てを暗号化 |
| In Transit暗号化 | TLS 1.3、mTLS、DB接続のSSL強制 |
| PII保護 | トークナイゼーション、マスキング、ログサニタイゼーション |
| GDPR準拠 | データポータビリティ、忘れられる権利、同意管理 |
チェックリスト
次のステップへ
次は「演習:ゼロトラストアーキテクチャを設計しよう」です。Step 2で学んだゼロトラスト、認証・認可、ネットワーク、データ保護の知識を統合して、実際のシステムにゼロトラストアーキテクチャを設計しましょう。
推定読了時間: 40分