Saturday, April 5, 2008

htpasswd のやってることを Perl で

Apache の htpasswd で作るパスワードのダイジェストと同じものを Perl で作るメモ。

CRYPT

htpasswd のスイッチ -n はダイジェストを STDOUT に出力する。
-d は CRYPT を使ってダイジェストを作るスイッチ。 
-b はパスワードをコマンドラインから指定するスイッチですが、ここでは例示を単純にするために使っているだけで、使用は推奨されてません。

$ htpasswd -ndb user password
user:ScRpgNPWh3biw

出力されたダイジェストのうち最初の二文字、ここでは Sc が SALT になっています。

同じことを Perl でやるには crypt 関数を使います。

$ perl -E "say crypt('password', 'Sc')"
ScRpgNPWh3biw

-E スイッチや say は 5.10 以降の安定版 Perl で使えます。
5.8 以前の安定版 Perl では代わりにそれぞれ -le スイッチと print を使います。

$ perl -le "print crypt('password', 'Sc')"
ScRpgNPWh3biw

以降は同様に適宜読みかえてください。

SHA (SHA-1)

-s はダイジェストを作るのに SHA を使うスイッチ。

$ htpasswd -nsb user password
user:{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=

出力されたダイジェストは Base64 でエンコードされています。

Perl では CPAN モジュールの Digest::SHA1 を使います。
Perl の Digest 系のモジュールは Base64 でエンコードされた文字列を出力できるので‥‥

$ perl -MDigest::SHA1 -E \
> "say Digest::SHA1->new()->add('password')->b64digest"
W6ph5Mm5Pz8GgiULbPgzG37mj9g

と、簡単に終わせたいところですが、ぱっと見て分かるように = でパディングされておらず、Base64 として不完全な文字列が出力されてしまっています。

Base64 は RFC 2045 で定義されているデータの符号化方式で、データをアルファベットと数字そしていくつかの記号だけで表現するために使われています。
Base64 は 3バイトのデータをひとかたまりとして、それを 4文字の文字列で表現します。そして、データの終端で元のデータが 3バイトに満たない場合に、符号化後の文字列の足りない部分を = で埋めて 4文字にします。

Digest::SHA1 の b64digest、正確にはベースクラスである Digest::base の b64digest は標準モジュールの MIME::Base64 を使っているのですが、MIME::Base64::encode が返した文字列からわざわざ文字列終端の = を削除しています。Digest::SHA のドキュメントによると互換性のためのようですが‥‥なんだかなぁ。

しょうがないので MIME::Base64 を使って自分でエンコードします。

$ perl -MDigest::SHA1 -MMIME::Base64 -E \
> "say encode_base64(Digest::SHA1->new()->add('password')->digest)"
W6ph5Mm5Pz8GgiULbPgzG37mj9g=

このダイジェストを .htpasswd ファイルなどで使う場合には、「ユーザ名:{SHA}パスワードのダイジェスト」といった書式にしなければならないことを忘れないでください。

ユーザ名:{SHA}パスワードのダイジェスト
user:{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=

Perl 5.10 では Digest::SHA が標準モジュールになっているので、普段 SHA を使う場合にはこちらを使うのが良さそうです。今後はあまり SHA-1 を使う機会は無いかも知れませんが、いちおう同様に書けます。

$ perl -MDigest::SHA -MMIME::Base64 -E \
> "say encode_base64(Digest::SHA->new('sha-1')->add('password')->digest)"
W6ph5Mm5Pz8GgiULbPgzG37mj9g=

Digest::SHA のコンストラクタは引数の数字しか見ていないので、new(1) でも new('sha-1') でも new('しゃ1') でも同じです。

MD5

最後に htpasswd の -m スイッチによる MD5 のダイジェスト。

$ htpasswd -nmb user password
user:$apr1$MWo/B...$VlC12Us2.D82inoA7m9w81

これも Perl 標準モジュールの Digest::MD5 で‥‥といきたいところですが、MD5 といっても単純に MD5 のダイジェストがそのまま使われているわけではなく、htpasswd は MD5 を使ってごにょごにょした後のものを出力しているので、ここは素直に CPAN にある Crypt::PasswdMD5 モジュールの apache_md5_crypt を使うのが良さそうです。

$ perl -MCrypt::PasswdMD5 -E \
> "say apache_md5_crypt('password', 'MWo/B...$')"
$apr1$MWo/B...$VlC12Us2.D82inoA7m9w81

2番目の引数は SALT で、htpasswd で作られた SALT と同じものを指定するのでなければ、引数自体を省略できます。

No comments:

Post a Comment