ゴミ箱.net

汚物は消毒

DBに保存したパスワードのハッシュ化アルゴリズムがどんどん陳腐化する件

パスワード認証機能つきのウェブアプリケーションにおいて、入力されたパスワードを照合するために比較用のデータをどこかに持つ必要がある。
パスワードそのものを保存するのはもちろんご法度。なので適当なハッシュ関数を使ってパスワードのハッシュ値を計算し、それをデータベースに保存するようにしていた。

ところが肝心のハッシュ関数が衝突耐性が破られたとかでどんどん陳腐化していく。MD5、SHA1はもう死んだ。SHA256もそのうち死ぬだろう。
かといって、ハッシュ関数をある日突然差し替えたら、それ以前に計算したハッシュ値がことごとく役に立たなくなる。それはユーザに大迷惑なので避けたい。
ならばハッシュ値だけでなく、そのハッシュ値をどのハッシュ関数で計算したかもあわせて保存すればいいわけだが…
車輪を最発明すれば済むだけの話だがそれもわずらわしい。ハッシュ値、ソルト、ハッシュ関数の識別子をどういうフォーマットで保存すればいいのか毎回考えるのも面倒だ。何か標準化された方法はないものだろうか?

そんなことを考えていたら、Apache Commons Codecのクラスorg.apache.commons.codec.digest.Cryptとやらを使えば願いが叶うと聞いた。

String saltedHash = Crypt.crypt(password);
のように書けば、現時点での最強のハッシュ関数を使い、ランダムなソルトつきハッシュ値を生成してくれる。
「アルゴリズムの識別子を含めたソルト」を指定してハッシュ値を計算するには
String saltedHash = Crypt.crypt(password, salt);
だ。

おもしろいことに、上記で出力されたソルトつきハッシュ値自体がソルトとしても機能する。
saltedHash == Crypt.crypt(password, saltedHash);
が成立するので、これを使うことでパスワードの照合ができるのだ。saltedHashには使用したハッシュ関数の識別氏も含まれるため、自動的にハッシュ関数が選択される。

その種はこうだ。
このメソッドで生成したハッシュ値は「$ハッシュ関数の識別子$ソルト$本来のハッシュ値」という形式になる(ハッシュ関数にDESを使った場合は少し違うが些細なことなので置いておく)。
一例を挙げると「$6$fD87BDEQ$duvOn9aGmehUe/psF.nJvDGh7RTh9qC8SbxR7G9iZ1cYMtqKvZDWfM3YMiit18fV9gNzmKq4.RROV6S4fi/i.. 」のような文字列になる。6というのがハッシュ関数としてSHA512を使うことを示す識別子、fD87BDEQがランダムに生成されたソルト、duvOn9aGmehUe…以下が真のハッシュ値である。
メソッドCrypt.cryptの第2引数に上記のソルトつきハッシュを指定すると、2番目の$より後ろを切り捨てて「$ハッシュ関数の識別子$ソルト」の部分だけを残して処理に使うのだ。

なおCrypt.cryptには、「アルゴリズムおよびソルトを指定しないもの」と「『アルゴリズムの識別子を含めたソルト』を指定するもの」があるが、「明示的にアルゴリズムだけを指定するが、ソルトをランダム生成するもの」が存在しない。
たとえば、何らかの事情でSHA1やSHA256などが使えず、あえて旧世代のハッシュ関数であるMD5を使う必要がある場合に、ソルトをランダム生成してパスワードをハッシュ化したい、といった場合にはどうすればいいか。
そのような場合は、使いたいハッシュ関数に応じてクラスMd5Cryptのメソッドmd5Crypt(byte[])クラスSha2Cryptのメソッドsha256Crypt(byte[])sha512Crypt(byte[])などのメソッドをソルト生成用に流用することができる。
先ほど、計算したハッシュ値自体がソルトとして使えると書いたことを思い出してほしい。
これらのメソッドは特定のハッシュ関数を使い、引数で指定したデータに対して「ハッシュ関数の識別子とランダム生成されたソルトを含むハッシュ値」を出力するものである。そして、その値をCrypt.crypt(password, salt)にソルトとして与えることができる。元データによって決まる真のハッシュ値の部分はどうせ使わないので、元データは何でもよい。とりあえず長さ0の配列でも指定しておこう。

SHA512を明示的に指定してランダムなソルトを使ったハッシュ値を得る例を示す。
String salt = Sha2Crypt.sha512Crypt(new byte[0]);
String saltedHash = Crypt.crypt(password, salt);

ちなみにこのメソッド群は、もともとLinuxのcrypt関数を移植したもので、処理も互換性があるらしい。
試してはいないが、上記の方法で生成したハッシュ値はJavaだけでなく、crypt関数を利用したC言語などのプログラムでも同様に使えるのだと思われる。

スポンサーサイト

PageTop

POIでシートの並べ替え→削除をすると内部データが壊れる件

JavaでExcelのデータを触れる便利なライブラリApache POI。色々なところで使われていてかなりメジャーなライブラリのはず。

そのPOIのけっこうきついバグを、ついこの間見つけてしまったようだ…

続きを読む

PageTop

JavaMailが文字化けする件2

以前の続き。
JavaMailの原因不明の文字化けに苦しんで、ようやく原因を突き止めて解決したと思ったら再発しやがた。
いったいおれ どうな て

真の原因は、どうやら同じアプリの中で、ウェブサービスのクライアントとJavaMailを同時に使っていたのがまずかったらしい。
[#JAX_WS-643] JAX-WS RI breaks JavaMail's DataContentHandlers in the container - Java.net JIRA

このいやらしいバグが発生したアプリは、外部のウェブサービスを呼び出して一部の処理をそいつに任せるようになっている。
ウェブサービスを呼び出すため、WSDLファイルをもとにwsimportでクライアントのソースコードを生成し、生成したクライアントをアプリケーションから呼び出すようにしていたのだが、こいつが食わせ物だった。
Javaランタイムに含まれるJAX-WS RIのバージョンが2.1.6より古いと、JavaMailも参照している静的な変数領域を勝手に書き換えてしまうらしく、そのせいで一度でもウェブサービスのクライアントを呼び出してしまうとそれ以降JavaMailの文字化けが続いてしまう。
そして、使っていたJavaランタイムのJAX-WS RIのバージョンがちょうど2.1.6だったせいで謎の文字化けに悩まされたわけだ。
1バイト文化圏の野蛮人どもめ。何も考えずに糞実装作りやがって。

対策としてはJAX-WS RIを2.1.7以上にアップグレードすること。簡易インストーラがついてるから導入が楽なのが救いだ。

PageTop

JavaMailが文字化けする件

JavaMail APIを使ってメールを送信するアプリケーションをJava Service Wrapperによってサービス化したら、なぜか送るメールがことごとく文字化けしてしまう。
文字セットとしてISO-2022-JPを指定しているにもかかわらず、データを見るとUTF-8で送信されているようだ。

マルチバイト文化圏の住民にありがちな話だと思ってぐぐっても、なぜか同じ症状の話がまったく出てこない。基本的な設定ミスの話ばかり引っかかるが、そもそも設定自体はできているのだ。というのも、同じ処理をスタンドアロンで実行したら文字化けせずにメールを遅れるのに、Java Service Wrapper経由で処理を実行したときだけ文字化けするのだ。

仕方がないので、JavaMailのソースコードを追っかけていって調べる羽目になった。
その結果、どうにか原因らしきものを突き止めることはできたのだが…

続きを読む

PageTop

結局「Java プログラマであるかを見分ける 10 の質問」の正解は何なんだよ

続きを読む

PageTop