データベース暗号化:pgcryptoとAES_ENCRYPTで機密データを保護する

Database tutorial - IT technology blog
Database tutorial - IT technology blog

セキュリティの時代にプレーンテキスト保存が「自殺行為」である理由

キャリアを始めたばかりの頃、私はデータベースの外部ポートを閉じるだけでデータは安全だと思っていました。しかし、現実はそう甘くありません。2023年のIBMのレポートによると、データ漏洩の平均コストは445万ドルに達しています。サーバーがRCE(リモートコード実行)で乗っ取られたり、バックアップファイルが第三者の手に渡ったりすれば、マイナンバーや顧客の住所はすべてプレーンテキストとしてさらけ出されてしまいます。

GDPR(一般データ保護規則)において、個人データの保護はもはやオプションではなく必須要件です。リスクを最小限に抑える最善の方法は、データベース層でデータを暗号化すること(Encryption at Rest)です。そうすれば、ハッカーがデータファイルを入手しても、復号キー(Secret Key)がなければ、ただの無意味な文字列しか得られません。

私はこれまでMySQLとPostgreSQLの両方で多くのプロジェクトを経験してきました。高いコンプライアンスが求められるシステムにおいて、パフォーマンスとセキュリティのバランスに優れたpgcrypto(Postgres)とAES_ENCRYPT(MySQL)は、非常に強力なツールとなります。

pgcrypto拡張機能を使用したPostgreSQLでのデータ暗号化

PostgreSQLはコア機能に共通鍵暗号を直接組み込んでいません。その代わり、AES、BlowfishからPGPまで幅広くサポートする強力な拡張機能であるpgcryptoを提供しています。

pgcryptoのインストール

まず、対象のデータベースでこの拡張機能を有効にする必要があります。次の1行を実行するだけです。

CREATE EXTENSION IF NOT EXISTS pgcrypto;

実行後、pgp_というプレフィックスを持つ関数をリストアップして、準備ができているか確認してください。

データの挿入と取得

例えば、customersテーブルのphone_numberカラムを保護する必要があるとします。重要な注意点として、暗号化されたデータを格納するカラムはbytea(バイナリデータ)型である必要があります。

CREATE TABLE customers (
    id SERIAL PRIMARY KEY,
    full_name TEXT,
    phone_number BYTEA
);

インサート時には、pgp_sym_encrypt関数を使用します。強力なシークレットキー(通常は32文字)を生成するには、Password Generatorなどのツールを使うのが便利です。これらのツールはクライアント側で動作するため、サーバーにキーが漏れる心配はありません。

INSERT INTO customers (full_name, phone_number) 
VALUES ('田中 太郎', pgp_sym_encrypt('0901234567', 'my_super_secret_key'));

データを読み取るには、pgp_sym_decrypt関数を使用して元のテキストに戻します。

SELECT full_name, pgp_sym_decrypt(phone_number, 'my_super_secret_key') as phone 
FROM customers;

AES_ENCRYPTによる MySQLでのデータ保護

Postgresとは異なり、MySQLには暗号化関数が組み込まれています。しかし、ジュニアエンジニアによくある間違いは、非常に脆弱なデフォルト設定のまま使用してしまうことです。

暗号化モードの設定

MySQLのデフォルトはaes-128-ecbです。ECB(電子符号表)モードは、同じデータブロックが常に同じ暗号化結果になるため非常に脆弱です。現代的なセキュリティ基準を満たすには、aes-256-cbcに切り替えるべきです。

-- セッションまたはmy.cnfファイルでの設定
SET block_encryption_mode = 'aes-256-cbc';

AES_ENCRYPTとAES_DECRYPTの使い方

CBCモードでは、初期化ベクトル (IV)というパラメータを追加する必要があります。IVを使用することで、同じ内容でも暗号化のたびに異なる結果が生成され、ハッカーがパターンを推測するのを防ぐことができます。

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255),
    ssn VARBINARY(255) -- VARCHARの代わりにVARBINARYを使用する
);

インサート時には、RANDOM_BYTES(16)関数を使用して16バイトのランダムなIV文字列を生成します。

SET @key = 'secret_key_32_chars_long_for_aes256';
SET @iv = RANDOM_BYTES(16);

INSERT INTO users (email, ssn) 
VALUES ('[email protected]', AES_ENCRYPT('123-456-789', @key, @iv));

この@ivは必ず別のカラムに保存してください。IVを紛失すると、復号ができなくなり、そのデータは実質的に「破棄」したことと同じになります。

実践経験と「痛い目を見ないための」注意点

データの暗号化は、単にSQLコマンドをいくつか実行すれば済む話ではありません。運用の中で私が学んだ3つの大きな教訓を紹介します。

1. むやみやたらに暗号化しない

暗号化と復号は非常にCPU負荷が高い処理です。数百万行あるテーブルのすべてのカラムを暗号化すると、レイテンシが2〜3倍に増加する可能性があります。本当に機密性の高い情報だけを優先してください。

また、データが変換されているため、WHERE phone_number = '090...'のような直接検索はできません。高速に検索したい場合は、Blind Index(データのハッシュ値を別途保存する)という手法を使います。この目的には、Hash GeneratorでSHA-256アルゴリズムを選択するのが良いでしょう。

2. シークレットキーの管理

キーをコードやSQLスクリプトにハードコードしてはいけません。誤ってGitHubにプッシュしてしまえば、システム全体が崩壊します。環境変数やAWS Secrets Managerのような専用サービスを利用しましょう。

3. カラム長の確認

カラムの長さが足りないと、データが切り捨てられて復号できなくなります。私はよくBase64 Encoderを使用して暗号化後の文字列を確認し、API経由で転送する際にエラーが発生しないかチェックしています。

結論として、データベースの暗号化はあなたの信頼を守る最後の砦です。PostgresでもMySQLでも、常に強力なアルゴリズムを選び、CBCモードを使用し、鍵を厳重に管理することを忘れないでください!

Share: