OpenLDAP 上のパスワード情報
OpenLDAP で userPassword アトリビュートにパスワード情報を保存する場合、推奨はソルト付き SHA(SSHA)とされていると思います。実際、CentOS Ver 6.3 上の OpenLDAP に付いてくる slappasswd コマンドは、オプションを指定しないデフォルトは、SSHA の形式を出力します。
しかし、高速な GPU が廉価に販売され、その GPU を駆使して並列計算をすると、ブルートフォースによる解析が、実用的な時間で成功するようになりました。
去年の 12 月に飛び込んできた、「NTLM ハッシュが 8 文字までのパスワードなら5時間版」*1は衝撃的で、私もそのことを日記に書きました*2。NTLM ハッシュは Unicode で表現されたパスワード文字列の MD4 ハッシュ値で、SHA-1 よりは出力されるハッシュ値の bit 長も短く、1つのハッシュ値を求める時間はおそらく、SHA-1 より MD4 の方が短いとは思いますが、それでも、何百倍、何千倍も違う、という事ではないでしょう。
http://www.cryptopp.com/benchmarks.html
上記サイトに、様々はハッシュ値計算や暗号化処理のベンチマーク結果がありますが、MD5 と SHA-1 の比較で、およそ2倍(MD5 の方が高速)という結果になっています。MD4 がどのくらいになるのか分かりませんが、大きく見積もっても 10 倍にはならないと思います。
仮に、SHA-1 の計算が MD4 より 10 倍の時間がかかる、としても、先の「5時間半」は「55 時間」にしかなりません。2日余りで必ず解ける、という事になってしまいます。「塩加減は重要? - JULYの日記」でも書きましたが、ソルトの有無は Rainbow Table 対策にはなっても、ブルートフォース対策にはなりません。
また、実際に SHA の値を単一の GPU で 33 日間で見つけられたという2年前の記事*3もあります*4。
ブルートフォース対策にはストレッチング、なのですが、自分で作る Web アプリケーションならともかく、LDAP のパスワードフィールドに対して、独自のストレッチングを行う OpenLDAP 用の overlay や plugin を作る、というのも気が引けます。
{CRYPT} は crypt(3)
OpenLDAP のパスワード・スキームに、{CRYPT} というのがあります。これは、古くから UNIX 系 OS の /etc/passwd や /etc/shadow のパスワード・フィールドに記述するされる形式で、古典的には ASCII のパスワード文字列を DES の鍵として、全ビット 0 のデータを出発点に、繰り返し暗号化した結果になります*5。
しかし、今時、この DES を使った形式の値を保存している物を見ません。商用 UNIX でも、Linux でも、10 年以上前から、MD5 を使った形式などに変わってきています。
これは、このパスワード・フィールドに記述する内容を計算する crypt という C 言語用の関数を拡張する形で実現されました。具体的には crypt の引数に渡すソルトで、先頭 3 文字に特別な意味を持たせ、それによって計算処理を変えるようになっています。例えば、「$1$」で始まったソルトが渡されたら、MD5 を使った計算処理を行う、といった感じです。今では MD5 の代わりに SHA-256、SHA-512 を使った形式もあり、最近の Linux ディストリビューションでは SHA-512 を使う「$6$」になっています。
ところが、設定したパスワードとソルトから MD5 の値を求めようとしても、実際に /etc/shadow に書き込まれた物とは、似ても似つかない結果になります。で、実際に crypt の処理を調べたことがあります。
実は MD5 を使った crypt というのは、1,000 回のストレッチングを行った処理でした。ちなみに SHA-256 や SHA-512 の場合も、同様のストレッチングをしています。
で、改めて OpenLDAP のドキュメントを読むと、slapd.conf の設定に password-crypt-salt-format*6 という項目がありました。
OpenLDAP Faq-O-Matic: How do I specify the crypt(3) salt format to use?
password-crypt-salt-format に指定するのは printf の書式指定子の形式で記述します。上記ページでは、「password-crypt-salt-format "$1$%.8s"」という例が出ていますが、こうすれば、「$1$」で始まり、その後ろに 8 桁のソルト文字列が続くことになります。
つまり、
- password-hash に {CRYPT} を指定し
- password-crypt-salt-format に "$1$%.8s" と設定する
と、MD5 を 1,000 回繰り返すストレッチング処理をした結果が、userPassword のアトリビュートに書き込まれる事になります。MD5 の計算時間が SHA-1 の半分だっとしても、ブルートフォースで解くには、単純計算で {SSHA} の 500 倍の時間がかかる事になります。仮に、MD5 と MD4 の計算時間が同程度、と控えめに見積もっても、先の 5.5 時間の装置で 2,750 時間、4 ヶ月近い時間がかかる事になります。もし SHA-512 の crypt を使うようにすれば、先に紹介したベンチマーク結果を見ると、さらに 2.5 倍程度の時間を要するので 10 ヶ月の期間が必要、という事になります。
制限事項
slapd を動かす OS 上の crypt(3) の機能
OpenLDAP の {CRYPT} は、その OS 上の crypt 関数の実装に依存します。なので、複数の slapd で同期をしているような場合、それぞれの OS 上の crypt の実装で揃っている必要があります*7。MD5 を使った形式であれば、よほどの事が無い限り、対応していない事は考えられませんが、SHA-256、SHA-512 を使うのであれば要注意です。
ちなみに、RHEL / CentOS の場合、pam_unix で SHA-256、SHA-512 対応となったのは Ver. 5.2 です*8ので、少なくともこのバージョン以降であれば大丈夫なはずです。
LDAP を使うプログラムの実装
ユーザ認証を行うプログラムが、そのバックエンドとして LDAP を使う場合、大きく分けて 2 つの実装方法があります。
前者の場合は、slapd がどんなパスワード・スキームを使ってるかは全く関係ありませんが、後者の場合はプログラム側が {CRYPT} で DES 以外の形式が扱えるか、という問題になります。
例えば、Dovecot でユーザ認証に LDAP を使う場合、
- Passdb LDAP with authentication binds (http://wiki2.dovecot.org/AuthDatabase/LDAP/AuthBinds)
- Passdb LDAP with password lookups (http://wiki2.dovecot.org/AuthDatabase/LDAP/PasswordLookups)
の二通りの方法があり、auth_bind を yes にすれば前者、no にすれば後者になります。
もし、POP3 で APOP、IMAP で DIGEST-MD5 や CRAM-MD5 を使いたければ後者しか選択の余地がありませんが、そうでなければ、前者の方式が使えます。
前者の方式が使えるのであれば、パスワード・スキームの問題は生じませんが、後者の場合は、Dovecot がうまく扱えるかどうか、確認する必要があります*9。
RFC 3062 非対応
パスワードを変更する際、RFC 3062 の「LDAP Password Modify Extended Operation」に対応しているプログラムからであれば、slapd が userPassword を設定したパスワード・スキームで保存してくれますが、これに非対応の場合、userPassword に保存する内容を直接書き込む物があります。コマンドラインの ldappasswd は RFC 3062 対応なので問題ないのですが、例えば Apache Directory Studio*10 は対応していません*11。
これは、password-crypt-salt-format を指定してるか否かにかかわらず、クライアント側で勝手にパスワード・スキームを変更できる事になるのでうれしくないのですが、もし、これらのツールを使うのであれば、slappasswd で -c オプションに password-crypt-salt-format に指定したのと同じ物を指定し、-h オプションに {CRYPT} を指定すれば、userPassword アトリビュートに保存される値が計算できます。
$ slappasswd -h {CRYPT} -c '$1$%.8s' New password: Re-enter new password: {CRYPT}$1$t6PXjyr8$R9LHggt9sf2ietomynfRo0
ただ、slappasswd が使えるなら、ldappasswd で変えた方が早いですが...。
パフォーマンス
そもそも、1度の SHA-1 の計算処理で綱渡り状態にあるようなサーバの場合、そのままパスワード・スキームを変更すれば破綻します。そもそも、認証処理でそこまで重い状態な時点で、システム増強などを検討すべきですが...。
まとめ
ということで、もし、システム全体で問題が無いのであれば、OpenLDAP でのパスワードスキームは、
password-crypt-salt-format を指定した上で、{CRYPT} を使う。
が、今のところ正解、という事になります。ブルートフォース耐性は、"$1$%.8s" で {SSHA} のおよそ 500 倍に上がります。パスワード長を1文字増やしたより高い効果が得られる事になります。
ただし、短いパスワードや単語を使ったようなパスワードなどを設定したら、ほとんど効果はありません。十分に長く、複雑なパスワードで、使い回しをしない、という鉄則は、何ら変わることはありません。
*1:8文字の全パスワードを5時間半で解析するコンピュータクラスタが登場 - CNET Japan
*2:こんなに早く、この日が来るとは... - JULYの日記
*3:エフセキュアブログ : 「SHA-1+salt」はパスワードに十分だと思いますか?
*5:crypt (C) - Wikipedia Traditional DES-based scheme 参照
*6:今時の cn=config からオンラインで設定変更をする場合だと olcPasswordCryptSaltFormat に該当します。
*7:試してはいませんが、同期自体は正常に行われるかもしれません。ただ、userPassword アトリビュート自体が正しく同期できても、バインド時に正しく認証できない、という事態になる事が想像されます。
*8:Bug 435804 – RHEL5.2 Release Notes: SHA-256 and SHA-512 support in password hashing
*9:未確認。Dovecot が {CRYPT} だった時にそのまま crypt 関数を呼び出していれば、大丈夫なはず。
*10:Welcome to Apache Directory Studio — Apache Directory
*11:Ver. 1.5 系の場合。Ver. 2 系は不明ですが、試した感じでは対応している気配が無かったです。サーバ側の設定の関係とか、あるのかなぁ。