MySQL 5.7の透過的データ暗号化

MySQL 5.7.11で導入、5.7.12で一部改良された、透過的データ暗号化をテストしたときのメモです。
とある勉強会のLTでグダグダになったので、あらためて書き直して投稿しておきます。

※内容は無保証です。

---

**2019/04/30 追記：**
MySQL 8.0 については以下の記事をご覧ください。

 * **[MySQL 8.0.16 でテーブルスペース・REDO ログ／UNDO ログ・システムテーブル暗号化](https://qiita.com/hmatsu47/items/bae53fd0f6d09511732c)**

---

# 透過的データ暗号化（TDE）とは
アプリケーション（SQL）側で暗号化／復号処理をしなくても、DBのデータファイルが暗号化される機能です。
データファイルや物理メディア（HDDなど）の窃取・盗難対策に有効です。
一方で、アプリケーション側にSQLインジェクションなどの脆弱性がある場合には、何の保護にもなりません。

# MySQL以外のRDBMSでは
商用のOracleなどでは、かなり前から使えるようになっています。
無償で使えるOSSでは、PostgreSQL（9.3～・要NEC提供のモジュール）が列単位、MariaDB（10.1.3～）がテーブル単位でのTDEをサポートしています。
また、Amazon RDSでは、MySQLを含め、様々なRDBMSをサポートしています。

# 利用準備
インストールとテーブル初期化、暗号化に関係のない設定チューニングについては省略します。

```
# vi /etc/my.cnf
```

以下を追記します。

```
innodb_file_per_table=1
early-plugin-load=keyring_file.so 
keyring_file_data=/usr/local/mysql/mysql-keyring/keyring
```

1行目：テーブル単位で.ibdファイル生成
2行目：キーリングプラグインとして、ファイルベースのプラグインを使用
3行目：キーリングファイルの置き場所を指定

追記したら保存します。
続いて、キーリングファイルの置き場所（ディレクトリ）を作成します。

```
# mkdir -p /usr/local/mysql/mysql-keyring
# chown -R mysql.mysql /usr/local/mysql
# chmod 750 /usr/local/mysql/mysql-keyring
```

終わったら、サービスを再起動します（以下、CentOS6などの場合）。

```
# service mysqld restart
```

# 暗号化テーブルを作成する
MySQLクライアントからサーバに接続して、暗号化テーブルを作ってみます。

```
mysql> CREATE DATABASE sample_db;
mysql> USE sample_db;
mysql> CREATE TABLE sample_tbl (id int(10) PRIMARY KEY AUTO_INCREMENT, value VARCHAR(100)) ENGINE=innodb ENCRYPTION='Y';
```

データを入れてみます。

```
mysql> INSERT INTO sample_tbl SET value='hogehoge';
```

この状態で、テーブルファイルを覗いてみます。

```
# view -b /var/lib/mysql/sample_db/sample_tbl.ibd
```

![image](https://qiita-image-store.s3.amazonaws.com/0/123899/6636b12f-9ba8-e969-49a6-08c2ea0f6e0d.png)

キーワード検索しても見つかりません。暗号化されたようです。
その他の操作は、以下のとおりです。

**・既存テーブルの暗号化**

```
mysql> ALTER TABLE ○○○ ENCRYPTION='Y';
```

**・暗号化されたテーブルを復号**

```
mysql> ALTER TABLE ○○○ ENCRYPTION='N';
```

**・鍵のローテーション**

```
mysql> ALTER INSTANCE ROTATE INNODB MASTER KEY;
```

**・ローテーションした鍵を、既存テーブルに適用（既存テーブルの暗号化と同じSQL）**

```
mysql> ALTER TABLE ○○○ ENCRYPTION='Y';
```

# 速度について
Oracleによると、「数%のオーバーヘッドしか生じない」とのこと。
バイナリログあり（同期出力）、メモリチューニングなし（デフォルト設定どおり）の環境で実際にやってみましたが、INSERT時に約10%、SELECT時に5%未満のオーバーヘッドが生じました。
ほぼ、謳い文句どおりです。

# 機能的な制限
ここまでは良いのですが、実は、REDOログが暗号化されません。

```
# view -b /var/lib/mysql/ib_logfile0
```

![image](https://qiita-image-store.s3.amazonaws.com/0/123899/9cdc165d-40bb-bc7a-db9a-b66d0ac03898.png)

システムテーブルスペース（ibdata1）に含まれるUNDOログ（ロールバックセグメント）も暗号化されません。

また、レプリケーション時に使う、バイナリログも暗号化されません（row・mixedの両方で確認）。

```
# view -b /バイナリログのパス/ログファイル名
```

![image](https://qiita-image-store.s3.amazonaws.com/0/123899/7d2dbb2c-1dbb-8e87-2e06-db4ac2f628cb.png)

おそらくですが、もし仮に、（今後、Oracleが）ログを暗号化するような処理に変更したとしても、暗号化のオーバーヘッドはもっと大きくなるでしょう。テーブルファイルは非同期書込ができるので、実質的なオーバーヘッドが少なくなるのに対し、ログは同期書込しなければデータを失うことになるからです。

**2016/10/04追記**：[Oracle奥野さんの著書](http://www.shoeisha.co.jp/book/detail/9784798147406)では、この機能を「透過的テーブルスペース暗号化」という表現で紹介されていますが、こちらの呼び方のほうが機能の実体をより正確に表していますね。

# その他の制限
Oracleのリファレンスマニュアル等にも（英語で）記されていますが、キーリングプラグインとしてファイルベースのプラグインを使う場合は、PCI DSSなどの要件を満たしません。
暗号鍵がサーバーローカルに保存されるので、キーリングファイルとデータファイルを一緒に窃取された場合、復号される危険性があります（キーリングファイルも一応暗号化されているようですが、ロジックはソースを解析すれば分かるはずです）。

5.7.12からサポートされた、Oracle Key Vaultを使うキーリングプラグインでは、要件を満たすそうですが、こちらはEnterprise Edition（有償サポート付き）専用です。REDO／UNDOログ、バイナリログについては、暗号化されないままのはずです。
また、メモリ上の処理（バッファプールなど）は暗号化されないため、OSレベルの侵入にも、効果は限定的です。

「何を守るのか」を良く考えた上で、この機能を使うか、Amazon RDSなど他のソリューションを使うか、選択したほうが良いでしょう。

**2017/04/15追記**：MySQL 8.0.1（DMR）で追加された、REDOログ／UNDOログの暗号化についても試してみました。
・**[MySQL 8.0.1 DMRでテーブルスペース暗号化＋REDOログ／UNDOログ暗号化](http://qiita.com/hmatsu47/items/1c6252c9a4b98dc936b1)**

---

**【おまけ】**
MySQL 5.7関連投稿記事へのリンクを集めました。

 * **[Qiitaに投稿したMySQL 5.7関連記事](http://hmatsu47.hatenablog.com/qiita_mysql57)**