Hatena::ブログ(Diary)

JULYの日記

2013-02-23 userPassword はあえて {CRYPT}

[]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 の方が短いとは思いますが、それでも、何百倍、何千倍も違う、という事ではないでしょう。

Speed Comparison of Popular Crypto Algorithms

上記サイトに、様々はハッシュ値計算暗号化処理のベンチマーク結果がありますが、MD5SHA-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} というのがあります。これは、古くから UNIXOS の /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 の処理を調べたことがあります

調べてみると、結構複雑 - JULYの日記

実は 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 桁のソルト文字列が続くことになります

つまり、

と、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 の実装で揃っている必要があります*7MD5 を使った形式であれば、よほどの事が無い限り、対応していない事は考えられませんが、SHA-256、SHA-512 を使うのであれば要注意です。

ちなみに、RHEL / CentOS場合、pam_unix で SHA-256、SHA-512 対応となったのは Ver. 5.2 です*8ので、少なくともこのバージョン以降であれば大丈夫なはずです。

LDAP を使うプログラムの実装

ユーザ認証を行うプログラムが、そのバックエンドとして LDAP を使う場合、大きく分けて 2 つの実装方法があります

前者の場合は、slapd がどんなパスワードスキームを使ってるかは全く関係ありませんが、後者場合プログラム側が {CRYPT} で DES 以外の形式が扱えるか、という問題になります

例えば、Dovecotユーザ認証LDAP を使う場合

の二通りの方法があり、auth_bind を yes にすれば前者、no にすれば後者になります

もし、POP3APOPIMAP で 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文字増やしたより高い効果が得られる事になります

ただし、短いパスワード単語を使ったようなパスワードなどを設定したら、ほとんど効果はありません。十分に長く、複雑なパスワードで、使い回しをしない、という鉄則は、何ら変わることはありません。

*18文字の全パスワードを5時間半で解析するコンピュータクラスタが登場 - CNET Japan

*2こんなに早く、この日が来るとは... - JULYの日記

*3エフセキュアブログ : 「SHA-1+salt」はパスワードに十分だと思いますか?

*4指数表現は実感しにくい - JULYの日記

*5crypt (C) - Wikipedia Traditional DES-based scheme 参照

*6:今時の cn=config からオンラインで設定変更をする場合だと olcPasswordCryptSaltFormat に該当します

*7:試してはいませんが、同期自体は正常に行われるかもしれません。ただ、userPassword アトリビュート自体が正しく同期できても、バインド時に正しく認証できない、という事態になる事が想像されます

*8Bug 435804 – RHEL5.2 Release Notes: SHA-256 and SHA-512 support in password hashing

*9:未確認。Dovecot が {CRYPT} だった時にそのまま crypt 関数を呼び出していれば、大丈夫なはず。

*10Welcome to Apache Directory Studio — Apache Directory

*11:Ver. 1.5 系の場合。Ver. 2 系は不明ですが、試した感じでは対応している気配が無かったです。サーバ側の設定の関係とか、あるのかなぁ。

トラックバック - http://d.hatena.ne.jp/JULY/20130223