パスワードの取り扱い

パスワードを知っている人のみが 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変数に格納して、そのアドレスを返します。

char* encrypt = crypt( passwd, salt );
この encrypt をファイルに保存します。 これで真のパスワードの暗号化ができました。

以上の操作をもっとも簡潔にまとめると次の1行になります。

puts( crypt( getpass("New password:"), makesalt() ) );
ただし、crypt関数が header fileに宣言されていないことがありますので その場合には次のようにして自分で宣言して下さい。
extern "C" char* crypt( const char*, const char* );
コンパイルの際には -lcrypt オプションをつけて下さい。

もうちょっとまともに、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を使うべきでしょう。


目次

Copyright(C) by Naoki Watanabe. Oct 21st, 1995.
渡辺尚貴 naoki@cms.phys.s.u-tokyo.ac.jp