秘密情報の管理

前へ << 文字コードとエンコーディング (2) cookie >> 次へ

パスワード

掲示板の管理者モードのように、パスワードを入力させる場合は、 ユーザにパスワードを入力させる必要があります。 根本的にはプログラム中にパスワードをそのまま記述し、
if ( $password eq 'secret' ){
  OK
} else {
  NO
}
と照合すればよいのですが、 CGI プログラムがスクリプトで、 CGI が nobody 権限で動くサーバの場合は好ましくありません。

password-plain.cgi (実行結果)

    1: #!/usr/local/bin/perl
    2: 
    3: print "Content-type: text/html\n\n";
    4: print "<HTML><BODY>\n";
    5: 
    6: if ( $ENV{REQUEST_METHOD} eq 'POST' ){
    7:     read(STDIN,$buf,$ENV{CONTENT_LENGTH});
    8:     foreach ( split('&',$buf) ){
    9:         ($key,$value) = split('=',$_);
   10:         $value =~ tr/+/ /;
   11:         $value =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack("C",hex($1))/eg;
   12:         $FORM{$key} = $value;
   13:     }
   14: 
   15:     if ( $FORM{'password'} eq 'secret' ){
   16:         print "ユーザ認証 OK です。\n";
   17:     } else {
   18:         print "パスワードが違います。\n";
   19:     }
   20: }
   21: 
   22: 
   23: # 自分自身のファイル名を取得
   24: $script_name = $ENV{SCRIPT_NAME};
   25: $script_name =~ s|.*/([^/]+)$|$1|;
   26: 
   27: print <<END;
   28: <HTML><BODY>
   29: <FORM METHOD=POST ACTION="$script_name">
   30: パスワードを入力して下さい。
   31: <INPUT TYPE=TEXT NAME=password VALUE=""><BR>
   32: (パスワードは secret です)
   33: <INPUT TYPE=SUBMIT VALUE=OK>
   34: </FORM></BODY></HTML>
   35: END
このように書けば認証はできるのですが、これでは同じサーバに所属する 別のユーザが、スクリプトの内容を覗けばパスワードがばれてしまいます。
CGI プログラムが所有者の権限で動く WWW サーバならば パーミッションを 700 にしておけば OK です。 それでも root が見ると一発でばれてしまいます。 別に root を信用するなというわけではありませんが、 パスワードを plain text で保存しておくのは気持ちが悪いです。

そういう場合は、crypt 関数か、MD5 を使います。

crypt

crypt 関数は、UNIX のユーザ認証でも使われています。 特徴をあげると、 というものです。

例えば、仮に crypt が `secret' という文字列を `AJRODS' に変換するとしましょう (あくまで例です)。 その場合は (この crypt は簡略化したもので、実際は2つの引数を与えます。後述)

if ( crypt($password) eq 'AJRODS' ){
  OK
} else {
  NO
}
と書けるわけです。これならソースを見られても 元の文字列はわかりませんので安心です。
crypt の逆の動作、つまり `AJRODS' から `secret' という文字列を 得ることはできません。唯一可能なのはあらゆる文字列を列挙し、 総当りで crypt を試し、`AJRODS' という文字列に変換されるかどうか 試す方法です。もしパスワードに a〜z、A〜Z しか使わないとして、 さらにパスワードを8文字と仮定しても、52*52*52*52*52*52*52*52、 つまり 53459728531456 通りの文字列が考えられます。 1秒1万通り処理できるとしても、169年かかります。 実際は 0〜9 や記号もあり、パスワード8文字とは限らないので、 可能性はさらに増えます。

実際の crypt はもう少し複雑で、salt という仕組みを使います。 まず、最初に crypt を使うときは、salt を決めます。 salt は普通はランダムな文字列を使いますが、ここでは仮に `AB' としましょう。

$crypted_password = crypt('secret','AB');
print "$crypted_password\n";
とすると、`ABS5SGh1EL6bk' と表示されるはずです。 先頭の2文字が salt と同じ `AB' になっていることに注意して下さい。

実際の crypt は、パスワードと salt を渡します。

if ( crypt($password,'ABS5SGh1EL6bk') eq 'ABS5SGh1EL6bk' ){
  OK
} else {
  NO
}
$password eq `secret' ならば OK になります。

DES と MD5

先にあげた
$crypted_password = crypt('secret','AB');
print "$crypted_password\n";
で、`ABS5SGh1EL6bk' ではなく `$1$AB$qHArPuqikDNZFVs6JTunC1' と 表示された方もいるでしょう。前者は DES の変換結果、 後者は MD5 の変換結果です。

crypt はあくまで一方向ハッシュへのインタフェースで、 その中でどういうふうに変換するかは OS が決めます。 Solaris・Linux などでは DES という仕組みが使われますが、 FreeBSD のデフォルトでは MD5 が使われています。 とはいえ、FreeBSD で設定によっては DES に切り替えることも できますので、「OS が〜だから DES だろう」と決めつけではいけません。

DES の場合は salt は2文字で、「XXYYYYYYY」 という形式になります。MD5 の salt は8文字 (以内) で、 「$1$XXXXXX$YYYYYYYY$」 となります (赤色の部分が salt です)。

ホストが DES でも MD5 でも、対応できるようにしたものが 以下のプログラムです。X68000.q-e-d.net は MD5 方式を使っているので $1$AB$...$ の方にマッチしますが、 DES 方式のマシンに持っていくと ABS...k の方にマッチするでしょう。

password-crypt.cgi (実行結果)

    1: #!/usr/local/bin/perl
    2: 
    3: # DES で crypt 済のパスワード
    4: $des_crypted_password = 'ABS5SGh1EL6bk';
    5: 
    6: # MD5 で crypt 済のパスワード
    7: $md5_crypted_password = '$1$AB$qHArPuqikDNZFVs6JTunC1';
    8: 
    9: $salt = 'AB';
   10: 
   11: print "Content-type: text/html\n\n";
   12: print "<HTML><BODY>\n";
   13: 
   14: if ( $ENV{REQUEST_METHOD} eq 'POST' ){
   15:     read(STDIN,$buf,$ENV{CONTENT_LENGTH});
   16:     foreach ( split('&',$buf) ){
   17:         ($key,$value) = split('=',$_);
   18:         $value =~ tr/+/ /;
   19:         $value =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack("C",hex($1))/eg;
   20:         $FORM{$key} = $value;
   21:     }
   22: 
   23:     print "crypt('$FORM{'password'}','$salt') =",crypt($FORM{'password'},$salt),"<P>\n";
   24:     print "\$des_crypted_password = '$des_crypted_password'<BR>\n";
   25:     print "\$md5_crypted_password = '$md5_crypted_password'<P>\n";
   26: 
   27:     if ( crypt($FORM{'password'},$salt) eq $des_crypted_password ){
   28:         print "ユーザ認証 OK です。このホストの crypt 方式は DES です。\n";
   29:     } elsif ( crypt($FORM{'password'},$salt) eq $md5_crypted_password ){
   30:         print "ユーザ認証 OK です。このホストの crypt 方式は MD5 です。\n";
   31:     } else {
   32:         print "パスワードが違います。\n";
   33:     }
   34: }
   35: 
   36: 
   37: # 自分自身のファイル名を取得
   38: $script_name = $ENV{SCRIPT_NAME};
   39: $script_name =~ s|.*/([^/]+)$|$1|;
   40: 
   41: print <<END;
   42: <HR><FORM METHOD=POST ACTION="$script_name">
   43: パスワードを入力して下さい。
   44: <INPUT TYPE=TEXT NAME=password VALUE=""><BR>
   45: (パスワードは secret です)
   46: <INPUT TYPE=SUBMIT VALUE=OK>
   47: </FORM></BODY></HTML>
   48: END
ただし、普通はこういう風に DES と MD5 に対応するようなことは しないと思います。

md5 コマンド

crypt と似たようなものに md5 コマンドがあります。 混乱しないでいただきたいのですが、crypt の方式として DES と MD5 という方式があります。それとは別に md5 という コマンドがあるということです。

前へ << 文字コードとエンコーディング (2) cookie >> 次へ

$Id: cryptogram.html,v 1.8 2004/12/04 15:26:41 68user Exp $