パスワードをハッシュ化して保存する

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 を使っています。

登録用CGI - touroku.cgi

#!/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;
}

確認用CGI - kakunin.cgi

#!/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

戻る


Last updated : 2011/03/27
Author : 犬山ぽち丸 / INUYAMA Pochimaru / Pochimaru Inuyama
E-mail : pochi@hoshinoumi.net
Key ID : 4A1B5E85
Key fingerprint : 4605 4D40 6154 20C1 5592 3E54 5A37 FEE9 4A1B 5E85

正当な XHTML 1.1 です 正当な CSS です