2014-09-21

はじめに

毎回、他の発表者の方々が有益情報を発表してくれる中で僕一人が誰得情報をひたすら発信しているわけですが、今回もご多分に漏れず 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 とか、僕は使ったことないですが、多分このあたりの面倒なお仕事をこなしえくれるのではないでしょうか。

なお、

こういうライブラリもあるようですよ!

参考文献

2014-09-17

VM 上の Ubuntu に apt-get で mysql-server-5.6 をインストールし、VM の外から VM 内部の mysqld に接続しようとしたときにこのエラーが発生した。

原因は my.cnf に

bind-address = 127.0.0.1
が設定されていたためであった。

この行を my.cnf から削りとって、ようやくVM 外部から mysqld に接続できるようになったけど、毎回こんな感じのトラブルで時間を喰われてしまっているわけで、MySQL 力が低いと何かと生きるのが辛い…

2014-09-15

「えーマジ native2ascii!?」「プロパティエディタプラグインが許されるのは J2SE 5.0 までだよねー」

ジャバエンジニアはそろそろ、プロパティファイルを扱う場合に native2ascii やら各種プロパティエディタプラグインやらの呪縛から解放されてもいいと思うのです…

概要

Java でアプリケーションの各種設定を記述したプロパティファイル (.properties) を取り扱う場合、 Properties クラスもしくは ResourceBundle クラスのお世話になるかと思います。

一昔前までは、プロパティファイル内で日本語などいわゆるマルチバイト文字を含む場合に、 native2ascii コマンドを利用して ASCII コードの文字のみで表現できる ユニコードエスケープ (!= UTF-xx エンコーディング) 表現に変換することが必要とされていました。

しかし、Java は 7 or 8 がメインストリームになり、また UTF-8 エンコーディングされたテキストを取り扱うことがわりと当たり前になりつつある昨今、いまだに Eclispe にわざわざプロパティエディタプラグインを導入してプロパティファイルをしこしこ編集したり、プロダクション環境でユニコードエスケープされたプロパティファイルを四苦八苦しながらパラメータ調整したりする現場があったりなかったりするようなので、ここで native2ascii とかプロパティエディタプラグインを必要と せずに UTF-8 エンコーディングされたプロパティファイルを扱う方法についてメモしておきます。

UTF-8 エンコーディングされたプロパティファイルを取り扱う方法

Properties クラスと ResourceBundle クラスそれぞれについて説明します。

その 1 : Properties クラスを使う場合

Properties#load(Reader) メソッドを呼び出す際の Reader オブジェクトを、UTF-8 エンコーディング指定した InputStreamReader オブジェクトにすれば OK です。

すなわち、以下のような実装になるでしょう。

その 2 : ResourceBundle クラスを使う場合

こちらは若干複雑になりますが、 ResourceBundle.Control クラスのサブクラスを用意して対処する方法をとります。

同クラスの newBundle() メソッドのオーバーライド実装にて、 Properties のときと同様に UTF-8 エンコーディング指定した InputStreamReader オブジェクトを用意し、 PropertyResourceBundle クラスのコンストラクタ引数にそのオブジェクトを指定します。

そして実際に ResourceBundle クラスのオブジェクトを取得する際に、その ResourceBundle.Control サブクラスのオブジェクトを ResourceBundle.getBundle(String, ResourceBundle.Control) メソッドの二つ目の引数に指定してやることで、UTF-8 エンコーディングなプロパティファイルを取り扱うことができるようになります。

実装は以下のとおり。

まとめ

  • PropertiesResourceBundle もどちらも、文字エンコーディング指定した InputStreamReader オブジェクトを扱うようにすれば、 native2ascii 的な呪縛から逃れることができるよ
  • UTF-8 エンコーディングなどにしておけば、プロダクション環境上でプロパティファイルをどうしても確認しなきゃいけなくなった場合でも、プロパティファイルに記述した日本語コメントを頼りにスムーズな確認ができるようになるよね
  • プロパティエディタプラグインはもうオワコン扱いでいいよね

2014-09-11

「何をいまさら」な感じの機能追加だけど、Java 8 から 標準のクラスライブラリで Base64 エンコーディング できるようになった。これはこれで嬉しいことなので、使い方とそのパフォーマンスをメモっておく。

使い方

まずはデモコードから。

エンコーダは以下のメソッド呼び出しで得られる 3 つの種類がある。

  • Base64.getEncoder()
  • Base64.getUrlEncoder()
  • Base64.getMimeEncoder()

対応するデコーダがそれぞれ用意されている。

  • Base64.getDecoder()
  • Base64.getUrlDecoder()
  • Base64.getMimeDecoder()

Base64.getUrlEncoder() で得られるエンコード文字列は、普通の Base64 エンコーディングではファイルパスや URI での利用時に問題となりうる +/ の文字の代わりに -_ を使ってエンコードしてくれる。「Base64 エンコーディングした文字列をファイル名に利用したい!」みたいなケースだととっても有用だと思う。

パフォーマンス

Java 7 以前は Apache Commons CodecBase64 クラス を使うことが一般的だったぽいので、それとの性能比較をしてみた。

今回は、メソッド呼び出しにおけるオーバーヘッドを見るスイート (上段) と、Base64 の変換処理の性能を見るスイート (下段) の二種類を用意した。

この結果から、

  • 全般的に、Java 8 で導入された Base64 クラスの方が処理性能的に優れている
  • 特に、Java 8 側の変換メソッドを呼び出す際のオーバーヘッドが Commons Codec のそれより明らかに小さいことがわかる
  • 変換処理の性能は、エンコーディングが 6 倍程度、デコーディングが 2 倍ちょっと、Java 8 の Base64 クラスの方が速い

ということがわかるかと思う。

なお、性能測定に用いたプログラムは以下のとおり。

まとめ

  • Base64 だけについて言えば、Commons Codec はその役目を終えた感がある
  • これからは Java 8 の Base64 を積極的に使うのがよさそうだね!