MySQL Enterprise Masking and De-identification とは?
MySQL Enterprise Masking and De-identification(マスキングと匿名化) に製品紹介ページが有るのですが、商用版の MySQL Edition のみで提供されている、セキュリティ関連の機能となっております
そこで、本記事はデータマスキング機能のうち、比較的利用シーンの多い機能である、選択的マスキングの使用方法と、単純な速度検証の結果をご紹介したいと思います
環境
| 項目 | 内容 | 
|---|---|
| MySQL | MySQL 8.0.15-commercial | 
| OS | Oracle Linux Server release 7.6 | 
| CPU | 2vCPU | 
| Memory | 15GB | 
| InnoDB Buffer Pool | 11GB | 
利用準備
商用版のバイナリにはプラグインは同梱されていますので、マニュアル の通りにコマンドを実行すれば、利用可能となります
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | mysql> INSTALL PLUGIN data_masking SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION gen_blacklist RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION gen_dictionary RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION gen_dictionary_drop RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION gen_dictionary_load RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.01 sec) mysql> CREATE FUNCTION gen_range RETURNS INTEGER SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION gen_rnd_email RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION gen_rnd_pan RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION gen_rnd_ssn RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.01 sec) mysql> CREATE FUNCTION gen_rnd_us_phone RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION mask_inner RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION mask_outer RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION mask_pan RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION mask_pan_relaxed RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION mask_ssn RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.01 sec) | 
データの準備
sysbench のカスタマイズスクリプトを用意する
今回は、後で行う速度検証のためにも、sysbench のカスタマイズスクリプトを用意しました
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | #!/usr/bin/env sysbench sysbench.cmdline.options = {   table_size = {"Number of rows per table", 10000},   column     = {"'Bare' for bare columns, 'mask' for masking columns", "bare"} } function get_con()   drv = sysbench.sql.driver()   return drv:connect() end function prepare()   con = get_con()   con:query([[     CREATE TABLE masking (       id INT NOT NULL AUTO_INCREMENT,       data VARCHAR(16) NOT NULL,       PRIMARY KEY (id)     );]])   con:query([[CREATE OR REPLACE VIEW masked_data AS SELECT id, MASK_INNER(data, 0, 4) AS data FROM masking;]])   for n=1, sysbench.opt.table_size do     con:query(string.format("INSERT INTO masking (data) VALUES ('%04d%04d%04d%04d')", math.random(0, 9999),  math.random(0, 9999), math.random(0, 9999), math.random(0, 9999)))   end end function cleanup()   con = get_con()   con:query("DROP VIEW IF EXISTS masked_data")   con:query("DROP TABLE IF EXISTS masking") end function thread_init()   con = get_con() end function event()   local sql = ""   if sysbench.opt.column == "bare" then     sql = string.format("SELECT data FROM masking WHERE id = %d ", math.random(1, sysbench.opt.table_size))   else     sql = string.format("SELECT data FROM masked_data WHERE id = %d ", math.random(1, sysbench.opt.table_size))   end   con:query(sql) end | 
こちらのスクリプトを下記コマンドにて実行し、データを 200,000,000 件登録しています
データサイズは約 8GB となり、全てのデータが InnoDB Buffer Pool に乗るデータ量です
| 1 2 3 | $ ./bench.lua --db-driver=mysql --mysql-host=localhost --mysql-user=bench --mysql-password=xxxxx --mysql-db=bench --table-size=200000000 --threads=2 prepare $ ./bench.lua --db-driver=mysql --mysql-host=localhost --mysql-user=bench --mysql-password=xxxxx --mysql-db=bench --table-size=200000000 --time=600 --threads=2 --column=bare run $ ./bench.lua --db-driver=mysql --mysql-host=localhost --mysql-user=bench --mysql-password=xxxxx --mysql-db=bench --table-size=200000000 --time=600 --threads=2 --column=mask run | 
実際のアプリケーションでは、 mask_inner() ファンクションを使用した View を定義し、実体の表には Select 権限を付与しないことで、マスクされていないデータの情報漏えいの対策を行うのが一般的だと思われます
そこで、本スクリプトでも、マスクされたデータは View 経由で取得するようにし、実際に利用されるアプリケーションの利用シーンに近い状態での確認を行っております
DEFINER には実体の表へ Select 権限を付与しているユーザを指定し、View にアクセスするユーザと分離する必要がありますが、本検証では同一ユーザで検証しております
選択的マスキングの確認
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | mysql> SELECT data FROM masking ORDER BY id LIMIT 10; +------------------+ | data             | +------------------+ | 4060144950650982 | | 9711561289287805 | | 2241778922257757 | | 2113729101188717 | | 3233001403701809 | | 7932423973964264 | | 9131286669366083 | | 9607978701151213 | | 5718495276083185 | | 3102595404684474 | +------------------+ 10 rows in set (0.01 sec) mysql> SELECT MASK_INNER(data, 0, 4) FROM masking ORDER BY id LIMIT 10; +------------------------+ | MASK_INNER(data, 0, 4) | +------------------------+ | XXXXXXXXXXXX0982       | | XXXXXXXXXXXX7805       | | XXXXXXXXXXXX7757       | | XXXXXXXXXXXX8717       | | XXXXXXXXXXXX1809       | | XXXXXXXXXXXX4264       | | XXXXXXXXXXXX6083       | | XXXXXXXXXXXX1213       | | XXXXXXXXXXXX3185       | | XXXXXXXXXXXX4474       | +------------------------+ 10 rows in set (0.00 sec) mysql> SELECT data FROM masked_data ORDER BY id LIMIT 10; +------------------+ | data             | +------------------+ | XXXXXXXXXXXX0982 | | XXXXXXXXXXXX7805 | | XXXXXXXXXXXX7757 | | XXXXXXXXXXXX8717 | | XXXXXXXXXXXX1809 | | XXXXXXXXXXXX4264 | | XXXXXXXXXXXX6083 | | XXXXXXXXXXXX1213 | | XXXXXXXXXXXX3185 | | XXXXXXXXXXXX4474 | +------------------+ 10 rows in set (0.00 sec) | 
mask_inner() ファンクションを使用することで、簡単にマスキングされたデータが取得できました
速度検証
計測対象は実行クエリ数とし、マスキング処理の有り・無しで、それぞれ 10 回計測しております
sysbench の実施オプションは下記を指定しております
| オプション | 値 | 
|---|---|
| 実行時間 | 10 分 | 
| スレッド | 2 | 
データの準備の箇所にも記載しておりますが、マスクされたデータは、 View 経由で取得しております
まとめ
マスキング+Viewでのアクセスを行うことでおおよそ、10〜20%ほど、実行クエリ数が下がっています
しかし、Web アプリケーションの脆弱性や GDPR への対応を考えると、このようなマスキング機能を活用し、データベースレベルでの対応も一考する必要があるのでは無いでしょうか?




 
		 
		 
			 
			 
			 
			 
			 
			 
			 
			