既存のCGIスクリプトでより強固なパスワード認証を行う

下記は現在参考になりません。
[CPANに上位互換のモジュール](http://search.cpan.org/~worthmine/Text-Password-AutoMigration/)をアップロードしましたのでこちらをお使いください。

また、その日本語解説は[こちら](https://qiita.com/worthmine/items/a7681a76e0ea23810b3d)をご覧ください。

---

ざっくりと探した感じ、同様の記事が見当たりませんでしたので自前のライブラリを共有します。

##概要
[kentweb](http://www.kent-web.com/)などを参考にパスワード認証付きCGIを書いていた場合、**パスワード認証のデフォルトが```crypt```(暗号化方式はDES)**だったりするのでパスワードの最大文字数に制限(正確には最大文字数以上の文字列の無視)があったり暗号化強度が低かったりします。
本ライブラリを用いると、新規作成分に関してはMD5となる一方、**```crypt```された文字列がサーバー内に残っていた場合には自動判別を行います**ので、より安全な認証へ低コストでマイグレーションできます。
**[Crypt::PasswdMD5](http://search.cpan.org/~rsavage/Crypt-PasswdMD5-1.40/lib/Crypt/PasswdMD5.pm)は別途cpanmなどでインストールしてください。**


##ライブラリ
~~~Password.pm
package Password;
use strict;
use warnings;

our $VERSION = '3.01';

use Carp;
use Crypt::PasswdMD5;

my @charset = ('A'..'Z', 'a'..'z', '0'..'9', '#', ',', qw# ! ? = + - * / _ [ ] { } ( ) < > | ~ ^ ' " % & . ; : $ #); # 強調表示の訂正用→'

our ( $Min, $Default ) = ( 4, 8 );	# パスワードとして使用を許可する入力文字列数の下限と初期値。

sub new {	# 後方互換
	generate(@_);
}

sub verify {
	my $class = shift;
	my ( $input, $data ) = @_;
	die __PACKAGE__. " doesn't allow any Wide character or white space.\n" if $input =~ /[\P{ASCII}\s]/;

	# MD5でない場合はperl cryptを使う
	return $data eq unix_md5_crypt( $input, $data ) if $data =~ m/^\$1\$/;
	return $data eq CORE::crypt( $input, $data );
}

sub nonce {
	my $class = shift;
	my $length = shift || 8;
	my $n;
	do {	# 暗号強度の低い文字列の場合はやり直す
		$n = '';
		$n .= $charset[ rand @charset ] until length $n >= $length;
	}while( $n =~ /^\w+$/ or $n =~ /^\W+$/ or $n !~ /\d/ or $n !~ /[A-Z]/ or $n !~ /[a-z]/ );

	return $n;
}

sub encrypt {
	my $class = shift;
	my $input = shift;
	croak __PACKAGE__ ." requires at least $Min length" if length $input < $Min;
	die __PACKAGE__. " doesn't allow any Wide character or white space.\n" if $input =~ /[\P{ASCII}\s]/;

	my $salt = shift || $class->nonce();
	carp "warning: short lengths salt is set. you don't have to." if length($salt) < 8;
	carp "warning: too many string lengths for salt. unix_md5_crypt() ignores more than 8." if $salt and length($salt) > 8;

	return unix_md5_crypt( $input, $salt );

	sub crypted {	# 超個人的後方互換
		encrypt(@_);
	}
}

sub generate {
	my $class = shift;
	my $length = shift || $Default;	# 文字数指定無しの場合、初期値で作成。
	croak "unvalid length is set" if $length !~ /^\d+$/;

	croak __PACKAGE__ ." requires list-context." unless wantarray;
	croak __PACKAGE__ ." requires at least $Min length" if $length < $Min;

	my $newpass;
	do {	 # 可読性の低い文字を含む場合はやり直す
		$newpass = $class->nonce( $length );
	}while( $newpass =~ m#[0Oo1Il2Zz5sS6b9qCcKkUuVvWwXx.,:;~^'"{}\[\]]# );


	return $newpass, __PACKAGE__->encrypt($newpass);
}

1;

__END__

=head1 NAME

Password - unix_md5_crypt()によるパスワードの作成と認証の簡素化。

=head1 VERSION

This document refers to version 3.00 of Password, released May 31, 2013

=head1 SYNOPSIS

 my( $pass, $cripted ) = generate Password(6);	# 新規作成

 my $input = $cgi->param('pass');				# フォームから読み取り
 my $data = Password->encrypt($input);			# 暗号強度の高いsaltを自動生成して暗号化
 my $flag = Password->verify( $input, $data );	# 認証

=head1 DESCRIPTION

=head2 Overview

OOPライクな呼び出しに対応したパスワードの作成と認証の一元化。
暗号化と認証はcrypt（認証のみ）とMD5によって実装。

 my( $pass, $encript ) = generate Password(6);	# セマンティクスな呼び出し
 my( $pass, $encript ) = Password->generate(6);	# OOPな呼び出し

どちらも可能ですが
B<オブジェクトを作りません。>
これは継続してblessすべき適当なデータが見つからないためです。

=head2 Constructor and initialization

 There is no constructor.

=head2 Methods and Subroutines

=over

=item new B<（注：コンストラクタではありません。）>
 same as generate( is B<NOT> a constructor )

=item generate
 新しいパスワードを作らせます。
 my( $pass, $cripted ) = generate Password(6);	# または Password->generate(6);

 指定長のランダム文字列を生成して暗号化文字列と共に返します。
 人間が誤読しやすい文字(0Oo1Il2Zz5sS6b9qCcKkUuVvWwXx.,:;~^'"{}[])を含まないように自動的に処理します。
 指定長の省略時は$Password::Defaultを使います。（変えなければ8）

=item encrypt

 指定文字列を暗号化して返します。
 saltは暗号強度の高い文字列を自動生成して使用します。
 my $data = encrypt Password($input);		# または Password->encrypt($input);

=item verify

 暗号認証して真偽値を返します。
 my $flag = verify Password( $input, $data );	# または Password->verify( $input, $data );

=back

=head1 SEE ALSO

 オブジェクト指向Perlマスターコース - ダミアン・コンウェイ pp187-190
 http://www.amazon.co.jp/exec/obidos/ASIN/4894713004/

=head1 Copyright

 Copyright (c) since 2005, Yuki Yoshida All rights reserved.

 This Module is free software.
 It may be used, redistributed and/or modified under the same terms as Perl itself.
~~~

##使い方

~~~passwd.pl
#! /usr/local/bin/perl

use strict;
use warnings;


use lib "$ENV{DOCUMENT_ROOT}/lib/perl5";	# CPANモジュールを読み込む
use lib "$ENV{DOCUMENT_ROOT}/lib";		    # 上位のモジュールを読み込む
use lib "./lib";				            # ローカルなモジュールを読み込む

use CGI ':standard';
use Password;

my $encode = 'UTF-8';
my $title = "パスワード暗号化スクリプト";
my $passwd = 'Qiita';

print header(-charset=>$encode), start_html( -title => $title, -encoding => $encode, -lang => 'ja');

print h1($title);
print p("Perl version : $]");
print h2('パスワード'),"\n";

my ($password, $salt) = ($passwd, Password->nonce() );
my @t_verify = ( 'deny', 'allow' );
my $encrypt;

print "raw: "		, $password ,br(),"\n";
print "salt: "		, CGI::escapeHTML( $salt ), br(),"\n";

print "Password.pm: ", CGI::escapeHTML( $encrypt = Password->encrypt($password, $salt) ), br(),"\n";
print "verify: "	, $t_verify[ verify Password($password, $encrypt) ],br(),br(),"\n";

my ($newpass, $newencrypt) = ( Password->generate(16) );
print "new: "		, CGI::escapeHTML( join " ",$newpass, $newencrypt ), br(), "\n";
print "verify: "	, $t_verify[ Password->verify($newpass, $newencrypt) ],br(),br(),"\n";

print end_html;
exit;
~~~