こんにちは!セキュリティの現場で日々泥臭くインシデントと戦っているホワイトハッカーの私ですが、今回は「暗号」の話を分かりやすくお届けしますね。
システム開発やインフラ構築をしていると、避けて通れないのが「データの暗号化」です。しかし、OWASP Top 10(Webアプリの脆弱性ランキング)の「A02:2021-Cryptographic Failures(暗号の失敗)」という項目を見ると、いまだに多くのシステムが古いやり方のままで、簡単に泥棒にデータを盗まれてしまっています。
「暗号なんてライブラリを呼び出すだけだから簡単でしょ?」と思っていませんか?
実はそこに大きな落とし穴があるんです。今回は、身近な防犯に例えながら、なぜ暗号の失敗が起きるのか、そしてどうやって最新の安全な仕組みへ移行すればいいのかを、一歩ずつ一緒に見ていきましょう!
—
1. 家の鍵で例える「暗号」の基本と失敗のメカニズム
まずは、サイバー空間の暗号を、私たちの日常生活にある「鍵と泥棒」に例えて考えてみましょう。
あなたが大切な宝物を入れる「金庫」を持っているとします。
この金庫を守る方法には、大きく分けて2つのアプローチがありますよね。
1. 共通鍵暗号(AESなど):金庫を開ける「同じ鍵」を、あなたと信頼する相手(またはサーバーと自分)でこっそり共有する方法。鍵のコピーが頑丈であれば、開け閉めがものすごく高速で安全です。
2. 公開鍵暗号(RSAや楕円曲線暗号 ECC):鍵を「鍵をかける用(公開鍵)」と「開ける用(秘密鍵)」の2つに分ける方法。鍵をかける用の鍵は誰にあげてもいいけれど、開ける用の秘密鍵はあなただけが金庫の奥深くに隠し持っています。
攻撃者はどこを狙うのか?(A02の根本原因)
OWASPの「暗号の失敗」が起きる根本的な原因は、主に以下の3つに集約されます。
- 昔の合鍵を使っている(古いアルゴリズム):何十年も前に作られた古い鍵(例えば、古いRSAやMD5、RC4など)は、今の強力なコンピューターを使えば、ピッキングや合鍵の量産が簡単にできてしまいます。「昔からこれで動いているから」という理由で放置されているケースが本当に多いんです。
- 鍵の管理がガバガバ(ハードコードや平文保存):金庫の秘密鍵やパスワードを、プログラムのソースコードの中にそのまま書き込んじゃう(ハードコードする)ミスです。これでは、家の合鍵を玄関のマットの下に置いておくようなもので、泥棒(攻撃者)に「どうぞ盗んでください」と言っているようなものです。
- 暗号化のモードが間違っている(鍵だけで中身が守られていない):ただデータを暗号化するだけでは不十分な場合があります。「途中で中身がすり替えられていないか」をチェックする仕組み(改ざん検知)が抜けていると、暗号化されていてもデータを書き換えられてしまいます。
—
2. 共通鍵暗号と公開鍵暗号の正しい使い分け
現場では、この2つの暗号をチームプレーのように組み合わせて使います。
- 通信の最初(握手):まずは「公開鍵暗号」を使って、お互いに安全な挨拶を交わし、これから使う「共通鍵」をこっそり相手に渡します。
- 実際のデータやり取り:通信が確立したあとは、スピードが速い「共通鍵暗号(AESなど)」を使って、大量のデータをガンガン暗号化してやり取りします。
新人のうちは、「とりあえずRSAで全部暗号化すればいいや」と思いがちですが、RSAは計算が重いため、大きなデータをそのまま暗号化するとシステムがものすごく重くなってしまいます。適材適所で使い分けるのがプロの技なんです。
—
3. 最新の暗号スイート(TLS 1.3 & AES-GCM)への移行手順
それでは、具体的にどうやってシステムをモダンで安全な状態にアップデートすればよいのでしょうか?
古く脆弱なプロトコル(SSL 3.0やTLS 1.0/1.1、CBCモードのAESなど)を捨て、現代の標準である TLS 1.3 と AES-GCM へ移行する手順を見ていきましょう。
ステップ1:Webサーバーの設定をアップデートする(Nginxの例)
インフラ担当者が最初に行うべきは、Webサーバー(NginxやApache)の設定ファイルの書き換えです。古い暗号化方式をすべて無効化し、TLS 1.3だけを許可するように設定します。
以下は、Nginxで安全な設定を行う際の設定ファイル(nginx.conf)のサンプルです。
server {
listen 443 ssl;
server_name example.com;
# 古くて危険なSSL/TLSバージョンを完全にシャットアウトし、TLS 1.3のみを許可します
ssl_protocols TLSv1.3;
# 現代の安全で高速な暗号スイート(AES-GCMなど)を指定します
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
# サーバー側の暗号スイートの優先順位を強制します
ssl_prefer_server_ciphers on;
# 証明書のパス設定
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
location / {
root /var/www/html;
index index.html index.htm;
}
}
このように設定することで、安全性の低い古い暗号化通信を一切受け付けなくなり、通信の盗聴や改ざんを強力にブロックできます。
ステップ2:アプリケーション側での暗号化実装(PHPの例)
次に、データベースに保存するパスワードや個人情報などの機密データを、アプリケーション側で暗号化するケースを考えてみましょう。
昔は mcrypt や自作の暗号関数を使う人がいましたが、現在は完全に御法度です。PHPであれば、標準で用意されている安全な openssl_encrypt() 関数を使用し、モードには改ざん検知もできる AES-256-GCM を選びましょう。
以下は、安全な共通鍵暗号(AES-256-GCM)を使った実装のサンプルコードです。
<?php
// 機密データを安全に暗号化・復号するクラスの例
class SecureEncrypter {
// 暗号化・復号に使用する共通鍵(環境変数などから安全に読み込むこと!ソースコードに直書き厳禁)
private $key;
private $cipher = 'aes-256-gcm';
public function __construct(string $secretKey) {
// 鍵の長さをAES-256に適合させます(32バイト)
$this->key = hash('sha256', $secretKey, true);
}
/**
* データを安全に暗号化する
*/
public function encrypt(string $plainText): string {
// GCMモードでは、毎回異なる使い捨ての初期化ベクトル(IV)が必要です
$ivLength = openssl_cipher_iv_length($this->cipher);
$iv = openssl_random_pseudo_bytes($ivLength);
// AES-256-GCMで暗号化を実行($tagには改ざん検知用の認証タグが格納されます)
$tag = "";
$cipherText = openssl_encrypt(
$plainText,
$this->cipher,
$this->key,
OPENSSL_RAW_DATA,
$iv,
$tag,
"",
16 // タグの長さ(16バイトが推奨)
);
// 後で復号できるように、IVとタグと暗号文をセットにしてBase64エンコードして返します
return base64_encode($iv . $tag . $cipherText);
}
/**
* 暗号化されたデータを復号する
*/
public function decrypt(string $payload): ?string {
$data = base64_decode($payload);
$ivLength = openssl_cipher_iv_length($this->cipher);
// IV、タグ、暗号文をそれぞれの長さに切り分けます
$iv = substr($data, 0, $ivLength);
$tag = substr($data, $ivLength, 16);
$cipherText = substr($data, $ivLength + 16);
// 復号を実行。もし途中でデータが改ざんされていれば、falseが返されます
$plainText = openssl_decrypt(
$cipherText,
$this->cipher,
$this->key,
OPENSSL_RAW_DATA,
$iv,
$tag
);
return $plainText !== false ? $plainText : null;
}
}
// --- 使い方テスト ---
$secretKey = "my_super_secret_environment_key"; // 実際は $_ENV['APP_KEY'] などから取得
$encrypter = new SecureEncrypter($secretKey);
$originalData = "マイナンバーやクレジットカード番号などの機密情報";
$encrypted = $encrypter->encrypt($originalData);
$decrypted = $encrypter->decrypt($encrypted);
echo "暗号化後: " . $encrypted . "\n";
echo "復号結果: " . $decrypted . "\n";
このコードのポイントは、AES-256-GCM を採用している点です。GCMモードを使うことで、万が一攻撃者がデータベースから暗号データを盗み出し、さらに中身を勝手に書き換えようとしても、復号時に認証タグのチェックで「データが改ざんされている!」と即座に検知し、不正なアクセスを防ぐことができます。
—
まとめ:一歩ずつ、確実なセキュリティ対策を
今回は、OWASP A02:2021の「暗号の失敗」の根本原因から、最新のTLS 1.3やAES-GCMへの移行手順までを見てきました。
「なんだかコードや設定項目が多くて難しそう…」と感じた方もいるかもしれませんが、安心してください。セキュリティ対策は、一度にすべてを完璧にする必要はありません。
1. まずは古い暗号(SSLや古いTLS、CBCモードなど)が現行システムに残っていないか棚卸しをする。
2. サーバーの設定やライブラリを最新の推奨設定にアップデートする。
3. 機密情報を扱うコードでは、鍵をソースコードに直書きせず、環境変数などで安全に管理する。
この一歩一歩の積み重ねが、あなたと、あなたが守るシステムのユーザーをサイバー攻撃から守る最強の盾になります。一緒に安全な開発ライフを作っていきましょう!
コメント