はじめに
毎回、他の発表者の方々が有益情報を発表してくれる中で僕一人が誰得情報をひたすら発信しているわけですが、今回もご多分に漏れず Java 8 で認証系を自作する、というニッチな話をしてきました。
昨今のパスワード流出系のセキュリティインシデントが業界内で話題になるたびに「認証系なんてものは自作するようなもんじゃない」という認識が醸成されつつあると思います。しかし、マイナー or オレオレ Web アプリケーションフレームワークなどを利用しているとそうもいってはおられず、認証系が用意されていないときはやむなく認証系の自前実装が要求されることもあるでしょう。自分自身も周りを見渡していても、過去何度かそういうことがありました。
ただ、実装者の認証セキュリティに対する認識や知識によって脆弱な認証系を作り込むことも少なくなく、そういうのをなるべく減らしたいなー、という思いを込めて今回の発表に至りました。
どうしても認証系を自前実装しなければならないときに気をつけたいこと
前述した発表資料に書いてあることにはなりますが、今一度文章に起こしておきます。
アカウント別 salt の生成と暗号論的擬似乱数生成器の利用
セキュリティを少しかじったことがある人であれば「salt ってやつとパスワードを組み合わせたものをハッシュする」ということぐらいは知っているものと思います。ただ、どうも salt だけがひとり歩きしていることがあったりして、アプリケーションで共通の事前に用意された salt を使いまわす、みたいなアレな実装を見たことがある人はそこそこいるんじゃないでしょうか?
本来は、認証単位であるアカウント別に salt を生成すべきであり、かつ salt の生成には暗号論的擬似乱数生成器を用いるべきです。この salt の生成を Java で実現するならば、
static byte[] newSalt(int length) {
try {
byte[] result = new byte[length];
SecureRandom.getInstance(
// "NativePRNGNonBlocking"
// "NativePRNGBlocking"
"SHA1PRNG"
).nextBytes(result);
return result;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
のような実装になることでしょう。 SecureRandom.getInstance()
の呼び出しで指定しているのは擬似乱数生成アルゴリズムで、Java 7 以前は SHA1PRNG
だけだったのが、Java 8 では新たに
NativePRNG
NativePRNGBlocking
NativePRNGNonBlocking
が (Windows を除く) *nix-like なプラットフォーム向け JRE にて導入されたようです。
暗号学的ハッシュ関数の利用とストレッチング
salt を生成したら、あとはパスワードと組み合わせてハッシュ化、なのですが、これも単に SHA-* を使えばいい、というほど簡単ではありません。
結論から言ってしまえば、PBKDF2 などのキー派生 (導出?) 関数を使ってストレッチングすべき、となります。
Java 7 では PBKDF2 の実装は PBKDF2WithHmacSHA1
しか用意されていませんでしたが、Java 8 にて
PBKDF2WithHmacSHA224
PBKDF2WithHmacSHA256
PBKDF2WithHmacSHA384
PBKDF2WithHmacSHA512
の 4 つが導入されました。この PBKDF2 を用いたパスワードのハッシュ化コードは以下になります。
static byte[] hash(String password, byte[] salt, int numIterations, int numHashLength) {
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, numIterations, numHashLength);
SecretKeyFactory factory;
try {
factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
try {
return factory.generateSecret(keySpec)
.getEncoded();
} catch (InvalidKeySpecException e) {
throw new RuntimeException(e);
}
}
まとめ
なんだかんだ書いてはいますが、やっぱり僕含めて素人は認証系の実装をするのはやめたほうがいいですね (過去何度もダメな認証系を作ってしまったし…)。
既存ライブラリの Apache Shiro とか Spring Security とか、僕は使ったことないですが、多分このあたりの面倒なお仕事をこなしえくれるのではないでしょうか。
なお、
Javaで認証系だとこういったのもありますね #渋谷Java / leleuj/pac4j https://t.co/9faG21iXoq
— とーます (@grimrose) 2014, 9月 20
こういうライブラリもあるようですよ!