●Dovecotのパスワードジェネレータ

 Dovecotは標準でdoveadmというコマンドを持っており、これを使うとdovecotの認証に使用するパスワードを作成できます。

 ただ、doveadmはCLIベースとなっており、他のプログラムから使う時にはシェルで呼んだりしてあげないといけません。ローカルで動作するものならまだしも、CGIなどでユーザ入力を受け付けるのは結構怖いものです。

 doveadmで作るパスワードはPerl/PHP/Pythonなどの外部プログラムで作成できるのですが、今や不人気言語になってしまったPerlの例は探してもありませんでしたので、その作成方法を紹介します。

●対象とする読者層

●環境

以下の環境で説明していきます。

●用語

説明の便宜上、本コンテンツでは下記のような用語を使用します。
$はプログラムにした時の変数名です。

Table1. それぞれが示す部分の例
これ全体がDovecot passwordまたはstored password。
{SHA512-CRYPT}$6$pW7QIney.1oWHkgL$yaFayAkZaan8jMfeFVinZPabcs71BfxGrONjxsZTzYv3DP2eTh5/0GlkTvhu1Dwln.9R4CWv/0pyG66jAV4Jb0
※色分けされた部分→
スキーム$6$salt$digest

↓これ全体がシャドウパスワード
$6$pW7QIney.1oWHkgL$yaFayAkZaan8jMfeFVinZPabcs71BfxGrONjxsZTzYv3DP2eTh5/0GlkTvhu1Dwln.9R4CWv/0pyG66jAV4Jb0

 最後のパスワードストア格納用文字列ですが、スキームの文字列はここでつけなくてもdovecotの認証時に付いていてれば問題ありません。そのためスキームはパスワードストアに格納せず、パスワードストアにはシャドウパスワードのみを格納し、dovecotの設定上にてmysqlのconcat関数で認証時に動的に付与する方法もあります。

 ですが、このコンテンツでは説明の都合上、パスワードストア格納用文字列、dovecot_passwordのいずれもスキームの文字列をつけることにします。

 また、検証の際の「# doveadm pw -t '$dovecot_password'」コマンドによるパスワードとハッシュの一致の確認作業はこのスキーム文字列が必須です。

●サンプルコード

こちらまたはこちらからどうぞ。

●ファイル名の説明

●利用条件・ご注意

●紹介するPasswordスキーム

 下記のスキームに対応したパスワード生成用コード紹介します。おすすめはBLF-CRYPTで、妥協案がSHA512-CRYPTです。
 MD5とかSHA1はどこ?ですか...。作者には何を仰っているのかよくわかりません。

●SHA512の特徴とデータ構造

特徴を1行でいうと:平文よりはマシ、レインボーテーブル攻撃にもブルートフォース攻撃にも弱い、実装は簡単、今更採用するメリットはない。

 プレインテキストをSHA2ファミリのSHA512でハッシュを取っただけです。今となってはセキュリティの低いスキームです。今時こんなスキーム使う人はいないと思いますが、一応紹介します。

 平文よりはマシですがレインボーテーブル攻撃に弱く、攻撃者にとっては平文とそれほど変わりません。またSHA512は計算速度が速く、ブルートフォースにも弱いハッシュです。

 SHA512は同じパスワードからは同じDigestが出ます。Digestは64Byteです。SHA512はDigest=シャドウパスワードです。

 SHA512のDigestはHexにしない限り文字列はデフォルトでBase64で符号化されており、B64をつけてもつけなくても出力は同じです。SHA512のハッシュ後のデータはただ単なる64バイトのバイナリデータですので、Base64かHEX化しないとスクリーンに表示できないためと思われます。。

# doveadm pw -s SHA512 -p y17
{SHA512}8gvHVYhwaek+BhOO9uo7HuAy1v+Y3/j6NZUXkgUynyOorSZ7GEF5RZ41sUMkALtgW1L3+82QjbHCBbq5dgLPuQ==

# doveadm pw -s SHA512.B64 -p y17
{SHA512.B64}8gvHVYhwaek+BhOO9uo7HuAy1v+Y3/j6NZUXkgUynyOorSZ7GEF5RZ41sUMkALtgW1L3+82QjbHCBbq5dgLPuQ==

doveadm pw -s SHA512.HEX -p y17doveadm pw -s SHA512.HEX -p y17

# doveadm pw -s SHA512.HEX -p y17
{SHA512.HEX}f20bc755887069e93e06138ef6ea3b1ee032d6ff98dff8fa3595179205329f23a8ad267b184179459e35b1432400bb605b52f7fbcd908db1c205bab97602cfb9


●SSHA512の特徴とデータ構造

特徴を簡単でいうと:SHA512よりはマシ、ブルートフォース攻撃には相変わらず弱い、実装は簡単、今更採用するメリットがない。

 レインボーテーブル攻撃に弱いならsaltつければいいじゃない?ということで、SHA512する際に、sha512(plaintext)にするところを、4バイトのsaltをつけて。sha512(plaintext + salt) + saltとして、レインボーテーブル攻撃を強化したのがSSHA512です。

 レインボーテーブル攻撃には強いのですが、salt自体は丸見えですので、ブルートフォースには弱いスキームです。今時こんなスキーム使う人はいないと思いますが、一応紹介します。

 シャドウパスワードは64ByteのDigestに4文字のsaltが末尾にくっつくので、ハッシュ後のバイト数は68Byteのバイナリ文字列になります。

 saltとパスワードが同じであれば、シャドウパスワードは必ず同じ文字列になります。ただ、通常saltはランダム文字列にしてパスワードを作りますので、一般的にはパスワード作るたびに異なったシャドウパスワード(=digest)が出ます。。

 SHA512同様に、Hexにしない限り文字列はデフォルトでBase64で符号化されており、B64をつけてもつけなくても出力は同じです。

# doveadm pw -s SSHA512 -p y17
{SSHA512}sBf9g6Xk5rEGEA8NntQVK5XSnkmOS6wj0KXW8rAgKjhDtENjSjbHZteUdx3+WLm700esPAfawxZ/jTPT7iBrlRtxTYo=

# doveadm pw -s SSHA512 -p y17
{SSHA512}nsFP1cV6EbVnfI3+a/rwDHBRYnj4/z6NGT1WnnDsjapslzfF+M6/ouoP4m+RnVu8ilexfIRqMlM6UNwKJSKkEtWYcT4=

私は自鯖でSSHA512用にパスワード生成/確認用スクリプトを実装したのですが、使う理由が全然無いので、salt漬けしており全く使っておりません。

【参考】
 SSHA512/SSHA512.B64の必ず末尾は=になります。
 SSHA512のBase64化前のシャドウパスワード部分は68Byte=544bitの固定長です。

 Base64は6bit x 4の24bitを1セットでエンコードします。544bitをエンコードするためには544÷24=22.6を切り上げ、24bitが23セット分、24x23=552bit必要です。

 しかしこれだと552-544=8bit、つまり最後のセットが8bit(2bit+6bit)分必要数に足りません。6bitに満たない部分である2bit分は00がPaddingされますが、エンコードネタが無い=何も文字がない部分の6bitは=でPaddingされます。

●SHA512-CRYPT

特徴を1行でいうと:適切なストレッチ回数を設定すれば各種攻撃耐性が高い、実装は面倒臭い、専用のPerlモジュールがないためcrypt関数に依存。

 SSHA512はレインボーテーブルにはある程度強いもののブルートフォースに弱いです。そのブルートフォースへの対策の方法の1つのアプローチが「1回の認証にかかる時間を長くする」です。

 SHA512-CRYPTはそんな思想を元にできているようで、「ハッシュ後の文字列とsalt使って何回もハッシュしまくれば時間かかるんじゃね?」という考え方です。

 このスキームは割とビンテージOSでも動いたりするので、古いOSやライブラリの充実していないOS上でも使える可能性が高く、ストレッチ回数を細かく設定できるため、低性能なビンテージ機材でも使いやすいスキームです。

 ただ、実装は割と面倒くさいのでBLF-CRYPTがまともに運用できるならあまり使う必要のないスキームです。

 round回数はデフォルト5,000で1,000〜999,999,999回です。doveadmはデフォルト5,000回で-rオプションで変更できます。

 saltは16Byteで、doveadmは[a-zA-Z0-9./]しかsaltに使っていないみたいですが、シャドウパスワードをまるっとB64かHEXでエンコードしてしまえば、実はバイナリ文字列16Byteのsaltでも大丈夫です。
 
 ただ、doveadmはsaltは毎回ランダムではあるものの、引数などで指定できませんので、バイナリ文字列のsaltはdoveadmでは生成できません。doveadmで生成できないというだけですのでシャドウパスワード全体がBase64化済み(またはHEX文字列)であれば検証は問題なく可能です。

 シャドウパスワードは$6$で始まり、ストレッチ(round)回数が5,000回であれば「$6$<16Byteのsalt>$digest」、ストレッチ(round)回数が5,000回以外なら「$6$round=<ストレッチ回数回数>$<16Byteのsalt>$digest」となります。

# doveadm pw -s SHA512-CRYPT -p y17
{SHA512-CRYPT}$6$pW7QIney.1oWHkgL$yaFayAkZaan8jMfeFVinZPabcs71BfxGrONjxsZTzYv3DP2eTh5/0GlkTvhu1Dwln.9R4CWv/0pyG66jAV4Jb0
スキーム$6$salt$Digest
※ストレッチ回数のデフォルトは5,000でその場合は$6$の後はsaltです。ストレッチ回数を明示的に5,000と指定してもこの形式で出ます。

# doveadm pw -s SHA512-CRYPT -p y17 -r 1700
{SHA512-CRYPT}$6$rounds=1700$q.quNdFGMlPVhpzE$rNPbbchYfIBDicm1.VBjQ0MiIdghtdPize659tuetvuNEutt1P./71AeGxEwjC4v.8pjjQtMiJ7TWCJw3robO/
スキーム$6$ストレッチ回数$salt$Digest
※ストレッチ回数を明示的に指定すると$6$の後はストレッチ回数ですとなり、そのあとにsaltが続きます。

●BLF-CRYPT (a.k.a BCRYPT/bcrypt/Blowfish Crypt)

特徴を簡単にいうと:適切にストレッチすれば各種攻撃体制が高い、ストレッチ処理が重い、実装はSHA512-CRYPTより楽、専用のPerlモジュールが用意されている。

 1回の認証にかかる時間を長くしたいんなら、ストレッチ回数をバカの一つ覚えみたいに増やすよりも「そもそも1回のハッシュ計算をクソ重くしてから、何回もハッシュしまくれば効果的じゃね?」という考え方に基づくのがBLF-CRYPTです。

 まともに動く環境を用意できるなら、Dovecotのスキームの連中の中では割と実装しやすく、セキュリティの高いオススメのスキームです。

 バカの一つ覚えとかSHA512-CRYPTをdisってるように見えますが、SHA512-CRYPTの2倍以上長い回数をストッチングできます。正確にはBLF-CRYPTはSHA2ファミリと異なり、ハッシュ関数じゃなくてどうも暗号化関数らしいのですが、知っててもここでは特に意味がないので無視します。

 このスキームは割と新しめで計算が重く、また、ストレッチ回数が2の累乗でしか設定できないため、ビンテージOSやビンテージ機材には辛いかもしれません。また、Perl内蔵のcrypt関数でも作れるのですがこのcryptがbcryptを利用できない状態でPerlパッケージがインストールできない環境では利用が難しいスキームでもあります。
※cryptがbcryptを利用できるかはライブラリ依存です。

 ストレッチ回数はデフォルトでdoveadmでは5(2^5=32回)で、設定可能範囲は4(16回)〜31(2,147,483,648回)です。SHA512-CRYPT同様に-rオプションで任意の2の階乗を指定できます。
 当然ですがストレッチ回数の指定数を1増やすと処理時間が2倍に増えます。

 saltは16Byteでdoveadmでは生成のたびにランダムです。出力結果はsalt、digestともに、それぞれBase64(的な何か)でエンコードするため、バイナリ文字列を使用しても問題ありません。ただdoveadmではsaltには[a-zA-Z0-9./]しか使っていないみたいです。

 シャドウパスワードは$2a$で始まり、「$2a$<ストレッチ回数(べき乗表示)2桁>$<22文字がsalt><残った部分がDigest>」となります。SHA512-CRYPTと異なり、ストレッチ回数は必ずシャドウパスワードに出るためストレッチ回数による構造差分はありません。

 BLF-CRYPTのストレッチ回数の実用的な値は、運用機材にもよりますが2019年現在では(2^)09〜12回程度だと思います。これより大きくするとメールチェックが体感的に遅くなったと感じるかもしれません。
 私は機材が2013年製のローエンド機と古くてしょぼいため(2^)11にしています

# doveadm pw -s BLF-CRYPT -p y17        
{BLF-CRYPT}$2y$05$uOlh4v/J3FBnUwXnXSmWZuWUO1pY22XdZLlRGx1y3oYOIaC3JX8c2

スキーム$2y$ストレッチ回数$saltDigest
※ストレッチ回数のデフォルトは05回で、saltとシャドウパスワードの間には区切り文字がありません。

# time doveadm pw -s BLF-CRYPT -p y17 -r 12

{BLF-CRYPT}$2y$12$S0B3jtfoMLEJyaiBe1hwieEnw77XnLjUl3WqycWcO6gRWF1jP6.ii

real    0m0.778s
user    0m0.754s
sys     0m0.024s


※ストレッチ回数を12回に変えても構造は同じです。
※timeは後ろに続くコマンドの実行時間を図るコマンドです。12だとかなり遅いことがわかります。

●パスワードの検証について

●コンソール上での確認

 コンソール上からはdoveadm pw -t '$dovecot_password'を入力して、正しいパスワードを入れればverifiedと出ます。スキームの文字列は必須です。また$dovecot_passwordをシングルクォートで囲わないと弾かれます。

# doveadm pw -t '{BLF-CRYPT}$2y$12$S0B3jtfoMLEJyaiBe1hwieEnw77XnLjUl3WqycWcO6gRWF1jP6.ii'
Enter password to verify: (※y17と入力)
{BLF-CRYPT}$2y$12$S0B3jtfoMLEJyaiBe1hwieEnw77XnLjUl3WqycWcO6gRWF1jP6.ii (verified)
※パスワードが合ってればverifiedと出ます。
※dovecot_passwordまたはstored_passwordはシングルクォートで括ってください(括らないとmismatchになります)。


# doveadm pw -p y17 -t '{BLF-CRYPT}$2y$12$S0B3jtfoMLEJyaiBe1hwieEnw77XnLjUl3WqycWcO6gRWF1jP6.ii'
{BLF-CRYPT}$2y$12$S0B3jtfoMLEJyaiBe1hwieEnw77XnLjUl3WqycWcO6gRWF1jP6.ii (verified)

※(履歴とか気にしないのであれば)パスワードは引数に含めても問題なく動作します。

# doveadm pw -t '{BLF-CRYPT}$2y$12$S0B3jtfoMLEJyaiBe1hwieEnw77XnLjUl3WqycWcO6gRWF1jP6.ii'
Enter password to verify:
(※y17以外を入力)
Fatal: reverse password verification check failed: Password mismatch
※(履歴とか気にしないのであれば)パスワードは引数に含めても問題なく動作します。

●プログラムorスクリプトからの確認

 どのスキームもハッシュ関数で作成していますので、(a).saltや(b).ストレッチ回数はシャドウパスワードから抽出できますが、Passwordはシャドウパスワードからの復号はできません。しかし、スキームとハッシュ回数、ストレッチ回数、Passwordが同じであればシャドウパスワードは必ず同じものが生成されます。

 そこでプログラム上は検証したいシャドウパスワードから(a).saltと(b).ストレッチ回数を抽出し、それらとユーザが入力したPasswordでもう一度同じスキームを使用してシャドウパスワードを生成します。

 この「もう一度作成したシャドウパスワード」と、「検証したいシャドウパスワード」が一致すれば、入力したパスワードが間違いない、または問題なくコードが書けていることがわかります。

●次のセクションでやること。

●参考文献