LESSON 40分

ストーリー

高橋アーキテクト
認証と認可で”誰が""何をできるか”を制御した。次は、データそのものを守る方法を学ぼう

高橋アーキテクトが暗号化の概念図を描きました。

高橋アーキテクト
暗号化は、たとえデータが盗まれても中身を読めなくする最後の砦だ。対称暗号、非対称暗号、ハッシュ。この3つの基本を理解しよう

暗号化の3つの柱

種類用途鍵の数
対称暗号データの暗号化1つ(共有鍵)AES-256
非対称暗号鍵交換、電子署名2つ(公開鍵/秘密鍵)RSA, ECDSA
ハッシュ完全性検証、パスワード保存なしSHA-256, bcrypt

対称暗号(Symmetric Encryption)

同じ鍵で暗号化と復号を行います。大量データの暗号化に適しています。

import crypto from "crypto";

// AES-256-GCM による暗号化(推奨)
const encrypt = (plaintext: string, key: Buffer): string => {
  const iv = crypto.randomBytes(12); // 初期化ベクトル
  const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);

  const encrypted = Buffer.concat([
    cipher.update(plaintext, "utf8"),
    cipher.final(),
  ]);

  const authTag = cipher.getAuthTag(); // 認証タグ(改ざん検知)

  // IV + AuthTag + 暗号文 を結合して返す
  return Buffer.concat([iv, authTag, encrypted]).toString("base64");
};

const decrypt = (ciphertext: string, key: Buffer): string => {
  const data = Buffer.from(ciphertext, "base64");

  const iv = data.subarray(0, 12);
  const authTag = data.subarray(12, 28);
  const encrypted = data.subarray(28);

  const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
  decipher.setAuthTag(authTag);

  return Buffer.concat([
    decipher.update(encrypted),
    decipher.final(),
  ]).toString("utf8");
};

// 使用例
const key = crypto.randomBytes(32); // 256ビットの鍵
const encrypted = encrypt("機密データ", key);
const decrypted = decrypt(encrypted, key);

AES暗号化モードの比較

モード認証並列処理推奨度
GCMあり(AEAD)最推奨
CTRなし認証付きで使用
CBCなし不可レガシー、非推奨
ECBなし使用禁止(パターンが残る)

非対称暗号(Asymmetric Encryption)

公開鍵で暗号化し、秘密鍵で復号します。鍵交換や電子署名に使用します。

// RSA鍵ペアの生成
const generateKeyPair = (): { publicKey: string; privateKey: string } => {
  const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
    modulusLength: 2048,
    publicKeyEncoding: { type: "spki", format: "pem" },
    privateKeyEncoding: { type: "pkcs8", format: "pem" },
  });
  return { publicKey, privateKey };
};

// 公開鍵で暗号化
const encryptWithPublicKey = (data: string, publicKey: string): string => {
  const encrypted = crypto.publicEncrypt(
    { key: publicKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING },
    Buffer.from(data),
  );
  return encrypted.toString("base64");
};

// 秘密鍵で復号
const decryptWithPrivateKey = (encrypted: string, privateKey: string): string => {
  const decrypted = crypto.privateDecrypt(
    { key: privateKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING },
    Buffer.from(encrypted, "base64"),
  );
  return decrypted.toString("utf8");
};

// 電子署名(秘密鍵で署名、公開鍵で検証)
const sign = (data: string, privateKey: string): string => {
  const signer = crypto.createSign("SHA256");
  signer.update(data);
  return signer.sign(privateKey, "base64");
};

const verify = (data: string, signature: string, publicKey: string): boolean => {
  const verifier = crypto.createVerify("SHA256");
  verifier.update(data);
  return verifier.verify(publicKey, signature, "base64");
};

ハッシュ関数

入力データから固定長のハッシュ値を生成します。元のデータに戻すことはできません(一方向性)。

// SHA-256 ハッシュ(データの完全性検証用)
const hashData = (data: string): string => {
  return crypto.createHash("sha256").update(data).digest("hex");
};

// パスワードのハッシュ化(bcrypt推奨)
import bcrypt from "bcrypt";

const hashPassword = async (password: string): Promise<string> => {
  const saltRounds = 12; // コストファクター
  return bcrypt.hash(password, saltRounds);
};

const verifyPassword = async (
  password: string,
  hash: string,
): Promise<boolean> => {
  return bcrypt.compare(password, hash);
};

// HMAC(メッセージ認証コード)
const createHMAC = (data: string, secret: string): string => {
  return crypto.createHmac("sha256", secret).update(data).digest("hex");
};

パスワードハッシュアルゴリズムの比較

アルゴリズム推奨度特徴
Argon2id最推奨メモリハード、最新標準
bcrypt推奨広く使われている、十分安全
scrypt推奨メモリハード
PBKDF2許容レガシー、FIPS準拠
MD5/SHA1使用禁止高速すぎてブルートフォースに弱い

エンベロープ暗号化

大量データの暗号化には、エンベロープ暗号化を使用します。データ暗号化キー(DEK)でデータを暗号化し、マスターキー(KEK)でDEKを暗号化します。

interface EnvelopeEncryption {
  // データ暗号化キー(DEK)でデータを暗号化
  encryptedData: string;
  // マスターキー(KEK)でDEKを暗号化
  encryptedDEK: string;
  // KEKのID(キーローテーション対応)
  kekId: string;
}

const envelopeEncrypt = async (
  plaintext: string,
  kmsClient: KMSClient,
): Promise<EnvelopeEncryption> => {
  // 1. ランダムなDEKを生成
  const dek = crypto.randomBytes(32);

  // 2. DEKでデータを暗号化
  const encryptedData = encrypt(plaintext, dek);

  // 3. KMS(マスターキー)でDEKを暗号化
  const { encryptedKey, kekId } = await kmsClient.encrypt(dek);

  // 4. DEKは即座に破棄(メモリからクリア)
  dek.fill(0);

  return { encryptedData, encryptedDEK: encryptedKey, kekId };
};

まとめ

ポイント内容
対称暗号AES-256-GCMを使用、大量データ向け
非対称暗号RSA/ECDSAで鍵交換と署名
ハッシュパスワードはbcrypt/Argon2id
エンベロープ暗号化DEK + KEK の二層構造

チェックリスト

  • 対称暗号と非対称暗号の違いを理解した
  • AES-256-GCMの実装方法を把握した
  • パスワードハッシュにbcryptを使う理由を理解した
  • エンベロープ暗号化の仕組みを理解した

次のステップへ

次は「データの分類と保護」を学びます。全てのデータを同じレベルで暗号化するのではなく、データの重要度に応じた保護戦略を立てましょう。


推定読了時間: 40分