【Oracle】DBMS_CRYPTOパッケージを用いてデータを暗号化する

TL;DR

  • OracleDBMS_CRYPTOという暗号化関数がまとまった標準パッケージがある。
  • 引数のデータ型はRAW型(デフォルトで最大2000bytes、設定変更で32757bytes)。CHAR/VARCHAR型との変換はUTL_I18Nパッケージを用いる。
  • 暗号化関数DBMS_CRYPTO.ENCRYPT関数 / 復号化DBMS_CRYPTO.DECRYPT関数は非DETERMINISTICなので直接関数インデックスに用いることはできない。ストアドファンクションでラップする必要がある。
  • 暗号化 / 復号化キーを隠匿するのはできるが各方面満点の解はない

前提

  • Oracle19c

DBMS_CRYPTOパッケージ

公式ドキュメント

docs.oracle.com

利用の前提条件

  • Oracle19c現在、追加ライセンス不要。
  • 初期では権限不足でどのユーザーも使えないため、権限付与SQLの実行要(たぶんsysユーザーが必要)
-- 権限付与
GRANT execute ON SYS.DBMS_CRYPTO TO youruser;

利用例

使い方例(暗号化)

-- 暗号化
SELECT
 DBMS_CRYPTO.ENCRYPT(
    UTL_I18N.STRING_TO_RAW('暗号化対象データ', 'AL32UTF8'),
    6+256+4096,  -- 暗号化指定。このパラメータはAES128
    HEXTORAW('010203040506070809a0b0c0d0e0f000') -- 暗号化キー
  )
  AS crypt
FROM
  dual;

result

CRYPT
--------------------------------------------------------------------------------
2F5C489FB4BE399A4EB81FFBA6B6E28645B7060B5EC1D93F63571A4E0F119C02
  • 第二引数の値は、DBMS_CRYPTOパッケージ配下の定数を参照。
    • URL:DBMS_CRYPTO
    • 例えば 6 + 256 + 4096 は、以下で確認できる
SET SERVEROUTPUT ON

BEGIN
   DBMS_OUTPUT.PUT_LINE(DBMS_CRYPTO.ENCRYPT_AES128);
   DBMS_OUTPUT.PUT_LINE(DBMS_CRYPTO.CHAIN_CBC);
   DBMS_OUTPUT.PUT_LINE(DBMS_CRYPTO.PAD_PKCS5);
END;
/

使い方例(復号化)

-- 復号化
SELECT
 UTL_I18N.RAW_TO_NCHAR(
   DBMS_CRYPTO.DECRYPT(
      crypt,
      6+256+4096,
      HEXTORAW('010203040506070809a0b0c0d0e0f000')
    )
  , 'AL32UTF8') as plain
FROM
  (SELECT
   DBMS_CRYPTO.ENCRYPT(
      UTL_I18N.STRING_TO_RAW('暗号化対象データ', 'AL32UTF8'),
      6+256+4096, 
      HEXTORAW('010203040506070809a0b0c0d0e0f000')
  ) AS crypt
  FROM
    dual
  );
  • 結果はRAW型なので RAW_TO_CHARRAW_TO_NCHAR で復号する必要あり
  • 暗号アルゴリズムによって、暗号キー・復号キーが異なる場合がある。(RSAなどが該当)

関数インデックス

  • 以下はエラーとなる。
CREATE TABLE sample(crypt RAW(2000));
CREATE INDEX idx_sample ON sample(DBMS_CRYPTO.DECRYPT(crypt, 0, HEXTORAW('00')));
CREATE INDEX idx_sample ON sample(DBMS_CRYPTO.DECRYPT(crypt, 0, HEXTORAW('00')))
                                  *
行1でエラーが発生しました。:
ORA-30553: 関数がDETERMINISTICではありません。
  • 以下のように、DETERMINISTICなストアドファンクションを定義することで回避できる。
CREATE OR REPLACE FUNCTION DECRYPT_AES128
  (
    -- 暗号データ
    src IN VARCHAR2,
    -- 復号キー。16bytesであること
    key IN VARCHAR2
  )
  RETURN RAW DETERMINISTIC
IS
BEGIN
  RETURN DBMS_CRYPTO.DECRYPT(
    UTL_I18N.STRING_TO_RAW(src, 'AL32UTF8'),
    DBMS_CRYPTO.ENCRYPT_AES128 +
    DBMS_CRYPTO.CHAIN_CBC +
    DBMS_CRYPTO.PAD_PKCS5,
    UTL_I18N.STRING_TO_RAW(key, 'AL32UTF8')
  );
END;
/
CREATE INDEX idx_sample ON sample(DECRYPT_AES128(crypt, 0, HEXTORAW('00')))
  • 後述するが、関数インデックスはデータファイル上暗号化されないので注意。

暗号化キーの隠匿化

  • SQLのパラメータとしてキー値を渡したりSQL文にベタ書きすると、Oracle内ログ、アプリログ、通信にキーが曝露するのでセキュリティ上非常によくない。
  • 色々方式はあるが、PL/SQL内にキーを隠匿し、SOURCEも隠匿する手法にいきついたので紹介

SOURCEを秘匿化しながらストアドファンクションを定義

DECLARE
   ddl_text VARCHAR2(32767);
   key_nchar VARCHAR2(32) := '010203040506070809a0b0c0d0e0f000';

   FUNCTION generaye_ENCRYPT_AES128_DDL RETURN VARCHAR2
   IS
   BEGIN
      return 'CREATE OR REPLACE FUNCTION ENCRYPT_AES128' ||
         '  (' ||
         '    src IN VARCHAR2' ||
         '  )' ||
         '  RETURN RAW DETERMINISTIC' ||
         ' IS' ||
         ' BEGIN' ||
         '  RETURN DBMS_CRYPTO.ENCRYPT(' ||
         '    UTL_I18N.STRING_TO_RAW(src, ''AL32UTF8''),' ||
         '    DBMS_CRYPTO.ENCRYPT_AES128 +' ||
         '    DBMS_CRYPTO.CHAIN_CBC +' ||
         '    DBMS_CRYPTO.PAD_PKCS5,' ||
         '    HEXTORAW('''|| key_nchar ||''')' ||
         '  );' ||
         'END;';
   END generaye_ENCRYPT_AES128_DDL;
   FUNCTION generaye_DECRYPT_AES128_DDL RETURN VARCHAR2
   IS
   BEGIN
      return 'CREATE OR REPLACE FUNCTION DECRYPT_AES128' ||
         '  (' ||
         '    src IN RAW' ||
         '  )' ||
         '  RETURN VARCHAR2 DETERMINISTIC' ||
         ' IS' ||
         ' BEGIN' ||
         '  RETURN UTL_I18N.RAW_TO_NCHAR(DBMS_CRYPTO.DECRYPT(' ||
         '    src,' ||
         '    DBMS_CRYPTO.ENCRYPT_AES128 +' ||
         '    DBMS_CRYPTO.CHAIN_CBC +' ||
         '    DBMS_CRYPTO.PAD_PKCS5,' ||
         '    HEXTORAW('''|| key_nchar ||''')' ||
         '  ), ''AL32UTF8'');' ||
         'END;';
   END generaye_DECRYPT_AES128_DDL;
BEGIN
   ddl_text := generaye_ENCRYPT_AES128_DDL();
   EXECUTE IMMEDIATE ddl_text;
   SYS.DBMS_DDL.CREATE_WRAPPED(ddl_text);
   ddl_text := generaye_DECRYPT_AES128_DDL();
   EXECUTE IMMEDIATE ddl_text;
   SYS.DBMS_DDL.CREATE_WRAPPED(ddl_text);
END;
/
  • デモ
select
 ENCRYPT_AES128('テストデータ') as crypt,
 DECRYPT_AES128(ENCRYPT_AES128('テストデータ')) as plain
from dual;
CRYPT
--------------------------------------------------------------------------------
PLAIN
--------------------------------------------------------------------------------
21E108C3DCA3AFFDAC709FB1079F71724C90C3EB22FC90E7D03EC066598568D8
テストデータ
  • 仕組みはよく分からないが、SYS.DBMS_DDL.CREATE_WRAPPED()を介すことでSOURCEが簡単に見れなくなる
  • Oracleのドキュメントを見ると動的に生成、とあるので、もしかしたらパフォーマンスに影響があるかもしれない(未検証)
SELECT text FROM ALL_SOURCE WHERE name = 'ENCRYPT_AES128';
FUNCTION ENCRYPT_AES128 wrapped
a000000
369
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
8
12a 128
rcNM2xfPeGeX3nLRY90YTcVHg+owg3nQDEgVZy+ix4kZiJlGlznTgNjUhvtgN2E96/8XKgYS
te98VEs30mFBh1xkI2H8uhvGK/l78NQCUdjjYInR42xy0nnbSF+Joq33V80CoUcoKtyQinr4
DiI3NyfE3M4d3vxM49YNkBmB1g2xedLplesOBkKkDfD63/IBtpdHnyszReXCz8ALtVJ6G0pP
TqCzx6ANEPB769u52UCVylVTD3kgjYieiXU4SkQZCDeeTxJ2uNu9F3GmkSnuzylTk7xM+2ha
DIA=

セキュリティ的な評価

脅威 評価 追加対策の余地
OSに不正ログインされ、Oracleのテーブルデータファイルを詐取される 〇:暗号化されており、復号キーも漏洩していない
OSに不正ログインされ、Oracleのインデックスデータファイルを詐取される ×:インデックス値は平文で保存されている Oracle Advanced Security(有料オプション)のTDEでデータファイルを暗号化する。問題のあるキーの組み合わせ(個人名, 電話番号の複合インデックスなど)を用いない、インデックス化を避けるなどの設計による対処
DBに不正ログインされ、SQLを実行される △:復号化関数や手法を知っていれば平文を漏洩してしまう 関数名の難読化、手掛かりにいきつくまでの妨害・ログの秘匿化
DBにログインした作業者による詐取 △:復号化関数や手法を知っていれば平文を漏洩してしまう 保守ユーザ/アプリユーザの分離・権限の制限
アプリケーション脆弱性によるSQLインジェクション経由での詐取 〇:復号化関数の存在を知ることは困難と考えられるため、暗号化したデータしか表示されない

TDEとの比較

  • TDEは、Oracle Advanced Security(有料オプション)の機能の1つ。
  • TDEは、Oracleのデータファイルを暗号化する。そのため、SQL実行による参照は保護されず、アプリケーションからのSQLインジェクションにも対応できない。
  • 情報のマスキングは、Oracle Advanced Securtyの他オプションでも可能だが、特定ユーザーはマスキング項目を非表示にするなどの作業者へのいじわると、暗号化による保護というよりは内部統制のソリューションであり、DBへの不正接続・SQLインジェクションなどによる任意クエリ実行の対策にはならないと考えている。
    • 詳しく調べてないので、有識者に聞いてみたい。