掲示板を作ったのはいいけど、「経験値システム」をうまく動作させるには、「経験値」を記録しておかなくちゃいけない。
↓
記録を名前で判別すると、同名の人とは経験値を共有することになってしまうから、パスワードも必要になってくる。
↓
パスワードをそのまま保存すると、他の人に見られたときに困るから、パスワードを暗号化して記録しておこう。
という、思考の流れで「暗号化」について考えてみた。
Perlには「crypt」という「文字列を暗号化する」命令がある。ただ、(Win32版の仕様かもしれないけど)半角で8文字までしか認識しないようで、9文字以降の文字が違っていても結果が同じになってしまうのが難点といえば難点か。
一般的に「暗号」というのは、「『鍵』を使って、暗号化されたものを元の文字列に戻せるもの」の事を言うのかもしれない。
「crypt」は逆に「『鍵』を使って文字列を暗号化(別の文字列に変換)する」ものです。「同じ文字列」を、「同じ鍵」を使って変換すれば、「常に同じ値が返ってくる」ので、パスワードなんかの認証にはちょうどいい。というか、そのために作られたのかもしれないけど・・・。その「鍵」は作成された文字列の中に隠されているので、それを使って変換すれば必ず同じ文字列になる。
「鍵」の事は「salt値」と言っているようです。その「salt値」は「2文字」で、大文字小文字アルファベットの52文字と数値の10文字、それに「.」と「/」を加えた64文字の中から選びます。試したかぎりではそれ以外でも大丈夫だけど、使わない方がいいでしょう。たぶん。
「crypt」で変換されると「salt値」は通常は最初の2文字につく。通常でない(より高度な変換?)場合は、最初が「$1$」で、その後ろの2文字が「salt値」になる。認証するときは、「salt値」を拾って同じように変換してやる。結果が違っていれば入力されたものは正しくないということになる。
これを元にして「登録」「認証」を作ればとりあえず完成だ。「変更」も作れば文句無しです。さて、頑張りましょうかね(笑)
「salt値」作成の部分で使っている「scalar()」という命令は「スカラー値」を明示的に要求する命令です。よくは知りませんが「コンテキスト」という概念が絡んでくるようです。
例えば、「localtime()」は直接「print」すると、意味不明な数値になって表示されます(本当は配列を区切り文字なしで表示しているからで、「join()」で連結させてやればそれなりに見えます)が、「スカラー値」を要求するとそれっぽい表示形式で値を返してくれるのです。
今の場合は、乱数の上限を制限するため「配列の個数」を要求しています。「$#char+1」とやれば済む話ですけど。この概念もなかなか奥が深そうです。
ちなみに「int()」は「整数化」。単に小数点以下を無視するようだ。
「rand()」は乱数発生装置。これも「0以上1未満」を返すんでしょうね。たぶん。これには引数が取れるので、「rand()*100」とかやらなくても「rand(100)」で済む。好みの問題でしょうけどね。
ここからは(も?)余談的。
世間に「crypt」が出てどのくらい経つのかわからないけど、実は「crypt」の結果から元の文字列を求めるプログラムが存在するらしい。だから暗号化したといっても、そのまま記録するのは危険なようだ。
簡単には解読できないようにする手段として「crypt」の結果に「crypt」をかける方法が、「Web裏技」で紹介されてました。今見たら、リニューアルされたようで、見つけられませんでしたけど・・・。
ただ、「crypt」の戻り値は「salt値」2文字を含めて13文字だから、8文字までしか認識できないのがやっぱり問題かもね・・・。分割して結合するっていう手もあるけど。
それに、公開するスクリプトの中には「複雑暗号化」のコードを書いても意味がない。「鍵」を見せないことが「暗号」である条件なんだから・・・。
「鍵」といえば「salt値」ですが、これを例の64文字にするのは、「『crypt』された文字列には、(経験上)その64文字が使われる」事に理由があるのではないかと思います。つまり、「crypt」の結果をそのまま使えば最初の(または「$1$」に続く)2文字ですが、それを適当な場所に埋め込んでもわからないようになってるんじゃないかと思ったわけです。解読プログラムがどういう物なのかわかりませんが、「salt値」がわからなければ多少なりとも解読が難しくなるんじゃないかな・・・と。ホントはマニュアルかなんかにお奨め方法として書いてあるのかもしれませんけどね(笑)
でも、パスワードを管理する側がいくら頑張っても、使う側が「生年月日」とか「連続した数字」、「意味のある単語」のようなモノを使っていたり、「紙に書いて保管」したりしてると・・・簡単にばれそうで恐いです。
ここから追記(2000/12/14)
「crypt」で暗号化された文字列の中で「salt値」が入っている場所は、今現在では間違いではないです。ただ、認証するときには「『暗号化された文字列そのもの』をsalt値の代わりに渡して、その結果が『暗号化された文字列そのもの』になるかを調べる」のが正しいようです。つまり、「crypt」が自分で「salt値」を探して暗号化するので、「salt値」がどこにあるかはスクリプトを組む上では知らなくてもいい事になります。
簡単に認証のサンプルを書けば以下のようなものになります。
1
2
3
4
5
6
|
$cryptedPW = crypt($PW, "xx"); # $PW を暗号化して $cryptedPW に格納する
if( crypt($enterPW, $cryptedPW) eq $cryptedPW ){
$enterPW は $PW と同じ文字列
}else{
$enterPW は $PW と違う文字列
}
|
この認証方法は「プログラミングPerl 改訂版(発行所/オライリー・ジャパン)」に使用方法の例として載ってる方法です。補足としてsalt値の場所の事についても書かれていますけどね・・・。
あと、このページでは「通常でない(より高度な変換?)場合は、最初が「$1$」で、・・・」と書いてある部分は、かなりの確率で間違っています。というよりは、暗号が2種類しかないように書いている時点で確実に間違っています。ただ、現時点では間違っているという事だけしかわかりません・・・。
ソースコード
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
#!/usr/bin/perl
#BEGIN{
# print "Content-type: text/plainnn";
# open(STDERR, ">&STDOUT");
# $|=1;
#}
$usr_title = 'テスト30';
require "tsenv.pl";
{
printHeader($usr_title);
printBodyHeader('<font face=times>Crypt Test</font>');
# 乱数初期化
srand;
crypt_test('12345678', '12345678');
crypt_test('987654321', '987654320');
crypt_test('abcdefgh', 'abcdefgi');
new_crypt_test('12345678', '12345678');
new_crypt_test('987654321', '987654320');
new_crypt_test('abcdefgh', 'abcdefgi');
print "<pre>";
print "Normal localtime : ";
print localtime();
print "n";
print "Scalar localtime : ";
print scalar(localtime());
print "n";
print "Expand localtime : ";
print join(", ", localtime());
print "n";
print "n";
print "int(1.5) = ";
print int(1.5);
print "n";
print "int(2.9) = ";
print int(2.9);
print "n";
print "int(-2.5) = ";
print int(-2.5);
print "n";
print "int(-2.9) = ";
print int(-2.9);
print "n";
print "int(-1.1) = ";
print int(-1.1);
print "n";
print "</pre>";
printFooter();
exit(0);
}
sub crypt_test{
my($setpass, $authpass) = @_;
# 設定パスワード確認
print qq(<table border=1 width="80%">n<tr>n<td width="40%">n);
Jprint("設定パスワード:$setpass<br>n");
# salt値作成
my @char = (".", "/", "0".."9", "a".."z", "A".."Z");
my $salt = '';
for(0,1){
$salt .= $char[int(rand(scalar(@char)))];
}
# salt値の確認
Jprint("Salt値:$salt<br>n");
# cryptする
$sp = crypt($setpass, $salt);
Jprint("crypt結果:$sp<br>n");
# 認証パスワード確認
print qq(<td width="40%">n);
Jprint("認証パスワード:$authpass<br>n");
# 作成したパスワードから「salt値」を取得
if($sp =~ /^$1$/){
$offset = 3;
}else{
$offset = 0;
}
$salt = substr($sp, $offset, 2);
# salt値の確認
Jprint("Salt値:$salt<br>n");
# cryptする
$ap = crypt($authpass, $salt);
Jprint("crypt結果:$ap<br>n");
# 結果は同じ?
if($sp eq $ap){
$result = 'OK';
}else{
$result = 'NG';
}
print "<tr>n<td colspan=2 align=center>n";
Jprint("認証結果:$result<br>n");
print "</table><br>nn";
}
# 新しい(というか、正しい)cryptの使い方
# 中身は手抜き・・・(^^;;;
sub new_crypt_test{
my($PW, $enterPW) = @_;
my $salt = './'; # salt値作成、手抜き
my $cryptedPW = crypt($PW, $salt);
my $result = '';
if( crypt($enterPW, $cryptedPW) eq $cryptedPW ){
$result = 'OK';
}else{
$result = 'NG';
}
print <<EOM;
<pre>
設定パスワード : $PW
認証パスワード : $enterPW
照合結果 : $result
</pre>
EOM
}
|