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