ストーリー
高橋アーキテクトが暗号化の概念図を描きました。
暗号化の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分