暗号化・復号化処理の組込み (GearedCipher)

概要

主にSQL関数を使った暗号化処理を、ConditionBean や Behavior などに組み込むことができます。 @since 0.9.8.4

これを、GearedCipher と呼びます。

可逆のケース

Behaviorで更新すると...

e.g. Behavior の updateNonstrict() で暗号化 {MEMBER.UPDATE_USER} @Java
Member security = new Member();
security.setMemberId(3);
security.setUpdateUser("foo"); // プレーンテキストのままセット
memberBhv.updateNonstrict(security);
e.g. Behavior の updateNonstrict() で暗号化 {MEMBER.UPDATE_USER} @DisplaySql
update MEMBER
   set UPDATE_USER = hex(aes_encrypt('foo', 'himitsu'))
     , ...
     , VERSION_NO = VERSION_NO + 1
 where MEMBER_ID = 3

ConditionBeanで検索すると...

e.g. ConditionBeanで暗号化した上で比較 {MEMBER.UPDATE_USER} @Java
MemberCB cb = new MemberCB();
cb.query().setMemberId_Equal(1);
Member member = memberBhv.selectEntityWithDeletedCheck(cb);
String updateUser = member.getUpdateUser(); // 復号化された foo が取れる
e.g. ConditionBeanで暗号化した上で比較 {MEMBER.UPDATE_USER} @DisplaySql
select dfloc.MEMBER_ID as MEMBER_ID, dfloc.MEMBER_NAME as MEMBER_NAME, ...
     , convert(aes_decrypt(unhex(dfloc.UPDATE_USER), 'himitsu') using utf8)
       as UPDATE_USER
  from MEMBER dfloc

不可逆のケース

検索条件でも...

e.g. ConditionBeanで暗号化した上で比較 {MEMBER_SECURITY.LOGIN_PASSWORD} @Java
MemberSecurityCB cb = new MemberSecurityCB();
cb.query().setLoginPassword_Equal("foo"); // プレーンテキストのままセット
...
e.g. ConditionBeanで暗号化した上で比較 {MEMBER_SECURITY.LOGIN_PASSWORD} @DisplaySql
...
  from MEMBER dfloc 
 where dfloc.LOGIN_PASSWORD = sha1('foo') -- 暗号化された値同士で比較

組み込みのやり方

DBFluteConfig

DBFluteConfig で GearedCipherManager を登録します。暗号化カラムに対して CipherFunctionFilter の実装クラスを関連付けます。

e.g. 暗号化・復号化処理の組み込み {MEMBER.UPDATE_USER} @Java
// 実際にキーをどのようにして扱うかは実業務では厳密に考慮する必要がある
// ここではとりあえず Example ということで簡単でベタなやり方で表現している
final String secretKey = ...

GearedCipherManager manager = new GearedCipherManager();
ColumnInfo updateUser = MemberDbm.getInstance().columnUpdateUser();
manager.addFunctionFilter(updateUser, new CipherFunctionFilter() {
    public String encrypt(String valueExp) {
        String exp = "hex(aes_encrypt(%1$s, '%2$s'))";
        return String.format(exp, valueExp, secretKey);
    }

    public String decrypt(String valueExp) {
        String exp = "convert(aes_decrypt(unhex(%1$s), '%2$s') using utf8)";
        return String.format(exp, valueExp, secretKey);
    }
});
e.g. 不可逆の暗号化処理の組み込み {MEMBER_SECURITY.LOGIN_PASSWORD} @Java
GearedCipherManager manager = new GearedCipherManager();
ColumnInfo loginPassword = MemberSecurityDbm.getInstance().columnLoginPassword();
manager.addFunctionFilter(loginPassword, new CipherFunctionFilter() {
    public String encrypt(String valueExp) {
        return "sha1(" + valueExp + ")";
    }

    public String decrypt(String valueExp) {
        return valueExp; // どのみち戻せないのでそのまま
    }
});

フィルタの複数登録

同じカラムに対して複数のフィルタを追加した場合は、追加された順番にフィルタが実行されます。

組み込み対象

ポリシー

  • アプリから値をDBに格納する場合は暗号化
  • DBからアプリで値を取得する場合は復号化
  • DBからDBに直接データを移行する場合は何もしない (暗号化されたデータを維持)
  • DB上の値とDB上の値の比較条件では互いに復号化する (復号化された状態で比較)
  • 外だしSQLやプロシージャは全くの対象外 (何もしないというより何もできない)

対象となる機能

組み込んだ暗号化・復号化処理が動作する機能は以下の通りです。

ConditionBeanによる検索
カラムデータを取得するときに復号化、検索条件では暗号化。 ScalarSelectも含む。
DerivedReferrer では集計関数の処理の前に復号化されます。 よって、(Query)DerivedReferrer ではアプリから指定した暗号化されない条件値と復号化された集権関数の結果で比較されます。
ScalarCondition では対象のカラムと集計関数の中において復号化され、復号化された状態同士で比較されます。 よって、パフォーマンス上の問題がある場合は、ファンクションインデックスなどの導入も検討した方が良いでしょう。
ColumnQuery では、互いのカラムで復号化されます。ScalarCondition と同じくファンクションインデックスなどの導入も考慮に入れると良いでしょう。
一件登録・更新
カラムデータを更新するときに暗号化
バッチ登録・更新
カラムデータを更新するときに暗号化
ConditionBeanによる更新・削除
カラムデータを更新するときに暗号化、検索条件では暗号化
ConditionBeanによる登録の固定値
queryInsert() における固定値の埋め込みにだけ暗号化 (その他は暗号化も復号化もなし)

基本的には、暗号化対象のカラムはあまりそれ自体のデータを使って複雑な計算処理や比較条件などが行われないことが前提となっています。 例えば、ログインパスワードやクレジット番号などを想定しているため、それらのカラムに対して利用することが考えにくい ScalarCondition は対象外となっていますし、その他にもドキュメントとして列挙されているもの以外でのグレーゾーンな領域で、 業務的に効果的でない仕様になっている可能性はあります。

対象外のカラム

また、暗号化対象カラムが以下の場合は利用できません。

  • PK
  • FK
  • 排他制御カラム

Exampleのススメ

dbflute-mysql-example では、実際に暗号化・復号化処理を組み込んでいます。 ぜひExampleを参考にしてみてください。