2011/12/30

Javaでできるだけ安全にデータを直列化するためのてきとう備忘録

現在絶賛放置中の Web サイトを今後の発信基地として再開発したいと思い立った事がきっかけで、最近は Web サイトの簡易更新支援ツールの開発を考えている。

簡単な HTML ジェネレータと FTP クライアントを一緒にしたようなものを作りたい。

特に、FTP クライアントを作る際には、ユーザビリティのためにアカウント情報(ユーザ名とかパスワードとか)をローカルに保存しておき、できるだけワンタッチでサーバにアクセスできる仕様にしたいなと考えている。

データのセーブとロードは至るところで使いそうだし、今日はそこらへんの実装をステップバイステップで書き残しておこうと思う。

あ、この記事のサンプルコードには FTP 接続の実装例は含まれていないので、そういう内容を期待している方にはごめんなさい。あくまでデータのアーカイブに関する話題に絞ってもそれなりの分量になってしまったので……。



【備忘録その1: 直列化】

データのアーカイブに関する最も簡単な方法は、以下のようにしてユーザ名とパスワードをそのまま直列化してしまう事であろう。以下に Java のコードを示す。エラー処理が不十分なのはご容赦

まずは、ユーザアカウントが存在しない状態でプログラムを走らせる。

【実行例1】
ユーザアカウントが存在しません
ユーザ名を入力して下さい: tercel
パスワードを入力して下さい: hoge
セーブに成功しました
なお、ユーザ名とパスワードは任意で入力する事ができる。

この後、プログラムを再起動させると、既に存在するアカウント情報を自動的にロードして、その内容を表示してくれる。

【実行例2】
ユーザアカウントが存在します
ロードに成功しました
ユーザ名: tercel
パスワード: hoge

一見するとこれでも充分そうだが、自動生成された Account.txt をメモ帳で開くととんでもない事が分かる。
ユーザ名とパスワードが平文のまま保存されていて、明らかにまずい。常識的に考えて何らかの暗号化を施すべきであろう。



【備忘録その2: 秘密鍵暗号】

そこで、アカウント情報を秘密鍵暗号方式で暗号化する(アルゴリズムは Blowfish)。ちなみに、暗号化に使用した秘密鍵は復号するまで破棄してはいけない。

秘密鍵暗号方式を実装するために、UserData クラスを以下のように書き換える(ただし javax.crypto.Cipherjavax.crypto.spec.SecretKeySpecimport する事)。

この時点で UserData クラスの互換性が無くなっているので、もし Account.txt が残っている場合は削除する。


実行結果は先程と同じだが、保存されるファイルの内容が異なる。アカウント名とパスワードが傍目には判読できなくなっているのだ。
だが、今度は秘密鍵が平文で保存されている。これでは結局、家の鍵を玄関先に放置しておくようなものだ。

これに関しては、秘密鍵をクラスの外で持てばとりあえずはよさそうだが、どのみちプログラム中で秘密鍵を埋め込む事になるので気持ちが悪い。逆コンパイルされたらイチコロだ。

それよりも、アプリケーションを起動した瞬間にログイン情報の復号化を試みる現在の実装では、無関係な第三者がサーバに繋げてしまう事の方が遙かに問題のような気がする。



【備忘録その3: メッセージダイジェスト】

上記の問題に対処するため、さらに次の方針を採る事にする。

まず、事前にユーザが入力した秘密鍵は、適当なハッシュ関数(ここでは MD5)でメッセージダイジェスト化したハッシュ値として保存しておく。ハッシュ値から元の文字列を復元する事は不可能とされているため、たとえ漏れてもそれほど心配する必要はない。

アカウント情報を復元する際には、ユーザに対して改めて秘密鍵の入力を要求する。鍵を知らない第三者を弾くためである。

システムは入力された鍵をメッセージダイジェスト化し、保存してある鍵のハッシュ値との突き合わせを行う事で同一性のチェックを行う。もし両者が等しければ、入力された鍵の平文を用いてアカウント情報を復号化できる。

この方法だと、プログラムを起動するたびに秘密鍵の入力(認証)を要求する事になるので、少々操作性が悪くなる。しかし、手間の割に守れる情報量は多いので、それなりに割に合っているとは思う。

とりあえず、これを実装した結果を以下に示す。前回の Account.txt が残っている場合は削除する。

まずは UserData クラスから。特に hash メソッドがチャームポイントである。


また、これを使用する Main クラスも以下のように書き換える。


というか、面倒な人(というか僕)のためにだだ書きしたソースも丸ごと置いておこう。コピペすれば動くと思う。


これを実行してみよう。まずユーザアカウント情報が存在しない状態から。

【実行例1】
秘密鍵を入力して下さい: HOGEHOGE_TERCEL
ユーザアカウントが存在しません
ユーザ名を入力して下さい: tercel
パスワードを入力して下さい: hoge
セーブに成功しました
次に、この状態でアプリケーションを再起動してみる。

【実行例2】
秘密鍵を入力して下さい: HOGEHOGE_TERCEL
ユーザアカウントが存在します
ロードに成功しました
ユーザ名: tercel
パスワード: hoge
秘密鍵が正しいと、ちゃんと復号に成功する。

ふたたび再起動し、今度は秘密鍵を間違えてみる。

【実行例3】
秘密鍵を入力して下さい: hogehoge_tercel
ユーザアカウントが存在します
ロードに成功しました
秘密鍵が正しくありません
復号化を行いませんでした
Java Result: 1

どのみちロードまではしてしまうわけだが(というかロードしないと秘密鍵の突き合わせが行えないので)、異なる秘密鍵の入力をミスると復号化できないので、セキュリティ的にはそこそこ良さげな気がする。

ちなみに、Account.txt をメモ帳で開くとこんな感じになっている。かろうじて判読できるのはクラス名とフィールドだけだ。
これだけでは解読は困難だろう。

また、プログラムに秘密鍵を直接埋め込んでいるわけでもないので逆コンパイルされてもそれほど痛くもかゆくもない。

というわけで、ひとまずこれでログイン情報を外部に保存する最低限の仕組みが一応できたっぽい。

ふぅ…。



【参考サイト】

0 件のコメント:

コメントを投稿

ひとことどうぞφ(・ω・,,)