※ 本記事は2017年11月18日に公開されたものです。
前回、アプリケーション暗号化を利用する際には、さまざまな影響があるので考慮が必要と説明しました。しかし、アプリケーション暗号化をおこなうことが有効なケースもあります。アクセス頻度が低い、一度にアクセスされるレコード数が少ない、検索条件になっていないといった条件を満たす特に重要なデータに関してはアプリケーションでの暗号化が有効です。
たとえば、ユーザーのパスワードをデータベースに格納する場合、アプリケーションの暗号化が有効です。アクセスはログイン時とパスワード変更時のみと低く、一度にアクセスするのは自分のパスワード1件だけで、パスワードをキーとして検索をされることはありません。そして、パスワード情報は重要な情報です。
クレジットカード番号やマイナンバーも暗号化すべき重要な情報ですが、請求処理や年末調整処理などのバッチで一度に大量の件数にアクセスすることがあったり、これらの番号をキーとして検索をおこなう可能性も考えられるため、アプリケーションでの暗号化が必ずしも有効とは言えません。番号をキーとする検索の例としては「あるクレジットカード番号は偽造の恐れがあるのですが、この番号での購入があったか調査してください。」という依頼への対応などが考えられます。
名前、住所、電話番号などの個人情報はアクセス頻度が高く、一度にアクセスされるレコード数が多いケースも多数考えられ、検索条件として使われることも多いのでアプリケーションでの暗号化が有効とは言えません。
クレジットカード番号やマイナンバーは、元の値を利用して処理をおこないますので、暗号化したデータを復号する必要があります。一方、パスワードは入力した値と保存された値が一致するかどうかの確認をおこなうだけで値自体を利用するわけではありません。暗号化した後の値が一致するかどうかを確認すればよいわけで、元のデータ(平文パスワード)に復号する必要はありません。さらに言ってしまうと復号できる方式で暗号化する必要がありません。復号するためには暗号鍵が必要ですが、暗号鍵の管理にはコストがかかります。そのためこのような元の値を利用しないようなケースでは、暗号化したデータを復号できる可逆性のある暗号化ではなく、元のデータを判別できないデータにした後、元のデータに戻せない不可逆性のハッシュ化を利用することができます。
Oracle Databaseでは、DBMS_CRYPTOパッケージでハッシュ化をおこなうためのHASHファンクションを提供しています。
利用できるハッシュアルゴリズムはOracle Databaseのバージョンにより異なりますので詳細はマニュアルを参照してください。
古いハッシュアルゴリズムはすでに危殆化しているものもありますので、可能であればCRYPTREC暗号リストに載っているハッシュ関数を利用することを推奨します。
ハッシュ化のためのファンクションの例は以下の通りです。
create or replace package myHash as function hash(var in varchar2) return raw; end; / create or replace package body myHash as myType number := DBMS_CRYPTO.HASH_SH256; function hash(var in varchar2) return raw is begin return dbms_crypto.hash(src => UTL_I18N.STRING_TO_RAW (var, 'AL32UTF8'), typ => myType); end; end; /
作成したファンクションを利用してハッシュ化をおこなう例は以下の通りです。
SQL> select myHash.hash('Password') from dual; MYHASH.HASH('PASSWORD') -------------------------------------------------------------------------------- E7CF3EF4F17C3999A94F2C6F612E8A888E5B1026878E4E19398B23BD38EC221A
実際にユーザー名とパスワードを格納するためのAUTHTABLE表の例は以下の通りです。パスワードを格納数列は今回はハッシュアルゴリズムにSHA-256を利用しているためデータ型をRAW(256)で定義します。
create table authtable ( username varchar2(32), password raw(256));
サンプルデータとして、ユーザー名user01、パスワードpass_user01というレコードを挿入します。
insert into authtable values ('user01', myHash.hash('pass_user01'));
格納されたパスワードはハッシュ化されている(元のデータが推測できない)のが確認できます。
SQL> select * from authtable; USERNAME -------------------------------- PASSWORD -------------------------------------------------------------------------------- user01 547CDB4C2B0954F3534EB032329EAC8D33C050870455566BB9579D296D79A424
アプリケーションからは、たとえば以下のようなSQL文を発行してユーザー認証を実施します。
select username from authtable where username='ユーザー名' and password = myHash.hash('パスワード');
ユーザー名とパスワードが正しい場合にはユーザー名が戻り、ユーザー名とパスワードが間違っている場合にはレコードが戻りません。
SQL> select username from authtable where username='user01' and password = myHash.hash('pass_user01'); USERNAME -------------------------------- user01 SQL> select username from authtable where username='user01' and password = myHash.hash('wrong_password'); レコードが選択されませんでした。
このユーザー認証ロジックをアプリケーションに組み込むときには、リテラルの連結でプログラムを組み込まず、必ずバインド変数を利用するようにしてください。リテラルを連結してSQL文を生成するとSQLインジェクション攻撃が可能な脆弱なアプリケーションとなってしまいます。
バインド変数を利用するSQLインジェクションが起こらない実装例 String sqltext = "select username from authtable where username = ? and password = myHash.hash(?)"; PreparedStatement pstmt = conn.prepareStatement(sqltext); pstmt.setString(1, v_username); pstmt.setString(2, password); ResultSet rset = pstmt.executeQuery(); リテラルの連結を利用したSQLインジェクションが起こってしまう実装例 String sqltext = "select username from authtable where username = '" + v_username + "' and password = myHash.hash('" + v_password + "')"; ResultSet rset = stmt.executeQuery(sqltext);
リテラルの連結を利用したSQLインジェクションが起こってしまう例ではアプリケーションからユーザー名(v_username)に「user01′ –」、パスワード(v_password)に「a」(何でもよい)を指定すると、sqltextは以下のようになります。
select username from authtable where username = 'user01' --' and password = myHash.hash('a')
ここで「–」以下はコメントとして無視されますので、結果としてuser01というユーザー名が戻ります。user01だけでなく、ユーザーが存在すれば任意のユーザーでユーザー名が戻りますので、結果として任意のユーザーに成り済まされて認証が成功してしまいます。
アプリケーションの認証部分に脆弱性があると、結果として任意のユーザーに成り済まされて、正規のユーザーとして自由にアプリケーションが利用されてしまいますので注意が必要です。
今回は場合によってはアプリケーションの暗号化が向いているケースがある例、また暗号化ではなくハッシュ化のほうが運用が簡単でより安全なケースとOracle Databaseでのハッシュ化の実装例を紹介しました。アプリケーションでの暗号化やハッシュはOracle Databaseの機能を利用しなくても、JAVAなどのプログラム側で実装したり、外部製品と組み合わせたりしても実現可能です。
今後紹介予定のデータベースでの暗号化機能と組み合わせて利用するとより安全にデータを保護することができますので、データベース設計の参考にしてください。
