【入門編】 暗号化アルゴリズムの選択と実装の落とし穴 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

こんにちは!セキュリティの世界へようこそ。
新米IT担当者の皆さんや、これからアプリ開発を頑張っていこうという開発者の皆さん、日々の業務お疲れ様です!

「暗号化」って聞くと、なんだかすごく難しそう、映画に出てくるような天才ハッカーの領域…そんなイメージを持っていませんか?
でも大丈夫です。実は私たちが普段使っているスマホやWebサイトの裏側では、この「暗号化」がめちゃくちゃ重要な役割を果たしています。そして、ちょっとした実装のミスで、その頑丈な金庫が「ペラペラのダンボール」になってしまうこともあるんです。

今回は、暗号化の歴史において「絶対にやっちゃいけないミス(ECBモード)」と、「今どきの正解(AES-GCM)」について、身近な防犯の例えを交えながら、一歩ずつ優しく紐解いていきましょう!

—

1. 家の鍵で例える「暗号化」と「モード」の基本

まずは、暗号化のイメージを掴むために「家と郵便受け」を想像してみてください。

あなたが大切な手紙(機密データ)を誰かに送るとき、普通の封筒に入れただけでは途中でビリッと破られて中身を見られてしまいますよね。そこで、カギをかけた頑丈な箱(暗号化)に入れて送るわけです。

このとき、箱をカギでロックする仕組み(アルゴリズム)には色々なルールがあります。現代で一番信頼されている標準的なルールが AES という仕組みです。

しかし、AESを使うときには「どのやり方(モード)で箱をロックするか」を選ばなければなりません。ここに、攻撃者がつけ入る大きな落とし穴があるんです。

—

2. 絶対に使っちゃダメ!「ECBモード」という大穴

ECBモードってなに?

ECB(Electronic Codebook)モードは、暗号化の歴史の中で一番最初に考えられた、いわば「一番シンプルで頭の悪いモード」です。

仕組みはこうです。
例えば、長い文章を暗号化するとき、ECBモードは文章を「16バイトずつのブロック」にパツパツと細かく切り分けます。そして、同じ内容のブロックは、すべて全く同じ暗号文に変換するのです。

家の鍵と泥棒の例えで考えてみよう

想像してみてください。あなたが100軒並んだ同じ分譲地の家に住んでいて、すべての家が「全く同じスペアキー」を使っていたとします。泥棒からしたら、「1軒の鍵の形さえ分かってしまえば、残りの99軒の家にもスルスルッと侵入し放題」ですよね? ECBモードはまさにこれと同じ状態です。

視覚的な恐怖:ペンギンの秘密

セキュリティの研修でよく使われる有名な例えに「ペンギンの画像」があります。
きれいなペンギンの写真をECBモードで暗号化すると、どうなると思いますか?

実は、写真の色や模様がブロックごとに同じパターンで変換されるため、暗号化されているはずなのに、ペンギンのシルエットがうっすらと見えてしまうという奇妙な現象が起きます。
攻撃者からすれば、「あ、これはペングィンの画像だな」「ここは同じ文字が繰り返されているな」と、中身の構造がダダ漏れになってしまうのです。これは機密を守る暗号としては大失格です。

—

3. 機密性だけじゃ足りない!「完全性」の重要性

さて、ECBモードの危険性が分かったところで、もう一つ重要な概念をお話しします。それが「完全性(Integrity)」です。

  • 機密性(Confidentiality): 中身を覗き見られないように隠すこと。
  • 完全性(Integrity): 途中で悪意ある第三者に中身を書き換えられていない(改ざんされていない)ことを証明すること。

もし、あなたが銀行の振込システムを作っているとします。金額のデータを暗号化して「10,000円」と送りました。
いくら中身が暗号化されて読めないようになっていても、攻撃者が暗号化されたデータをごっそり別の「1,000,000円」という暗号データにすり替えることができたらどうでしょう? 中身が読めなくても、データが改ざんされてしまえば大惨事ですよね。

従来の暗号化方式(CBCモードなど)では、機密性は守れても、「中身が書き換えられていないか」をチェックする機能(認証)が別盛りになっていました。これが実装ミスの温床になっていたのです。

—

4. 今日の決定版:AES-GCM(認証付き暗号 / AEAD)を使おう!

そこで登場するのが、今回の主役である AES-GCM(Galois/Counter Mode) です。
これは、「中身を隠す力(機密性)」と「中身が書き換えられていないかを見破る力(完全性)」の2つを同時に持っている、最強の仕組みです。セキュリティ業界では AEAD(Authenticated Encryption with Associated Data) と呼ばれています。

防犯に例えるなら、「頑丈なカギ」と「絶対に破れない特殊な封印シール」がセットになった宅配便のようなものです。泥棒がこじ開けようとしたり、途中で中身をすり替えようとシールを剥がしたりすると、一瞬で「あ、これ誰かにいじられたな!」とバレる仕組みになっています。

—

5. 実装例:PHPで安全なAES-GCMを使ってみよう

それでは、実際の開発現場でどのように書くべきか、PHPを例に見ていきましょう!
新人の皆さんも、「こういう風に書けば安全なんだな」という雰囲気を感じ取ってもらえればOKです。

以下のコードは、PHPの標準関数である openssl_encrypt を使って、安全なAES-GCMで文字列を暗号化・復号するサンプルです。

<?php
/**
 * AES-GCMを用いた安全な暗号化・復号のサンプルコード
 * 開発者:セキュリティを愛するエンジニア
 */

// 1. 暗号化に使用する秘密鍵(※実際の環境では環境変数などから安全に読み込んでください。ハードコードは厳禁!)
// AES-256の場合は、必ず32バイト(258ビット)のランダムな文字列を用意します。
$encryption_key = 'this-is-a-super-secret-key-32bytes!'; // 32バイトの鍵

/**
 * データを安全に暗号化する関数
 * 
 * @param string $plainText 暗号化したい平文
 * @param string $key 秘密鍵
 * @return string 暗号化されたデータ(IV、タグ、暗号文を結合したもの)のBase64エンコード
 */
function encryptData($plainText, $key) {
    // 2. 暗号化方式の指定(AES-256のGCMモードを指定)
    $cipher = 'aes-256-gcm';
    
    // 3. 初期化ベクトル(IV: Initialization Vector)の生成
    // GCMモードでは、同じ鍵で同じIVを二度使ってはいけない(Nonceの一意性)という鉄則があります。
    // 暗号化のたびに、openssl_cipher_iv_length で推奨される安全な長さを取得し、ランダムな値を生成します。
    $ivLength = openssl_cipher_iv_length($cipher);
    $iv = openssl_random_pseudo_bytes($ivLength);
    
    // 4. 暗号化の実行と同時に認証タグ($tag)を取得する
    // この $tag が、データの「完全性(改ざんされていないこと)」を保証する要になります。
    $tag = '';
    $ciphertext = openssl_encrypt(
        $plainText,
        $cipher,
        $key,
        OPENSSL_RAW_DATA,
        $iv,
        $tag,
        '', // AAD(追加認証データ)が必要な場合はここに指定しますが、今回は省略
        16  // タグの長さ(通常は16バイトが推奨されます)
    );
    
    if ($ciphertext === false) {
        throw new Exception('暗号化に失敗しました。');
    }
    
    // 5. 復号時に必要になる「IV」「タグ」「暗号文」をまとめてBase64でエンコードして返す
    // サーバー側で保存・送信する際は、これらをセットで管理します。
    $combined = $iv . $tag . $ciphertext;
    return base64_encode($combined);
}

/**
 * 暗号化されたデータを安全に復号する関数
 * 
 * @param string $encodedData 暗号化されたBase64文字列
 * @param string $key 秘密鍵
 * @return string|false 復号された平文、または改ざん検知等で失敗した場合はfalse
 */
function decryptData($encodedData, $key) {
    $cipher = 'aes-256-gcm';
    $combined = base64_decode($encodedData);
    
    $ivLength = openssl_cipher_iv_length($cipher);
    $tagLength = 16; // 暗号化時に指定したタグの長さ
    
    // データの長さが短すぎる場合は不正とみなす
    if (strlen($combined) < ($ivLength + $tagLength)) {
        return false;
    }
    
    // 結合したデータから、IV、タグ、暗号文を正確に切り出す
    $iv = substr($combined, 0, $ivLength);
    $tag = substr($combined, $ivLength, $tagLength);
    $ciphertext = substr($combined, $ivLength + $tagLength);
    
    // 6. 復号の実行
    // ここで少しでもデータが改ざんされていると、openssl_decrypt は自動的に検知し、
    // 偽りのデータとして `false` を返します(これがGCMモードの最大の強みです!)。
    $plainText = openssl_decrypt(
        $ciphertext,
        $cipher,
        $key,
        OPENSSL_RAW_DATA,
        $iv,
        $tag
    );
    
    return $plainText;
}

// --- 動作テスト ---
try {
    $originalMessage = "極秘情報:明日のランチは焼き肉です!";
    echo "元のメッセージ: " . $originalMessage . "\n";
    
    // 暗号化
    $encrypted = encryptData($originalMessage, $encryption_key);
    echo "暗号化された文字列 (Base64): " . $encrypted . "\n";
    
    // 復号
    $decrypted = decryptData($encrypted, $encryption_key);
    echo "復号されたメッセージ: " . $decrypted . "\n";
    
} catch (Exception $e) {
    echo "エラー: " . $e.getMessage();
}
?>

—

6. 実務で絶対に守るべき鉄則まとめ

最後に、実務で暗号化を実装する際につまづきやすいポイントを、レッドチームの視点からいくつかお伝えしておきます。

1. ECBモードは絶対に選ばない
フレームワークのデフォルト設定や古いライブラリのサンプルコードで AES/ECB/... となっていたら、即座に修正を求めてください。
2. IV(初期化ベクトル)やNonceは必ず「毎回ランダム」にする
同じ鍵に対して同じIVを使い回すと、攻撃者に暗号のパターンを解析される致命的な脆弱性(リプレイ攻撃や暗号解析の糸口)になります。毎回 openssl_random_pseudo_bytes や random_bytes 等を使って非予測な値を生成しましょう。
3. 鍵のハードコードをしない
ソースコード(GitHubなど)に直接暗号鍵を書き込むのは、「自宅の玄関の鍵をドアの横の植木鉢の下に置いておく」のと同じです。環境変数(.envファイルなど)や、AWS KMS、Azure Key Vaultといった適切な鍵管理サービスを使いましょう。
4. 自作の暗号アルゴリズムを作らない
「俺が考えた最強の暗号化関数」は、プロの攻撃者からすれば数秒で突破されるおもちゃです。歴史ある標準化されたアルゴリズム(AES-GCMなど)を、信頼できる言語・ライブラリの標準機能を使って正しく実装するのが一番の近道です。

—

セキュリティの道は一日にして成らずですが、一つひとつの「なぜ?」を理解していけば、誰でも堅牢で安全なシステムを作ることができます。
一歩ずつ、確実に知識をアップデートしていきましょう!それではまた次回の記事でお会いしましょう!

コメント

タイトルとURLをコピーしました