QA@IT

Railsアプリでの復号化が遅い

3662 PV

Railsアプリで個人情報を扱っていおり、パスワードだけでなく氏名も暗号化しています。
集計結果をCSV出力する機能があるのですが3万件程度を出力する際に1分近く時間がかかってしまっています(復号化しなければ10秒もかかりません)。データがまだ増えていく想定なので今のうちに手を打っておきたいと思っています。

ワークアラウンドとして、ローディングアニメーションや定期的なバッチ処理で事前に出力しておくということを考えていますが、もう少し良い方法がないものかと考えています。

MySQLで復号化する方法もありますが、下記構文を書かなければならないのが正直億劫です。。

CONVERT(AES_DECRYPT(UNHEX(name), 'password')

復号化する箇所によって、アプリ側でやるか、MySQL側でやるか選べる様にすることも考えていますが、現状、AES256で暗号化してしまっています。MySQL5.1では128bitしか扱えなく、データを洗い変える必要が出てくるのでこれは最終手段と考えています。

まずは、一般的にどうしてるかが知りたいのですが、うちではこうしてる、自分ならこうする、とかでもよいので色んな意見を聞きたいと思います。

よろしくお願いします。

=== 環境 ===
Ruby:1.8.6
Rails:2.2.3
MySQL:5.1.57

回答

コメント欄の記述を見て、パスワードから鍵生成しているように受け取ったのですが、鍵生成はやや重い可能性があるので、気をつけたほうがよいかもと思います。

参照元ブログには下記のようなコードが載っていますが、4行目のpkcs5_keyivgenが鍵生成メソッドです。soltはsaltと紛らわしいですが、実際はパスワードです。

def decrypt(bbb, solt = 'solt')
  dec = OpenSSL::Cipher::Cipher.new('aes256')
  dec.decrypt
  dec.pkcs5_keyivgen(solt)
  (snip)

このようなメソッドを3万回呼ぶと、3万回鍵生成が呼ばれてしまいます。鍵生成は中でハッシュを何千回も行なっていたりするので、3万回も呼ぶと、数千万回ハッシュを行なってしまい、不必要に重いかもと思います。パスワード(およびそれから生成される鍵)がつねに同一だとすると、1回でよいはずです。

マニュアルには例えば下記のようなコードが載っていて、鍵生成にpbkdf2_hmac_sha1メソッドを使っています。このやり方だと生成した鍵(とIV)をとっておけるので、decryptメソッドの中で鍵生成するのでなく、引数で鍵を渡すようにすれば、CSV出力処理時の鍵生成を1回にできるのでは、と思います。

(snip)
enc = OpenSSL::Cipher.new("AES-256-CBC")
enc.encrypt

key_iv = OpenSSL::PKCS5.pbkdf2_hmac_sha1(pass, salt, 2000, enc.key_len + enc.iv_len)
key = key_iv[0, enc.key_len]
iv = key_iv[enc.key_len, enc.iv_len]

enc.key = key
enc.iv = iv
(snip)
編集 履歴 (0)
  • ありがとうございます。カギは固定なのでこちらの方法が使えそうです。ちょっと試してみようと思います。 -
  • 試してみたのですがrubyが1.8.6のため、pbkdf2_hmac_sha1は無いと怒られてしまいました。。しかし、鍵生成部分が遅いことはわかったので他の方法で回避してみようと思います。ありがとうございました。 -

MySQL5.1での暗号化
MySQL5.1をいじれば 256バイト長の暗号化が可能となることがかかれています。

Linux(x86)+MySQLで3GB制限がある等ここら辺も注意が必要

編集 履歴 (1)
  • ありがとうございます。MySQLのソースをいじるのは出来ればやりたくないのですが、その様な方法もあるということで頭に入れておきます。 -

3万件程度のCSVで、復号しなければ10秒、復号すれば60秒というのは、(それほど特殊なレコード構造ではないと想定すると)どちらもだいぶ遅い気がしますね。

とくに「復号しなければ10秒」のほうが気になります。ActiveRecordで3万件というと、そこそこメモリ食いますが、メモリは潤沢にありますか?足りないとスワップ・スラッシングしてるかもしれません。スワップしてたら、何もかもがとんでもなく遅くなります。

実際どうかはfree/top/vmstatなどで確認するとして、もし当たりだとすると、

  • 一度に全件メモリに読まず、find_in_batchesを使ってN件づつCSVに追記出力する
  • そもそもActiveRecordのインスタンス生成はとても遅いので、CSVの生成にモデルのメソッドを使ってなければ、なるべくARをバイパスさせる(pluckメソッドとか)

などの工夫で、メモリを節約&速度を稼げると思います。

あと、さらに先の話だとは思いますが、CSVの生成ジョブ自体が並列に実行されると、メモリ的にもCPU的にも多大な負担がかかるので、UI的にボタンの2度押しできないようにdisableするのはもちろんのこと、サーバサイドでも並列実行されないようにロックをかけておくのがベターだと思います。

Redisを使っているなら、拙作の https://github.com/kenn/redis-mutex というのもあります。

編集 履歴 (0)
  • 回答ありがとうございます。
    確かに「複合しなければ10秒」という時点でだいぶ遅いとは思います。それはそれでチューニングしなければならないのですが、複合化処理が入るだけで10秒→60秒と約6倍になってしまうところがどうしたものかと思ってます。
    メモリに関しては12GBのローカルマシンで動かしてみているので、足りないということはなさそうです。メモリ不足の際は上記方法を試してみようと思います。
    -
ウォッチ

この質問への回答やコメントをメールでお知らせします。