Hatena::Grouppostgresql

PostgreSQL 雑記 このページをアンテナに追加 RSSフィード

2009-06-28透過的なデータ暗号化 (案) このエントリーを含むブックマーク このエントリーのブックマークコメント

セキュリティ、監査、改竄防止、アクセス制御、情報漏洩防止、暗号化……この辺りはデータベースでどうすれば良いかを聞かれることは多いものの、「そもそも何を守りたいのか?」と突っ込むと、いまいち明確な答えが得られないので後回しになっていました。最近やっと、この中の「暗号化」についてはイメージが比較的定まってきたので、試作してみました。

暗号化への要請は以下を想定しています:

  • データディスクの盗難対策になればよい。
  • SQLにパスワードを書きたくない。
  • アプリケーションからは暗号化されていないのと同様に扱いたい。

実装としては、bytea の簡単なラッパ型を作り、その入出力関数で pgcrypto の pgp_sym_encrypt/decrypt を呼んでいるだけです。パスワードは GUC 変数から取得するので SQL に埋め込む必要はありません。postgresql.conf またはクライアントから (PGOPTIONS 環境変数など) 設定する想定です。

=# CREATE TABLE tbl (plain text, cipher encrypted_text);
=# SET pgcrypto.options = 'cipher-algo=aes128'; ★暗号形式をGUC変数で与える。
=# SET pgcrypto.password = 'passwd'; ★パスワードもGUC変数で与える。
=# INSERT INTO tbl VALUES ('postgres', 'postgres');
INSERT 0 1
=# SELECT * FROM tbl;
  plain   |  cipher
----------+----------
 postgres | postgres
(1 row)

勝手にエンコード/デコードされるので、このように普通のテキスト型と同様の入出力になります。内部ではきちんと暗号化されていることは、以下のように確認できます。

=# SELECT pg_column_size(plain) AS plain,
          pg_column_size(cipher) AS cipher FROM tbl;
 plain | cipher
-------+--------
     9 |     75 ★エンコードするとサイズが増える場合もある。
(1 row)

=# SELECT substring(page, lp_off + 25, 9) AS plain,
          substring(page, lp_off + 25 + 9, 75) AS cipher
     FROM heap_page_items(get_raw_page('tbl', 0)), get_raw_page('tbl', 0) AS page;
    plain     |    cipher
--------------+----------------------------------
 \023postgres | \227\303\015 (中略) \212\241\366 ★バイナリダンプ内に元の文字列は無い。
(1 row)

この encrypted_text 型は、contrib/pgcrypto の拡張として提案してみたいと思います。

残るToDo:

  • インデックスでの範囲検索を許すか? 大小関係がバレるのは問題ですが、要望も多いので……。
  • SHOW pgcrypto.password を防ぐ方法があるか? write-only パラメータ?
  • 「パスワードを変更したい」というワガママにどう応える?


追記: 背景を書き忘れていました。Linux の暗号化ファイルシステムは使いたくない / 使わせたくないというオーダーもありました。性能、信頼性、メンテナンスの継続性の面で、あまり良いものが無いのだとか。

kaigaikaigai2009/06/29 18:41> インデックスでの範囲検索を許すか? 大小関係がバレるのは問題ですが、要望も多いので……。

この場合、データの機密性が侵害されるのは周辺状況からの推測(not 直接的チャネル)です
ので、一種の covert channel (隠れチャネル)と考える事ができます。
直接のデータ読み出しと異なり、一般に商用システムで隠れチャネル対策までが求められる
ケースは稀で、Oracle等商用DBMSのISO/IEC15408認証の評価レポートを読んでも、その辺の
要件に対しては対象外としているようです。
軍用で隠れチャネルの対策も行いたい場合は、DB自体の基本設計を見直さざるを得ませんが、
それは流石に(私から見ても)バランスを欠いているように思えます。

> SHOW pgcrypto.password を防ぐ方法があるか? write-only パラメータ?
GUC変数の定義の中に、SHOW/SET時にコールされる関数ポインタってありませんでしたっけ?
それを使ってSHOW時に「******」みたいに伏字で返却するとかはできそうに思います。

> 「パスワードを変更したい」というワガママにどう応える?
GUC変数 pgcrypto.password の状態に関係なく、暗号化文字列(第一引数)を所与の暗号化キー(第二引数)で復号化する関数を定義しておいて
UPDATE foo SET bar = pgcrypt_decpypt(bar, 'old_passwd') WHERE baz = 123;
みたいな感じでどうでしょうか?