RDS for PostgreSQLでデータを暗号化する

はじめに

RDSに個人情報や機密情報を保存する場合、データの暗号化をしたいという要件はよくあります。RDS for SQL ServerRDS for OracleはTransparent Data Encryption(TDE)で透過的な暗号化ができるのですが、残念ながらRDS for MySQLRDS for PostgreSQLではTDEが使えません。

しかし、RDS for PostgreSQLでは拡張モジュールを使うことで、データの暗号化が可能です。今回は拡張モジュールの1つであるpgcryptoを使って、データの暗号化をしてみました。

やってみた

今回使ったRDS for PostgreSQLのDBエンジンはPostgreSQL 9.3.5です。

pgcryptoを使うための準備

まずはEC2からRDSにpsqlコマンドで接続します。psqlコマンドではデフォルトでSSL接続されていることがわかりますね。なお、Amazon Linuxにデフォルトでインストールされているpsqlコマンドは9.2.9なので、sudo yum install postgresql93してサーバ側とバージョンを合わせておくことをオススメします。

$ psql -h mydb.1234567890.ap-northeast-1.rds.amazonaws.com -U dbadmin mydb
ユーザ dbadmin のパスワード:
psql (9.3.5)
SSL 接続 (暗号化方式: DHE-RSA-AES256-SHA, ビット長: 256)
"help" でヘルプを表示します.

mydb=>

拡張モジュールを確認すると、ちゃんとpgcryptoが組み込まれていることがわかります。

mydb=> SHOW rds.extensions;

                                                                  rds.extensions


----------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------
-----------------------------------------------------
 btree_gin,btree_gist,chkpass,citext,cube,dblink,dict_int,dict_xsyn,earthdistance,fuzzystrmatc
h,hstore,intagg,intarray,isn,ltree,pgcrypto,pgrowlocks,pg_stat_statements,pg_trgm,plcoffee,pll
s,plperl,plpgsql,pltcl,plv8,postgis,postgis_tiger_geocoder,postgis_topology,postgres_fdw,sslin
fo,tablefunc,test_parser,tsearch2,unaccent,uuid-ossp
(1 行)

しかし、この状態ではpgcryptoは使えません。以下のようにファンクションが登録されていないからです。

mydb=> \df *pgp_sym*
                        関数一覧
 スキーマ | 名前 | 結果のデータ型 | 引数のデータ型 | 型
----------+------+----------------+----------------+----
(0 行)

なのでCREATE EXTENSIONします。

mydb=> CREATE EXTENSION pgcrypto;
CREATE EXTENSION

すると以下のようにファンクションが登録されます。

mydb=> \df *pgp_sym*
                                        関数一覧
 スキーマ |         名前          | 結果のデータ型 |  引数のデータ型   |       型
----------+-----------------------+----------------+-------------------+----------------
 public   | pgp_sym_decrypt       | text           | bytea, text       | normal(通常)
 public   | pgp_sym_decrypt       | text           | bytea, text, text | normal(通常)
 public   | pgp_sym_decrypt_bytea | bytea          | bytea, text       | normal(通常)
 public   | pgp_sym_decrypt_bytea | bytea          | bytea, text, text | normal(通常)
 public   | pgp_sym_encrypt       | bytea          | text, text        | normal(通常)
 public   | pgp_sym_encrypt       | bytea          | text, text, text  | normal(通常)
 public   | pgp_sym_encrypt_bytea | bytea          | bytea, text       | normal(通常)
 public   | pgp_sym_encrypt_bytea | bytea          | bytea, text, text | normal(通常)
(8 行)

pgcryptoを使ってみる

まずは普通のTableを作ってみます。

mydb=>  create table users (id int, name text, password text);
CREATE TABLE
mydb=> \d
          リレーションの一覧
 スキーマ | 名前  |    型    | 所有者
----------+-------+----------+---------
 public   | users | テーブル | dbadmin
(1 行)

この状態ではただのテキストフィールドなので、passwordというヤバそうな項目も、ただの文字列としてセットされてしまいます。丸見えですね。

mydb=> insert into users values(1,'佐々木','mypassword');
INSERT 0 1
mydb=> select * from users;
 id |  name  |  password
----+--------+------------
  1 | 佐々木 | mypassword
(1 行)

なのでパスワードフィールドをバイナリ列データ型としてTableを作成してみます。

mydb=> create table users_crypt (id int, name text, password bytea);
CREATE TABLE
mydb=> \d
             リレーションの一覧
 スキーマ |    名前     |    型    | 所有者
----------+-------------+----------+---------
 public   | users       | テーブル | dbadmin
 public   | users_crypt | テーブル | dbadmin
(2 行)

それではパスワードを暗号化して入れてみます。pgcryptoでは

  • 汎用ハッシュ関数
  • パスワードハッシュ化関数
  • PGP暗号化関数

の3つの暗号化関数が使えますが、今回は最も強固なPGP暗号化関数を使います。なお、最も強固=最も処理オーバーヘッドが大きいのでご注意を。

以下のように、pgp_sym_encrypt関数を使います。"mypassword"がユーザのパスワード、"pass"がPGPキーです。

mydb=> insert into users_crypt values(1,'佐々木',pgp_sym_encrypt('mypassword','pass'));
INSERT 0 1

単純にselectしてみると、バイナリデータになっていることがわかります。

mydb=> select * from users_crypt;
 id |  name  |                                                                          passwo
rd
----+--------+--------------------------------------------------------------------------------
----------------------------------------------------------------------------
  1 | 佐々木 | \xc30d04070302e2a74947b8fe93a865d23b019d77bb693b2eb5f24be6c2a5bc4fcb8d951e7b58f
9a50e7aaebf5ab87c82e6391bd8c7d2df0af4b1e953e6734b8565e623ce31bbaa225d4ba7d3
(1 行)

暗号化するときに指定したPGPキーを使ってselectすることで、復号され元の文字列が取得できます。

mydb=> select id, name, pgp_sym_decrypt(password, 'pass') from users_crypt;
 id |  name  | pgp_sym_decrypt
----+--------+-----------------
  1 | 佐々木 | mypassword
(1 行)

PGPキーが間違っていたら?もちろん怒られて復号できません。

mydb=> select id, name, pgp_sym_decrypt(password, 'fail') from users_crypt;
ERROR:  Wrong key or corrupt data

簡単!

さいごに

 データの暗号化は処理オーバーヘッドがかかりますので、システムへの適用についてはしっかりと検証する必要がありますが、よりセキュアなシステムのためにはこのようなモジュールを使うことも一つの手段かと思います。