パスワードを知っている人のみが CGIを特定の目的で 使えるようにしたいことがよくあります。 その目的には .htaccessを使うことが 最も簡単かつ確実なのですが、その方法ではなくて、自分のCGIで パスワードを受け取って確かめたいと思うことがあるかも知れません。 その場合にはパスワードを注意して取り扱わないなりません。
例えばFORMに入力されたパスワードがCGIのポインタ変数 password の 指すメモリに格納されたとしましょう。次のようにして パスワードを照合する方法は最低です。絶対にこのようなことをしてはいけません。
なぜなら、プログラムのソースを見れば真のパスワードが判ってしまうからです。 ソースが見えなくても、実行ファイルを strings コマンドで見れば これかパスワードであることはすぐに判ります。 また、実行中に core dump させてメモリイメージをファイルに書き落とすことでも パスワードを知ることができます。デバッガをうまく使うことでもできるでしょう。if( strcmp( password, "abcdefgh" ) ){ printf("Mismatch!\n"); exit(1); }
この問題は真のパスワードを暗号化することで解決できます。
つまり真のパスワードの文字列をある特定の操作法でグチャグチャにかき混ぜて
暗号化された真のパスワードの文字列を作成して、これをプログラムかファイルに
格納しておきます。
そして、ユーザーから入力されたパスワードを同じ操作法で暗号化します。
これと暗号化された真のパスワードとを比較して認証します。
この暗号化で重要なのは、暗号化した文字列から元の文字列を 得ることが不可能であることです。つまり修復不可能なくらいにグチャグチャに してあるわけです。なので例え暗号化された真のパスワードが見られても、 元のパスワードを推測することは不可能なのです。もちろん、さまざまな文字列を 暗号化してみて、この暗号化された真のパスワードと一致するものがないかを 探せば真のパスワードを見つけることはできますが、真のパスワードが十分に 複雑な文字列ならば、それを探し当てるののには相当な時間がかかるでしょう。
UNIXではこの暗号化の目的に crypt関数が提供されています。 この crypt 関数が用いている暗号化の操作のアルゴリズムには MD5 と DES の 2種類があります。普通は MD5 のアルゴリズムで暗号化するのですが、 OSによっては DES で暗号化します。FreeBSDは MD5 を使いますが、 crypt関数が定義されているライブラリを DESのものに入れ換えることで DESで暗号化するようにすることもできます。
真のパスワードを暗号化するプログラムの作成手順を紹介します。
まずパスワードとなる文字列を考えて下さい。 パスワードは意味不明な記号列とするべきです。辞書に載っているような 英単語やそれに数字を加えただけのような文字列では駄目です。 アルファベットの大文字と小文字、数字、記号を含んだ記号列を使うと良いでしょう。 覚えにくそうでも何度も打ち込んで使えば自然に覚えられます。
真のパスワードをキーボードから打ち込むことにします。 打ち込まれたパスワードを読み込むのには getpass()関数を使うのが良いでしょう。 この関数を使うと打ち込んだ文字が画面に表示されないので パスワードを打ち込むのに向いているのです。
getpass関数にはプロンプトととなる文字列を引数に与えます。 この関数は読み込んだパスワードを関数内の static 変数に格納して そのアドレスを返します。なので別の変数にそのパスワードを コピーしたほうが良いでしょう。簡単に strdupで済ませます。getpass("New password:")
もう一度、パスワードを打ち込んでもらって 打ち間違えていないことを確認にしましょう。char* passwd = strdup( getpass("New password:") );
実はこの確認の作業で getpass() 関数を再度使うので 読み込んだパスワードを別変数にコピーしていたのでした。if( strcmp( passwd, getpass("Again please:"))){ fputs("Miss type! Confirm the password.\n", stderr ); exit(1); }
次に、パスワードをグチャグチャに暗号化する際のグチャグチャの塩加減?を 調整するパラメタである salt を用意します。 salt には [./0-9A-Za-z] の範囲の文字をデタラメに 8文字ならべた文字列にします。 saltの作り方には他の方法もありますがこれで十分でしょう。char* passwd = getpass("New password:");
この関数は関数内 static変数にsaltを作成し、そのアドレスを返します。 saltの乱数にそれほど凝っても意味は無いので簡単に済ませます。 乱数の下位桁はあまり乱数でないのでやや上位の6ビットを使うようにします。 saltはNULL終端しなくても構いません。char* makesalt( void ) { static char salt[10]; static char saltchar[] = { "./0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" }; srand(time(NULL)); for( int i=0; i<8; i++ ){ salt[i] = saltchar[ (rand()>>6) & 0x3f ]; } return salt; }
そして crypt関数による暗号化です。crypt関数も暗号化の文字列を 関数内static変数に格納して、そのアドレスを返します。
この encrypt をファイルに保存します。 これで真のパスワードの暗号化ができました。char* encrypt = crypt( passwd, salt );
以上の操作をもっとも簡潔にまとめると次の1行になります。
ただし、crypt関数が header fileに宣言されていないことがありますので その場合には次のようにして自分で宣言して下さい。puts( crypt( getpass("New password:"), makesalt() ) );
コンパイルの際には -lcrypt オプションをつけて下さい。extern "C" char* crypt( const char*, const char* );
もうちょっとまともに、2度タイプしてミスタイプをチェックするようにすると 以下のようになります。
int main( void ) { char* passwd = strdup( getpass("Input a new password:") ); if( strcmp( passwd, getpass("Input a new password:") ) ){ fputs("Miss type! Confirm the password.\n", stderr ); exit(1); } puts( crypt( passwd, makesalt() ) ); free(passwd); return 0; }
ユーザーがFORM等から送ってきたパスワードを照合するには以下のようにします。 encryptに暗号化された真のパスワードを読み込んでおき、 passwdにユーザーが打ち込んだパスワードが入っているとします。
if( strcmp( encrypt, crypt( passwd, encrypt ) ) ){ printf("Mismatch!\n"); exit(1); }
FORMに打ち込んだパスワードは、そのままの形で ネットワーク回線を流れるので、盗聴されている可能性があります。 盗聴された場合、このパスワードは無力です。 Web browserはFORMのデータを暗号化してからネットワーク回線に流す ことができますが、そのためには Apache-SSL を使わないとなりません。
でも、少なくともパスワードを含んだFORMデータをGET methodもしくは CGIのコマンド引数として送信することだけは絶対にやめましょう。 clientとserverの両方のログにはっきりと残ってしまい、公衆の面前に さらされてしまいます。
複数のCGI間をこのパスワードで移動するなら、 なんらかの方法でこのデータを次のCGIに渡さなければなりません。 FORMのHIDDEN属性の変数にパスワードを含めて、SUBMITで次のCGIに 移動するか、パスワード丸見えを覚悟でCGIのコマンド引数に付けるか、 パスワードをcookieにしてユーザに食べさせるかです。
これらを考えるとやはり .htaccessを使うべきでしょう。