パスワードのフォーマットを選択可能に(LDAP互換に)†
- 元タイトル: ユーザー定義のパスワードをmd5hashに
- ページ: BugTrack
- 投稿者: merlin
- 優先順位: 普通
- 状態: 完了
- カテゴリー: 本体新機能
- 投稿日: 2004-09-30 (木) 17:31:54
- バージョン: 1.4.6
(PukiWiki>1.4.6)
管理者パスワードとユーザーパスワードの保存形式を、「RFC2307および既存のLDAP実装」に準拠させました。その結果、管理者が保存方式を自由に選択(変更)できる様になりました。具体的にはMD5以外に平文、SHA-1、Unix crypt()、OpenLDAPが利用できるパスワードフォーマット全てを利用できる様になりました。
従来のMD5パスワード保存形式を使う場合は、先頭に
{x-php-md5}
を追加する必要があります。
使用できるフォーマット†
| 保存フォーマット / scheme prefix | アルゴリズム | ソルト(seed) |
1 | Cleartext (平文) | なし / {CLEARTEXT} | 無変換 | なし |
2 | LDAP SSHA (sha-1 with a seed) | {SSHA} | SHA-1 | あり |
3 | LDAP SHA | {SHA} | SHA-1 | なし |
4 | LDAP SMD5 (md5 with a seed) | {SMD5} | MD5 | あり |
5 | LDAP MD5 | {MD5} | MD5 | なし |
6 | LDAP CRYPT | {CRYPT} | システム依存 | あり |
7 | PHP sha1()関数の出力 | {x-php-sha1} | SHA-1 | なし |
8 | PHP md5()関数の出力 | {x-php-md5} | MD5 | なし |
9 | PHP crypt()関数の出力 | {x-php-crypt} | システム依存 | あり |
使用例†
// SELECT ONE
//$adminpass = 'pass'; // Cleartext
//$adminpass = '{CLEARTEXT}pass'; // Cleartext
//$adminpass = '{SSHA}B78f8i/RpNC+CyFdKLH2odaK8hlPNjlOOUUyMA=='; // SSHA 'pass'
//$adminpass = '{SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ='; // SHA 'pass'
//$adminpass = '{SMD5}o7lTdtHFJDqxFOVX09C8QnlmYmZnd2Qx'; // SMD5 'pass'
//$adminpass = '{MD5}Gh3JHJBzJcaScd3wyUS8cg=='; // MD5 'pass'
//$adminpass = '{CRYPT}$1$nxrVut5a$c9LdXN1rKQC1HQOwBY4O//'; // CRYPT 'pass'
//$adminpass = '{x-php-sha1}9d4e1e23bd5b727046a9e3b4b7db57bd8d6ee684'; // sha1('pass')
//$adminpass = '{x-php-md5}1a1dc91c907325c69271ddf0c944bc72'; // md5('pass')
//$adminpass = '{x-php-crypt}$1$nxrVut5a$c9LdXN1rKQC1HQOwBY4O//'; // crypt('pass')
cvs-raw:UPDATING.txt.diff?r1=1.23&r2=1.24&diff_format=u に注意点を書いた通りですが、既存の管理者パスワード(MD5ハッシュ)を今後も使用する場合、先頭に '{x-php-md5}' というフォーマット宣言を付ける必要があります。
平文を除く八種類の保存フォーマットについては、強化されたmd5プラグインで生成・検証することができます。
参考文献など†
- RFC 2307: An Approach for Using LDAP as a Network Information Service
メッセージ†
編集や閲覧認証に使われるユーザー定義のパスワードが直打ちなので md5hashにした方がいいのではないでしょうか?
PukiWiki/1.4/ちょっと便利に/任意のページごとの閲覧・編集制限 参照
auth.php
// ユーザリストに含まれるいずれかのユーザと認証されればOK
if (!isset($_SERVER['PHP_AUTH_USER'])
or !in_array($_SERVER['PHP_AUTH_USER'],$user_list)
or !array_key_exists($_SERVER['PHP_AUTH_USER'],$auth_users)
- or $auth_users[$_SERVER['PHP_AUTH_USER']] != $_SERVER['PHP_AUTH_PW'])
+ or $auth_users[$_SERVER['PHP_AUTH_USER']] != md5($_SERVER['PHP_AUTH_PW']))
{
pukiwiki.ini.php
/////////////////////////////////////////////////
// ユーザ定義
$auth_users = array(
-'foo' => 'foo_passwd',
-'bar' => 'bar_passwd',
-'hoge' => 'hoge_passwd',
+// ユーザ名 => パスワード(md5 hash)
+// pukiwiki.php?md5=pass のようにURLに入力し
+// MD5ハッシュにしてから記入してください。
+// 面倒ならば以下のようにどうぞ
+// ユーザ名 => md5(パスワード)
+'foo' => 'f122914144e12fa7d7b7b14cc223f671',
+'bar' => 'f53ae779077e987718cc285b14dfbe86',
+'hoge' => md5("hoge_passwd"),
);
- md5("パスワード")と計算するのではなく、md5("パスワード"."ユーザ名")としないと同じパスワードは同じmd5出力列になります。たとえばfooさんが"mypassword"で、barさんが"mypassword"だったとき、fooさんのパスワードがわかれば自動的にbarさんのパスワードがわかってしまいます。このような混ぜ物(Salt)があるとより安全です -- すずきひろのぶ
- コメントありがとうございます :) 検証する側で同じパターンで文字連結してからmd5を取れば問題ないですね。 -- henoheno
- ロジックの根本対応せずとも、md5 対応程度はコミットしておいても良いのでは? -- upk
- お疲れ様です。とり急ぎ明日 1.4.5_1 をリリースする予定なので、それ以後になりますです -- henoheno
- もはや1.3と1.4の関係は、1.4.5では無いということですかね。では、この状況をどう料理しておきましょうかね? -- upk
- こんにちは。「1.3と1.4の関係」という部分をもう少し説明してください (^^; それとこのトピックを取り上げるにあたり、何か背景となる話題をすっ飛ばしていませんか? -- henoheno
- (これ以後のリリースサイクルに関する話題はBugTrack2/12へ移動しました)
- 私が聞きたい背景情報というのは(移動しましたが)そういう話ではありません。「PukiWiki Plus! とかで一部この機能に依存しているところがある*1からこれを先に片付ければ楽になるのか、なるほど~」とか「すずきひろのぶさんが言われているSaltのことを考慮するとmd5($_SERVER['PHP_AUTH_PW'])という部分は md5($salt . $_SERVER['PHP_AUTH_PW']) なんて風になっていないと駄目なので、そこまでケアしていない様ではこのBugTrackが「CVS待ち」の状態だとは思えない」とかそういう反応をしたいので、率直にこのBugTrackをピックアップされた背景を語っていただきたかったという事です (^^; -- henoheno
- やっと素晴らしい言い訳がみつかったわけですね。 --
- 先頭のすずきひろのぶさんのコメントと、upkさんのコメントをきっかけに動き出している下の話に目を通されていますか? 「言い訳」というのは上に挙げた二つの話題のどちらで、素晴らしいのはどちらですか? 短く鋭く愛を込めたツッコミをお願いします。 -- henoheno
パスワードのマルチフォーマット対応†
ちょっと確認: LDAP, LDIF(LDAP Data Interchange Format), slappasswd†
- Manpage of LDIF: ldif - LDAP データ交換フォーマット (稲地 稔 訳)
- Manpage of SLAPPASSWD: slappasswd - OpenLDAP パスワードユーティリティ (稲地 稔 訳)
- PHP: LDAP 関数 - Manual (※これはPHPからLDAPを利用する場合の説明)
- OpenLDAP Faq-O-Matic: What are {CRYPT} passwords and how do I generate them?
- OpenLDAP Faq-O-Matic: What are RFC 2307 hashed user passwords?
- OpenLDAP Faq-O-Matic: What are {MD5} and {SMD5} passwords and how do I generate them?
- OpenLDAP Faq-O-Matic: What are {SHA} and {SSHA} passwords and how do I generate them?
- Sun Software Forums: How is the SSHA salt calculated?
ちょっと確認: cryptなどのハッシュ関数†
- Hotwired: データ暗号化チュートリアル -- レッスン2
- ldiffライクに「+'bar' => '{md5}f53ae779077e987718cc285b14dfbe86',」とかはだめすかね? 殆どの場合問題ないと思うのですが -- ELF
- すいませんldiffライクというのがわかりませんでしたが、すずきひろのぶさんが言われているのは pukiwiki.ini.php の中身が(例えば脆弱性のある別のCGIを経由したりして)外部に漏洩した時の事を言われているので、md5ハッシュの部分がどこからどこまでかを特定できるようでは意味がないのです。md5()関数に与える文字列を、md5()に与える前に加工することで、MD5ハッシュをスクランブルするのがキモなのです。当然ながらSaltの値の一部は個々のPukiWiki管理者が任意に決定できる(= ユーザー名とパスワードの組み合わせがたとえ同じ文字列であっても、PukiWikiごとにハッシュが異なる状態にできる)ものが好ましいです。Googleに問い合わせればバレるようなハッシュを作らないようにするという点では、PukiWiki独自のsaltがsaltの一部に加えられているとなお好ましいです -- henoheno
$string_example = 'pkwk ' . // Hope PukiWiki-original hash
$admin_defined_salt . ' ' . // Site-admin original pass-phrase
$username . ' ' . // Username
$password; // Something password
$hash = md5($string_example);
- ただ、あんまり複雑にしすぎると管理者さんによるハッシュ生成の手間がかかるというトレードオフがあります。 -- henoheno
- 上記の例であれば、管理者は "pkwk admin_pass username password" といった一本のパスフレーズをMD5にかけることでハッシュを取り出すことになります。 -- henoheno
- このように、PukiWikiユニークかつ、個別のPukiWikiユニークかつ、ユーザーネームごとに、パスワードが違うたびに異なる文字列を用意することで、生成されるハッシュがユニークな値となることがより期待できるようになります。 -- henoheno
- 少々考えましたが、PukiWiki管理者がPukiWikiごとのSaltをきちんと考えてくれる状況であれば 'pkwk' という固定文字列は不要で、インストール直後の状況であれば 'pkwk' という文字列は推測できるので不要ですね。(結論: あっても無くても同じ => 不要) -- henoheno
- $adminpass と絡めるとか (^^; -- teanan
- そうすると・・・Unixのrootユーザーのパスワードに相当する $adminpass を変更した途端に、他のユーザーがログインできなくなるような依存関係が生じてしまうでしょう ;) -- henoheno
- ぬお、確かに・・・orz -- teanan
- UNIXのshadowなどは「$1$」+ハッシュ生成時に作成した5文字くらいのランダムなsalt+md5パスワードになります.saltが推測できてしまいますが,ある程度の強度はあると思います. -- ELF
- ldiff likeといったのはパスワード欄を「{暗号化方式}ハッシュ」にしませんかということです.単にmd5('passwd')なり何らかの結果のみにすると後々の拡張がしづらいということで,よくあるぱたーんです. -- ELF
- Linux Shadow Password HOWTO: shadow パスワードを使うべき理由
- http://www.linux.or.jp/JF/JFdocs/Shadow-Password-HOWTO-2.html
- "ユーザがあるパスワードを決めた場合、このパスワードはランダムに決められた salt と呼ばれる値を用いてエンコードされます。こうすることで一つの文字列がエンコードされた結果として取りうる結果は 4096 通りになります。salt の値はエンコードされたパスワードと一緒に記録されます。"
- "クラッカーはこのような事情をよく知っているので 4096 個全ての salt を用いて辞書の単語とよく使われそうなパスワードをあらかじめエンコードしておきます。そして、/etc/passwd に書かれているエンコードしたパスワードをこの結果と比較します。ここで一致するものが見つかれば、クラッカーは他人のパスワードを破ったことになるわけです。これは「辞書攻撃」と呼ばれるもので、正規の認証を受けずにシステムにアクセスするための常套手段です。"
- ELFさんコメントありがとうございます。ひとつは一方向ハッシュが生成される組み合わせを拡大しようというsaltですね。もう一つのお話は例えばMD5からSHA-1*2に切り替えるられるようにしようということで、まず設定文字列の先頭を見て方式を調べ、その方式でハッシュを生成し、方式と連結した文字列と比較するというイメージでしょうか。 -- henoheno
- 前者のsaltについては、PukiWikiの場合ハッシュの生成および設定が基本的に人力であるため、「ハッシュ生成時に作成した5文字くらいのランダムなsaltを取り出す」ような方式を強いるのが難しい(いちいち頭を使わねばならない)と思っています。md5を計算するプラグイン自体は既存で、これをカスタマイズすれば、やや難しいハッシュ生成を自動化できないこともありませんが、md5プラグイン自体がそもそもネットワークにデータを平文で流してしまう矛盾した特性を抱えているため、利用手段としてうまくないと思います。 -- henoheno
- 前者はsaltな話がでてたので*3,UNIXではこうなってますよという紹介.次はまぁそんなところです > ldifライクでの切り替え -- ELF
- メモ: PukiWiki毎のsaltを管理者が別個に設定したとして、両方の設定ファイルが漏洩したとする。このとき、それぞれのPukiWiki用saltも漏洩しているはずなので、それぞれに対する辞書攻撃は依然として可能。また、一方のあるユーザーで辞書攻撃が成功したとき、他方のPukiWikiに同じユーザーが存在した場合、そのユーザーが全く同じパスワードを使っていると仮定できるため、別のPukiWikiのsaltを使ったテストを最初に行うことができる。 = PukiWiki毎のsaltはあんまり意味がないか、労力と見合わないかも -- henoheno
switch ($method) {
case 'md5': $hash = $method . ':' . md5($username . $password); break;
case 'sha1': $hash = $method . ':' . sha1($username . $password); break;
default: die_message('Unknown method'); break;
}
- これまでのご意見を整理するとこんな感じですか :) -- henoheno
メモ: メモリ中のハッシュ文字列自体をそもそも改変する攻撃のことも考えてみては -> 自分。 (今のこの話題とは関係ない) -- henoheno
- 1.4.5_1も出せたし、ELFさんからの確認も取れたので、この方向でテスト実装ができそうですね -- henoheno
- あーこんな感じ.面倒じゃなかったら「{method}」が長いものに巻かれろ的でステキです. -- ELF
- 長いモノに巻かれろと仰るなら、巻くべき長いモノについて明確にして大文字小文字まで一致させるようにしたいのですが、ldiffについてはどこを見ると良いでしょうか。とりあえずopenldapだとMD5の字が大文字だったり、MD5のハッシュの表現形式がPHPのmd5()関数の出力と異なっていたりして、キッチリ巻けそうに見えないのですが (^^; -- henoheno
- ちゃんと調べずに書いていました.すみません.確かに生成されるハッシュ値が違いますね.一応ここに定義されているようですが,生成方法にたどり着くほど読みきれませんでした*4.少なくともPHPのmd5()と値が違うということで混乱を避けるためにhenohenoさん提示の方式でいいと思います. -- ELF
- ldiff → ldif(LDAP Data Interchange Format)ですかね。 --
- slappasswdで試してみました。
# slappasswd -h '{MD5}' -s piyo
{MD5}B+GuIasPRiqZzXK0Pi6AVg==
ぱっと見BASE64エンコードのようなので、PHPでmd5(バイナリ)をbase64エンコードしてみたら
# php
<?php echo '{MD5}' . base64_encode(md5('piyo', TRUE)) . "\n"; ?>
{MD5}B+GuIasPRiqZzXK0Pi6AVg==
同じ値が出せましたよ。 --
- sha1実装(PHP4.3.0以前用)はここにコードがありますね。
- saltつき (SMD5, SSHA, ...)はここのコードが参考になるかと。
- ということで。teanan:自作プラグイン/crypt.inc.php -- teanan
- 皆さんツッコミありがとうございます。やっと解りました。筋が悪くてすいません (^^;*5 PukiWikiのパスワードシステムのフォーマットについて、LDAP互換のフォーマットにも対応させておくと、少々興味深い事になりそうですね。その一方で、今のPukiWikiユーザーになじみのあるPHP md5()関数の表現形式は今後も維持する必要があります。またPHP sha1()関数の表現形式が扱えても良いでしょう。 -- henoheno
- ということで、PHP md5() か PHP sha1() か 平文か、というレベルではなく、OpenLDAPとの互換性を含めたマルチフォーマット対応という路線に視野を広げて検討を進めたいと思います。 -- henoheno
- といってもRFC2307の該当箇所をさらっと見た限り {crypt}(Unixの標準), {md5}, {sha}(アルゴリズムとしてはSHA-1), slappasswd のマニュアルにある {smd5}, {ssha}, そしてPukiWikiが従来利用しているPHP md5()関数の表現形式として {x-php-md5}*6, 同じくsha1の表現形式として {x-php-sha1} 、そして平文({cleartext}あるいは{clear}あるいは識別符号なし)の8つについて考えれば良さそうですね :) 面白くなりそうだ -- henoheno
- LDAPに関する基礎知識とか、標準的なRFCやその日本語訳については osdev-j:LDAP をどうぞ。RFC関連はたったいまアップデートをかけました。 -- henoheno
- とりあえずmd5プラグインへの妙なこだわりを一部捨てて、これを拡張して PHP md5(), PHP sha1(), PHP crypt(), OpenLDAP形式 MD5, OpenLDAP形式 SHA を出力できるようにしてみました。 {MD5} のようなスキームプレフィックスも追加できます(選択式)。中にある plugin_md5_compute() 関数をいずれ lib/func.php あたりに持っていくイメージでいます。PukiWikiをlocalhostで使うぶんには、これはこれで便利かもしれない :) -- henoheno
- ご紹介いただきました、PHPマニュアル中のノートにある SSHA のコードをチェックしました*7。このサンプルコードは PHP mhash extension を必要とするmhash関数を利用して PHP sha1() と同等の処理を行っている様です。この内容からは SSHA の構造(saltがどこか、といった)の推測ができるようですね。 -- henoheno
- SSHA & SMD5 対応から始まって、一気に管理者パスワードとBasic認証のユーザーパスワードの複数フォーマット対応まで片付いてしまいました (^^; (展開はや) -- henoheno
- 互換性情報については cvs:UPDATING.txt (1.24) をご覧下さい。
- sha1() のケアについては、mhashエクステンションが存在した場合にそれを利用する互換コードを用意するに留めさせていただきました。各種情報をお寄せいただき、ありがとうございました :) -- henoheno
- md5('user' . 'passwd') の類はどうするかは考えましたが、管理上の面倒臭さがより強まってしまうのと、少なくともこれでSMD5(salt付きMD5)が使える様になりましたので保留とさせて下さい。 -- henoheno
- 現状、SMD5およびSSHA を新たに生成する場合のsaltのランダムさは crypt() に依存しています (コード参照 (^^; ) 心配な方は (md5プラグインなどで生成する際に)任意のsaltを叩き込んで下さい。 -- henoheno
- パスワードを生成する関数 pkwk_hash_compute() はコマンドラインツールから利用される事も想定して作ってありますので、php cli が使える環境であれば auth.php (あとfunc.php)をrequire() することでコマンドラインから利用することもできると思います。 -- henoheno
- 保存しているパスワードを検証する際に、スキーム(フォーマット)の大文字・小文字を保存せずに比較するため、仮に {X-PHP-MD5} などと入力した場合に({x-php-md5}という値が返る為)検証時にパスワードが一致しない状態になっていました。そこで pkwk_hash_compute() のデフォルト動作として、入力されたスキームの文字列をそのまま用いる様に修正しました。 -- henoheno
- 一通り片付きましたので、状態を「完了」とします。また何かあれば書き込んで下さい :) -- henoheno
- なお、「スキームプレフィックスが無かった時のデフォルト」は空文({CLEARTEXT}、保存フォーマットなし)です。いずれ空文の使用を禁止しても良いとも思うのですが、今禁止してしまうと過去のユーザーパスワードとの互換が無くなりますので行い(え)ません。 -- henoheno
- $adminpassに {CLEARTEXT} が使用できなかった点を修正しました。そのほかクリンナップ。 -- henoheno
- より関数として自然な形となる様に、 pkwk_hash_compute() の第一引数と第二引数を入れ替えました。結果的にcrypt()関数と引数の意味と順番が揃いました -- henoheno