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で固定されており、ブルートフォースへの耐性がそれほど強くなく、また鍵導出関数そのものもやや古い。そのため、実用的なアプリケーションを作るにあたっては、これをそのまま用いるべきかどうかはよく検討する必要がある。

FreeBSDにPostfixとDovecotをインストールしてメールを送受する

ConoHaのVPSを借りたので、ConoHaのVPSにPostfixとDovecotをインストールして普通にメールできるサーバーを立ち上げた。

Postfixに手元のPCから送信するためには、SASLを利用して認証を行う必要がある(信用できるローカルネットワークからしか送信しない場合は不要)。
SASLは、認証を行うためのフレームワークだそうだ。DovecotのWikiのSASLのページを見ての通り、SASLというのはRFCで決められた仕様で、その実装としてCyrus SASLが標準的だがDovecot SASLもあるということ。

portsからpostfixをインストールする。その時、Cyrus SASLのチェックではなくDovecot 2.xのSASLを使うオプションにする(もちろんCyrus SASLでもできるはず)。dovecotはメール受信に利用するが、Dovecot 2.xのSASLを選択した場合は依存関係で自動的に入るはず。

main.cfを編集する。mydestinationsには、受信したいドメインを指定する。バーチャルドメインを指定する場合は別途指定する。
また、ipv6を使いたい場合は、inet_protocols = ipv4, ipv6とする。
/usr/local/etc/postfix/main.cfに

smtpd_tls_cert_file=/usr/local/etc/postfix/ssl/your-cert-file.crt
smtpd_tls_key_file=/usr/local/etc/postfix/ssl/your-key-file.key
smtpd_use_tls=yes

smtpd_sasl_type=dovecot

smtpd_sasl_path = private/dovecot-auth

と書く。SSLのファイルの場所は置いた場所に合わせる。中間証明書などを使いたい場合は、証明書本体の後に結合する。

/usr/local/etc/postfix/master.cfではsmtpsの部分のコメントアウトを解除し、

smtps     inet  n       -       n       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_reject_unlisted_recipient=no
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o smtpd_sender_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING
#  -o smtpd_client_restrictions=$mua_client_restrictions
#  -o smtpd_helo_restrictions=$mua_helo_restrictions
#  -o smtpd_sender_restrictions=$mua_sender_restrictions

というようにする。これで、SASLで認証済み以外は中継を受け付けないようになる。
なお、smtp_recipient_restrictionsはPostfix 2.10.0から使えなくなった(Postfix 2.10 から中継制限の設定が変わった (smtpd_recipient_restrictions はダメ)! – @yuumi3のお仕事日記)とのことで、smtp_relay_restrictionsと書く。

Dovecot側では、/usr/local/etc/dovecot/dovecot.confに

protocols = imap
listen = *, ""

と書き、/usr/local/etc/dovecot/conf.d/10-ssl.confのssl_certとssl_keyの行をコメントアウトしてDovecotと同じく鍵と証明書の場所を書くことで、IMAPSでの受付が可能になる。非SSLでの接続を無効にするため、/usr/local/etc/dovecot/conf.d/10-master.confの該当箇所を下のようにいじるか、下のように書き足す。
また、Dovecot SASLを先ほどのPostfixの設定に合わせるため、/usr/local/etc/dovecot/dovecot.confに

service auth {
	unix_listener /var/spool/postfix/private/dovecot-auth {
		group = postfix
		mode = 0660
		user = postfix
	}
}
userdb {
	driver = passwd
}
userdb {
	driver = passwd
}

と書き足す。

これでdovecotとpostfixをインストールするとメールが受信できるようになるはずなので、確認する。

その他、PCから送信する場合PCのIPアドレスがメールのヘッダに書かれてしまうので、気になる場合は
Remove sensitive information from email headers with postfix | major.ioなどを見ながらフィルタリングする。

/usr/local/etc/postfix/main.cfに

header_checks=regexp:/usr/local/etc/postfix/maps/header_checks

と書き足した上で、ここで指定したファイルに

/^Received\: from .*by your.host.name \(Postfix\) with ESMTPSA/	IGNORE

と書くと、キューに入ったあと実際に送信されるときにこのようなヘッダが削除される。ESMTPSAの部分は受信時には違う文字になるため、自分がつけたヘッダであっても受信時のものはフィルタされない。