Javaでのパスワードベースの暗号化(openssl互換)

パスワード管理のため、暗号化してサーバーにファイルを置くことでファイルを置くことでパスワードを各端末のどれからでも見れるソフトウェアを作った。(2018/3/28: すみません、 GitHub に上げていたコードを何かの理由で消してしまっていたことに気付きましたが、元コードもないので、リンクを削除しました。)

さて、このソフトを作るにあたって、暗号化後のファイルの形式を何かと合わせておくことであとから扱いやすくしたいと思い、OpenSSL互換にしようと思って調べてみた。
暗号化を行う場合、特に暗号利用モードにCBCを用いる場合は、「鍵」「初期ベクタ」を正しく生成し、それを元に実際の暗号文を平文に戻す。「暗号化方式」「パディングの方式」が合っていれば、この二つを正しくやりとりすれば暗号文をやりとりできるが、暗号化ファイルのフォーマットがソフトにより異なるのでそれに注意する必要がある。また、パスワードを暗号化に用いる場合(PBE)は、鍵導出関数を合わせる必要がある。
OpenSSLの形式を使いたい場合は、基本的にはOpenSSLの暗号文をJava/Perl/Rubyで開く | mixi Engineers’ Blogに掲載されているように、OpenSSLの鍵生成方式に則ってパスワードとソルト(ランダム生成)から鍵と初期ベクタすることになる。手順もだいたいこの通りだが、一部に誤りがあるようで、鍵の長さとハッシュ関数の組み合わせによっては初期ベクタが正しく生成されなかった。Discreet Blog 25.6.2007「OpenSSL の PBE (Password Based Encryption)」のアルゴリズムを参照しながら書くと正しい。

hashed ← 長さ0のバイト列
必要な長さ ← 鍵の長さ + 初期ベクタの長さ(=ブロック長)

while (hashed.length<必要な長さ) {
	if (ループ初回)
		base ← 長さ0のバイト列

	base ← base + (パスワード + salt)

	for (i=0; i<iterationcount; i++) # iterationcountは1で固定
		base ← hash(base)
	
	hashed ← hashed + base
}

key = hashedの前半、鍵の長さの分
iv = hashedのkeyに使わなかった部分の左から必要な長さ

Javaで書くとこんな感じ(SHA-256がハードコーディングされているが、ここの部分を書き換えると他のハッシュ関数も使用可能)(2018/3/28: すみません、 GitHub に上げていたコードを何かの理由で消してしまっていたことに気付きましたが、元コードもないので、リンクを削除しました。)

また、パスワードは当然ユーザーが入力するが、saltに関してはランダムに生成した上でファイルに埋め込む。ファイルは、

Salted__<salt: 8バイト><暗号文>

という形式になっている。これをバイト列として扱い、暗号化/復号化の処理を行う関数は(2018/3/28: すみません、 GitHub に上げていたコードを何かの理由で消してしまっていたことに気付きましたが、元コードもないので、リンクを削除しました。)

ただし、リンク先で指摘されている通り、OpenSSLの暗号化はハッシュ化の繰り返し回数が1で固定されており、ブルートフォースへの耐性がそれほど強くなく、また鍵導出関数そのものもやや古い。そのため、実用的なアプリケーションを作るにあたっては、これをそのまま用いるべきかどうかはよく検討する必要がある。