2010年10月13日の日記で書いているように、Perlモジュールの Crypt::SaltedHash を使って、パスワードを salt値を加えてハッシュ化して保存し、さらにそれをチェックする CGIプログラムを書いてみました。
そのサンプルがこちら↓
サンプルには、アカウントに pochi、パスワードに secret_passwords を登録済みですけども、お好きなアカウントでパスワードを登録して確かめてみても大丈夫です。
まずは Crypt::SaltedHash を使う為に libcrypt-saltedhash-perl をインストールします。
$ sudo apt-get install libcrypt-saltedhash-perl
Webサーバの公開ディレクトリのうち、Perl の CGI を実行可能なディレクトリに下のコードを放り込んで Webブラウザから実行してみると、サンプルのようになります。このコードでは salt値の長さは 8バイト、ハッシュ関数には SHA-512 を使っています。
#!/usr/bin/perl -T
use strict;
use warnings;
use CGI;
use CGI::Carp qw/fatalsToBrowser/;
use Crypt::SaltedHash;
my $account;
my $password;
my $salt_len = 8; # salt値の長さ
my $file = '/path/to/data.txt'; # データファイルは公開ディレクトリには置かないようにしましょう
my $cite_name = 'サイト名';
my $page_name = 'ページ名';
my $home = '/path/to/index.html'; # トップページへのパス
my $style_sheet = '/path/to/stylesheet.css'; # スタイルシートへのパス
my $mime = $ENV{'HTTP_ACCEPT'} =~ /application\/xhtml\+xml/
? 'application/xhtml+xml'
: 'text/html';
my $cgi = CGI->new();
unless ($cgi->param()) {
print_xhtml();
} else {
$cgi->param('account') =~ /(^\w{3,64}?$)/
? $account = $1
: print_xhtml("アカウントは半角英数 3字以上 64字以下です。\n");
$cgi->param('password') =~ /(^\S{8,64}?$)/
? $password = $1
: print_xhtml("パスワードは半角 8字以上 64字以下です。\n");
set_account($account, $password);
}
sub set_account {
my ($id, $pwd) = @_;
if (-e $file) {
$/ = undef;
open my $fh, '<', $file or die "Couldn't open $file for reading: $!\n";
my $str = <$fh>;
close $fh or die "Couldn't close $file: $!\n";
$/ = "\n";
$str =~ s/\n/ /g;
my %db = split / /, $str;
exists $db{$id} and print_xhtml("そのアカウントは既に使われています。\n");
}
my $csh = Crypt::SaltedHash->new(algorithm => 'SHA-512',
salt_len => $salt_len,
); # ランダムな salt値をまぶして SHA-512 でハッシュ化する準備を整え
$csh->add($pwd); # パスワードを受け取って
my $salted = $csh->generate; # salt値をまぶしてハッシュ化
open my $fh, '>>', $file or die "Couldn't open $file for writing: $!\n";
print $fh $id, ' ', $salted, "\n";
close $fh or die "Couldn't close $file: $!\n";
print_xhtml("アカウントを登録しました。\n");
}
sub print_xhtml {
my $msg = shift;
print <<"EOX";
Content-Type: $mime; charset=utf-8
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
<head>
<link rel="start" rev="appendix" href="$home" title="$cite_name" />
<link rel="Stylesheet" href="$style_sheet" title="stylesheet" type="text/css" />
<title>$page_name - $cite_name</title>
</head>
<body>
<h1>$page_name</h1>
EOX
unless (defined $msg) {
print <<"EOH";
<form method="post" action="$ENV{'SCRIPT_NAME'}">
<fieldset>
<legend accesskey="I">入力情報</legend>
<div>
<label for="account">アカウント</label>
<input type="text" name="account" id="account" />
</div>
<div>
<label for="password">パスワード</label>
<input type="password" name="password" id="password" />
</div>
</fieldset>
<p><input type="submit" value="決定" /></p>
</form>
<p><a href="$home">戻る</a></p>
EOH
} else {
print <<"EOT";
<p>$msg</p>
<p><a href="$ENV{'SCRIPT_NAME'}">戻る</a></p>
EOT
}
print <<"EOM";
</body>
</html>
EOM
exit;
}
#!/usr/bin/perl -T
use strict;
use warnings;
use CGI;
use CGI::Carp qw/fatalsToBrowser/;
use Crypt::SaltedHash;
my $account;
my $password;
my $salt_len = 8;
my $file = '/path/to/data.txt'; # データファイルは公開ディレクトリには置かないようにしましょう
my $cite_name = 'サイト名';
my $page_name = 'ページ名';
my $home = '/path/to/index.html'; # トップページへのパス
my $style_sheet = '/path/to/stylesheet.css'; # スタイルシートへのパス
my $mime = $ENV{'HTTP_ACCEPT'} =~ /application\/xhtml\+xml/
? 'application/xhtml+xml'
: 'text/html';
my $cgi = CGI->new();
unless ($cgi->param()) {
print_xhtml();
} else {
$cgi->param('account') =~ /(^\w{1,64}?$)/
? $account = $1
: print_xhtml("無効なアカウントです。\n");
$cgi->param('password') =~ /(^\S{1,64}?$)/
? $password = $1
: print_xhtml("無効なパスワードです。\n");
valid_password($account, $password);
}
sub valid_password {
my ($id, $pwd) = @_;
$/ = undef;
open my $fh, '<', $file or die "Couldn't open $file for reading: $!\n";
my $str = <$fh>;
close $fh or die "Couldn't close $file: $!\n";
$/ = "\n";
$str =~ s/\n/ /g;
my %db = split / /, $str;
my $salted = $db{$id};
my $valid = Crypt::SaltedHash->validate($salted, $pwd, $salt_len); # ここで検証しています
$valid
? print_xhtml("正しいパスワードです。\n")
: print_xhtml("不正なパスワードです。\n");
}
sub print_xhtml {
my $msg = shift;
print <<"EOX";
Content-Type: $mime; charset=utf-8
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
<head>
<link rel="start" rev="appendix" href="$home" title="$cite_name" />
<link rel="Stylesheet" href="$style_sheet" title="stylesheet" type="text/css" />
<title>$page_name - $cite_name</title>
</head>
<body>
<h1>$page_name</h1>
EOX
unless (defined $msg) {
print <<"EOH";
<form method="post" action="$ENV{'SCRIPT_NAME'}">
<fieldset>
<legend accesskey="I">入力情報</legend>
<div>
<label for="account">アカウント</label>
<input type="text" name="account" id="account" />
</div>
<div>
<label for="password">パスワード</label>
<input type="password" name="password" id="password" />
</div>
</fieldset>
<p><input type="submit" value="決定" /></p>
</form>
<p><a href="$home">戻る</a></p>
EOH
} else {
print <<"EOT";
<p>$msg</p>
<p><a href="$ENV{'SCRIPT_NAME'}">戻る</a></p>
EOT
}
print <<"EOM";
</body>
</html>
EOM
exit;
}
動作確認環境 : Perl 5.10.1 on Debian GNU/Linux 6.0 squeeze