MySQL 8でTransparent Data Encryption(TDE)を設定する:ストレージ層(At-Rest)での機密データ保護

MySQL tutorial - IT technology blog
MySQL tutorial - IT technology blog

背景:データベースの暗号化が「あれば嬉しい」から必須になった理由

昨年、契約情報と財務データを保存するSaaSプロジェクトのセキュリティ監査を担当した。本番データベースはMySQL 8.0で稼働しており、データ量は約50GB——巨大ではないが、外部に漏れれば十分な被害をもたらす規模だ。監査担当者からこんな質問が飛んできた:「サーバーから.ibdファイルを取得された場合、データは読めてしまいますか?」

聞きたくなかった答えがこれだ:読めます。

InnoDBはデータをテーブルスペースファイル(拡張子.ibd)に保存する。暗号化がなければ、ファイルシステムへの読み取り権限を持つ者——ホスティング業者のスタッフや、シェルを乗っ取った攻撃者も含めて——MySQLのパスワードなしに生データを抽出できてしまう。これはat-rest(保存中のデータ)層における典型的な脆弱性であり、at-transit(ネットワーク転送中のデータ、すでにSSL/TLSで対応済み)とは別の問題だ。

Transparent Data Encryption(TDE)はまさにこの問題を解決する。「Transparent(透過的)」とは、アプリケーション側でコードを一行も変更する必要がないことを意味する——MySQLがストレージエンジン層で暗号化・復号化をすべて自動的に処理してくれるからだ。

TDEが守るもの、守れないもの

  • ✅ 外部にコピーされたテーブルスペースファイル(.ibd
  • ✅ ディスクから直接読み取られたredoログ・undoログ
  • ✅ 物理バックアップ(XtraBackup)——キーが一緒に持ち出されなければ
  • ❌ 通常のMySQL接続経由のクエリ——MySQLがRAMに読み込む際にデータは復号される
  • mysqldumpによるダンプファイル——論理バックアップのため、エクスポート前にデータが復号される
  • ❌ サーバーのrootユーザーによるプロセスメモリの読み取り

「完全なセキュリティ」という幻想を持たないよう、この制限を事前に把握しておくことが重要だ。

セットアップ:Keyringコンポーネントの設定

MySQL 8のTDEは、マスター暗号化キーを管理するためにkeyringコンポーネントに依存している。MySQL 8.0.24以降、コンポーネントが旧来のプラグインを完全に置き換えた。主な種類は以下のとおりだ:

  • component_keyring_file — ローカルファイルにキーを保存。ほとんどの本番環境で十分
  • component_keyring_encrypted_file — ファイル形式だが、キーファイル自体も暗号化される
  • component_keyring_oci — Oracle Cloud Infrastructure Vault
  • component_keyring_aws — AWS KMS(MySQL Enterpriseが必要)

ここではVPSやベアメタルサーバーに適したcomponent_keyring_fileを使って解説する。

ステップ1:MySQLサーバー用のマニフェストファイルを作成する

# 起動時にMySQLがロードするコンポーネントを宣言するファイル
sudo nano /var/lib/mysql/mysqld.my

ファイルの内容(純粋なJSON):

{
    "components": "file://component_keyring_file"
}

ステップ2:キー保存ディレクトリとコンポーネント設定ファイルを作成する

# 重要:データと一緒にバックアップされないよう、keyringはdatadirの外に配置する
sudo mkdir -p /etc/mysql/keyring
sudo chown mysql:mysql /etc/mysql/keyring
sudo chmod 750 /etc/mysql/keyring

# keyringコンポーネントの設定ファイルを作成
sudo nano /var/lib/mysql/component_keyring_file.cnf

設定ファイルの内容:

{
    "path": "/etc/mysql/keyring/keyring_data",
    "read_only": false
}
# 設定ファイルのパーミッションを厳格に設定
sudo chown mysql:mysql /var/lib/mysql/mysqld.my
sudo chown mysql:mysql /var/lib/mysql/component_keyring_file.cnf
sudo chmod 600 /var/lib/mysql/mysqld.my
sudo chmod 600 /var/lib/mysql/component_keyring_file.cnf

ステップ3:MySQLを再起動してkeyringのロードを確認する

sudo systemctl restart mysql

# コンポーネントのステータスを確認
mysql -u root -p -e "SELECT * FROM performance_schema.keyring_component_status;"

Status: Activecomponent_keyring_fileが表示されれば、MySQLは暗号化の準備が整っている。

詳細設定:テーブル・テーブルスペース・ログの暗号化

個別テーブルの暗号化

最も柔軟なアプローチだ——実際に機密データを含むテーブルだけを暗号化し、ログやキャッシュ用テーブルへのオーバーヘッドを避けられる。

-- 最初から暗号化を有効にして新しいテーブルを作成
CREATE TABLE users_sensitive (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    id_card VARCHAR(20),
    phone VARCHAR(20),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENCRYPTION='Y';

-- 既存テーブルに暗号化を有効化(オンライン操作のため長時間ロックは発生しない)
ALTER TABLE payment_info ENCRYPTION='Y';

-- 必要に応じて暗号化を無効化(例:他システムへの移行時)
ALTER TABLE payment_info ENCRYPTION='N';

サーバー全体でデフォルト暗号化を有効にする

新たに作成するすべてのテーブルが自動的に暗号化されるよう、/etc/mysql/mysql.conf.d/mysqld.cnfに以下を追加する:

[mysqld]
# 新規作成するすべてのInnoDBテーブルを自動的に暗号化
default_table_encryption = ON

# バイナリログを暗号化——レプリケーション使用時は重要
binlog_encryption = ON

# undoテーブルスペースを暗号化(トランザクションロールバックデータを保存)
innodb_undo_log_encrypt = ON

# redoログを暗号化(InnoDBのwrite-aheadログを保存)
innodb_redo_log_encrypt = ON
sudo systemctl restart mysql

default_table_encryption = ONを設定しても、既存のテーブルはそのままの状態を維持する——手動でALTERする必要がある。ディスクI/Oの急激な負荷増大を避けるため、通常はスクリプトを書いてテーブルを一つずつバッチ移行するようにしている。

マスターキーの定期ローテーション

セキュリティのベストプラクティスは、暗号化キーを定期的にローテーションすることだ。MySQL TDEは2層のキーモデルを採用している:

  • マスターキー — keyringが管理し、テーブルスペースキーのラップに使用
  • テーブルスペース暗号化キー(TEK) — テーブルスペースごとに1つのTEKを持ち、.ibdファイルのヘッダーに暗号化して保存

マスターキーをローテーションすると、MySQLは新しいマスターキーを生成してすべてのTEKを再暗号化する。データページは再暗号化されないため、データベースが大規模でも処理は非常に高速だ:

-- マスターキーをローテーション(オンライン実行のためダウンタイム不要)
ALTER INSTANCE ROTATE INNODB MASTER KEY;

このコマンドをcrontabに追加して毎月実行している:

# /etc/cron.d/mysql-key-rotation に追加
0 3 1 * * root mysql -u root -p'YOUR_PASSWORD' -e "ALTER INSTANCE ROTATE INNODB MASTER KEY;" >> /var/log/mysql-key-rotation.log 2>&1

検証とモニタリング

テーブルの暗号化を確認する

-- 暗号化中のすべてのテーブルスペースを表示
SELECT 
    SPACE,
    NAME,
    ENCRYPTION
FROM information_schema.INNODB_TABLESPACES
WHERE ENCRYPTION = 'Y'
ORDER BY NAME;

-- 特定データベース内のテーブルを確認
SELECT 
    TABLE_SCHEMA,
    TABLE_NAME,
    CREATE_OPTIONS
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'your_database'
    AND CREATE_OPTIONS LIKE '%ENCRYPTION%';

.ibdファイルを直接読み込んで検証する

最も実践的なテスト——stringsコマンドでテーブルスペースファイルの内容を読み取ってみる:

# .ibdファイルのパスを確認
sudo find /var/lib/mysql -name "users_sensitive.ibd"

# 暗号化済みファイルを読み込む——バイナリノイズしか表示されない
sudo strings /var/lib/mysql/your_db/users_sensitive.ibd | head -20

# 暗号化されていないテーブルと比較する
# (your_dbとtable_nameは適宜変更すること)
sudo strings /var/lib/mysql/your_db/non_encrypted_table.ibd | grep -i "@gmail"

これはクライアントへのデモでよく使うやり方だ:.ibdファイルにプレーンテキストのメールアドレスではなくランダムなバイナリデータしか表示されない様子は、どんなドキュメントより説得力がある。

パフォーマンスへの影響をモニタリングする

TDEはAES暗号化・復号化によるオーバーヘッドが生じるが、現代のCPUはほぼすべてAES-NIハードウェア命令をサポートしているため、実際の影響は非常に小さい:

# CPUがAES-NIをサポートしているか確認
grep -m 1 aes /proc/cpuinfo
# 期待される出力:flags: ... aes ...(flagsに'aes'が含まれている)
-- TDE有効化後のI/Oパフォーマンスを監視
SELECT 
    EVENT_NAME,
    COUNT_READ,
    ROUND(SUM_TIMER_READ / 1000000000, 2) AS read_time_ms,
    COUNT_WRITE,
    ROUND(SUM_TIMER_WRITE / 1000000000, 2) AS write_time_ms
FROM performance_schema.file_summary_by_event_name
WHERE EVENT_NAME LIKE '%innodb%datafile%'
ORDER BY SUM_TIMER_READ DESC
LIMIT 10;

50GBのデータベースでredoログとundoログを含むTDEを完全に有効化した結果、クエリ時間は約2〜3%増加した——ノイズの範囲内であり、セキュリティコンプライアンスと引き換えにするには十分許容できる水準だ。

keyringファイルのバックアップ——最も見落とされがちな手順

多くの人が見逃す最重要ポイント:keyringファイルはデータファイルとは別に、独立してバックアップしなければならない。keyringファイルを失うことは、暗号化されたすべてのデータを失うことと同義だ——.ibdのバックアップが完全に揃っていても復元できない。

# キーのローテーション後にkeyringをバックアップ
sudo cp /etc/mysql/keyring/keyring_data \
    /backup/keyring/keyring_data_$(date +%Y%m%d_%H%M%S)
sudo chmod 600 /backup/keyring/keyring_data_$(date +%Y%m%d_%H%M%S)

# バックアップ一覧を確認
sudo ls -la /backup/keyring/

理想的には、keyringのバックアップをデータベースサーバーとは完全に別のサーバーまたはストレージに保存すること。「職務分離」の原則として、キーとデータは別々の場所に保管すべきだ——これはPCI-DSSやISO 27001といったほとんどのセキュリティ標準でも要求されている事項だ。

Share: