このブログを検索

ラベル 暗号化 の投稿を表示しています。 すべての投稿を表示
ラベル 暗号化 の投稿を表示しています。 すべての投稿を表示

2021/05/13

公開鍵暗号方式における暗号化とデジタル署名の双方向性に関する考察

公開鍵暗号方式において、暗号化は、公開鍵を用いて行われ、受信者は秘密鍵を用いて暗号化されたデータを復号する。

 

このとき公開鍵と秘密鍵はペアであり、データを暗号化して送信する者をA、データを受信して復号する者をBとすると、公開鍵はBが作成して公開した鍵であり、秘密鍵はBが作成して自分で保持している鍵である。

 

公開鍵を公開しないと暗号化することができないし、

秘密鍵を公開してしまうと誰でも復号化できてしまう。

 

公開鍵暗号方式では署名と検証ということも行われる。

 

署名と検証においては送信者が秘密鍵を用いて署名し、

受信者が公開鍵を用いて検証する。

 

この話を聞くと、

『要するに暗号化は公開鍵で暗号化して、署名は秘密鍵で暗号化するってことだね?』

と言いたくなってしまうのだが、そうではない。

 

とりあえず、「署名は秘密鍵で暗号化するということではない」と覚えておけば、

会話して笑われたり怒られたりすることはない。

 

が、そこで『じゃあいったい署名ってどんな処理なの?そしてそれを検証するってどういう処理なの?』

 

と考えてほしい。

 

というか、私はそれを考えて軽く調べてみたのだがよくわからないのである

 

公開鍵暗号方式は、ある程度大きな数の素因数分解が困難であることを利用している。

ある程度大きな桁数の素数を掛けることは容易であるが、

結果からもとの二つの素数を求めることはコンピュータを使っても事実上不可能である。

 

 

d1をもとのデータ、d2を暗号化したデータ、k1を公開鍵、Eを暗号化する演算とすると、


d1 E k1 = d2

 

k2を秘密鍵、Dを復号する演算とすると

 

d2 D k2 = d1

 

となればよい。

 

 このとき、Eが単なる掛け算であったら、k1は公開されているので、

d1を求めるにはd2 k1で割ればよいことになってしまう。

 

だから、Ek2を使わないと復号できないような演算でなければならない。

 

それはどういう方法だろうか?

 


 

RSA暗号のwikipediaを見る


鍵のペア(公開鍵と秘密鍵)を作成して公開鍵を公開する。

まず、適当な正整数 e を選択する。

また、大きな2つの素数の組み{p,q}を生成し、

それらの積 n=pq を求めて、

組み{e,n}を平文の暗号化に使用する鍵(公開鍵)とする。

2つの素数{p,q} は秘密に保管し、

暗号文の復号に使用する鍵(秘密鍵)の生成にも使用する

d=e^-1 mod Φ

ここで

Φ=lcm(p-1,q-1)

lcmは最小公倍数

暗号化(平文 m から暗号文 c を作成する):

c=m^e mod n

復号(暗号文 c から元の平文 m を得る):

m=c^d mod n

暗号化(乗)は公開鍵 {e,n} があれば容易に計算でき、

復号(乗)も容易に計算できる。

しかし、秘密鍵 d を知らずに解読(法 n の下で e 乗根を計算)するのは、

の素因数を知らないと難しい(大きい合成数の素因数分解も難しい)」

と考えられている。

つまり、「秘密鍵を用いずに暗号文から平文を得ることは難しい」と信じられている。

これがRSA暗号の安全性の根拠である。



 

鍵生成

 

をセキュリティパラメータとする。

p,q(ただしp≠q)をk/2ビットの素数とし、

n=pqとする。

eΦ(n)未満の正の整数で、

Φ(n)と互いに素な数とし、

dを、Φ(n)を法としたeの逆数 ( deΞ1 (mod Φ(n)) )とする。

ただしここでΦはオイラーのφ関数で、

この場合は

Φ(n)=(p-1)(q-1)

である。

dは、e,Φ(n)が既知の時には拡張されたユークリッドの互除法を使えば容易に求まる。

deΦ(n)で割った整数商をxとした場合、

de+(-x)Φ(n)=1

が成り立ち、

かつ

eの取り方から

gcd(e,Φ(n)) = 1

であるのでこれを解けばよい、

dを秘密鍵とし

n, eを公開鍵とする

pqが漏れるとdが計算で求まるため、pqは安全に破棄すること

 

暗号化

 

aを平文空間Znの元とする。

b=a^e mod n を計算し、bを出力する。

 

復号化

bを暗号文とする。

a' = b^d mod n を計算し、a'を出力する。

 

ここでa'=aとなり、復号できる。

  

ちなみに公開鍵暗号方式は最初RSA暗号方式なしに考案されて、

それを実現するためにRSA暗号方式が考えられたらしい。

 

 

さて。

 

では署名と検証である。

 

だいたい、以下のように説明されている。

 

送信者がデータのハッシュ値を計算して秘密鍵で暗号化し添付(これが署名)

受信者が暗号化されたハッシュ値を公開鍵で復号

受信者が自分でデータのハッシュ値を計算して、復号したハッシュ値と等しいかどうかを確認

 

wikipediaに書いてある通り、暗号化も復号化もおこなっている演算はある数について

ある数の累乗を求めそれをある数で割った余りを計算している。


同じ計算を違うパラメータでおこなうことによって暗号化になり復号化になるのである。


デジタル署名においては先に秘密鍵による演算がなされてその結果に対して公開鍵による演算がなされる。


これを、『秘密鍵により暗号化し公開鍵により復号化する』と言ってはいけないのだ。

そう言うと怒る人がいる。


怒る理由は、私が調べて理解したところでは以下の2点である。


1. それはRSA暗号方式にしかあてはまらない

2. 署名で「暗号化」するのはデータではなくデータのハッシュである




以下はRSA暗号方式を使うこと前提にした話である。



私が調べて理解した知識によれば、「秘密鍵でも暗号化できる」。


ただし、デジタル署名で暗号化するのはデータそのものではなくデータのハッシュである。


秘密鍵で暗号化されたデータのハッシュを公開鍵で復号化し、

自分でデータのハッシュを計算してその結果を復号化したハッシュと比較する、

これが「署名の検証」である。



では、データのハッシュを公開鍵で暗号化して秘密鍵の保持者に送信したら「署名」になるか?


ならない。


なぜなら公開鍵は公開されているので誰でもそれを使ってハッシュを暗号化することはできるからだ。





では、ハッシュではなくデータそのものを秘密鍵で暗号化して公開鍵で復号化させたらどうか?



暗号化というのは、「鍵を生成した人」ではなく、「鍵を生成した人に暗号化したデータを送信する人」が行う。


では、逆の場合は?「鍵を生成した人」が、誰かに暗号化したデータを送りたいときにはどうすればいいのか?


その場合は、データを送りたい相手に鍵ペアを生成してもらうのか。




秘密鍵でハッシュを暗号化し復号できるなら、秘密鍵でデータそのものも暗号化できるはずだが、そういう話は聞かない。


それをしないひとつの理由として、秘密鍵は公開鍵より鍵長が長くて演算に時間がかかる、ということが説明されていたが、


時間がかかるからやらない、ということなら不可能ではないということだ。


(未完)





2019/07/27

公開鍵と秘密鍵のどちらで暗号化するのか

先日受けた安全確保支援士試験で公開鍵か秘密鍵かを選ぶ問題があった。

あまり自信がなかったので翌日WEBで検索していたら、自分の回答が間違っている、と愕然とした。

公開鍵暗号方式とは、暗号通信をおこなうときの受信者が、暗号化するデータを送信する相手に対し公開鍵を送付し、送信者はその公開鍵を用いてデータを暗号化して送信し、受信者は秘密鍵でそれを復号する方式である。

昨日の試験で、私は「秘密鍵は公開できないからそれで復号できない」と考えたことを覚えていたのだが、公開鍵で復号できるのなら暗号化の意味がないではないか!

しまった!

しかも、同じような問題が2回出ていて同じように考えた。

これを全部間違えていたとしたら、もうアウトだ。

というか、そもそもこんな単純なことを理解していないようではたとえ合格してもセキュリティの専門家を名乗ることはできない...

検索すると「秘密鍵で暗号化するという間違った解説が蔓延している」という記事がいくつも出てきた。

...が、さらによく調べると、「秘密鍵で暗号化(この言い方は微妙なのでカッコをつける)」する場合もあるらしかった。


そして、問題を見直してみると、

「xxxを用いて署名を作成」
「xxxを用いて署名を検証」

となっていた。

2か所ともに。


これは公開鍵暗号化ではなく、デジタル署名の話だ。
だから、署名を作成するのに用いるのは秘密鍵で、検証するのは公開鍵であっている。


でも私は、「公開鍵で復号(検証)できてしまったら意味がないのでは?」
という疑問を抱くこともなかったので、
たとえ合格していてもあまり胸をはって「俺は安全確保支援士だ」と言うことはできない。


デジタル署名において秘密鍵を用いて署名を作成することを、「秘密鍵で暗号化」というのは厳密には正しくないらしいが、
よくわからない。
私が信頼していてよく参考にしているWEBサイトでもそういう言い方がされている。

https://www.ipa.go.jp/security/pki/024.html

IPAのデジタル署名を説明したサイトにも、「生成したダイジェストを自分の秘密鍵で暗号化します。」という記述がある。



公開鍵、秘密鍵、証明書、署名...

これらについての説明は何度も何度も聞いた(読んだ)ことがある。
が、何度聞いても、当たり前すぎるような、なんとも腑に落ちない感じがあった。

暗号化というのは他人に内容を知られないように、何かを書き換えることである。
そして、当然、それは読むことを期待する相手には解読(復号)できなければならない。

私たちがよくやるのは、データを暗号化し、パスワードをかけることだ。
そして、そのパスワードを相手に伝える。

メールの添付ファイルをパスワード付きzipで圧縮して、
後からそのzipファイルの解凍パスワードを送信する、というのはよく見る光景である。
私もよくやる。

その際によく言われるのが、「ファイルを添付したメールにパスワードを記載するな」ということである。

それは宛先を誤った場合に他者にファイルを解凍されてしまうからだ。


この方式は、方式というほどのことではないくらいの簡単な方式だが、「共通鍵暗号」方式という。

私がそうなのだが、暗号化なんてこれしか方法がないんじゃないかと考える人が多いのではないだろうか。

しかし、これもすぐ気づくことであるが、共通鍵暗号方式では共通鍵を他者に知られないように、通信相手に渡す必要がある。


解読するための鍵をさらに暗号化しても、やっぱりそれを復号する鍵が必要になり、
何重に鍵をかけようと、この課題はついて回る。

その課題を解消するのが公開鍵暗号方式である。


公開鍵暗号方式では、暗号化する鍵と復号化する鍵をペアで用意する。

このとき重要なのは、二つの鍵はそれぞれ暗号専用、復号専用であるだけでなく、
当然かもしれないが、ペアになっている、ということである。

つまり、Aさんの公開鍵で暗号化したデータを、Bさんの秘密鍵では復号できない。

そしてもう一つ、復号化する鍵を他者に渡してならないことは誰にでもわかるだろう。

だがそれと同じくらい重要なのが、暗号化するために、データの送信者に対して、
受信者が公開鍵を渡さなければならないということである。

そして、そのためにさらに次の重要事項が発生する。
それは、公開鍵の内容は他者に知られてもかまわないが、
その公開鍵が暗号化したデータを送信したい相手の鍵であるかを確認しなければならない、
ということである。


物理的な鍵でたとえたいのだが、「開けるときと閉めるときで違うカギを使う」
という実例はないだろうか?

よく、南京錠の絵を使って、閉めることだけができる鍵、開けることだけができる鍵、
などと説明されるが、そんな物理的鍵はこの世に存在しない...

と思って検索してみたら、「投函できるが自分は鍵をもっていないポスト」と言っている人がいた。

なるほど、これに似ている。


誰でもポストに郵便物を投函できるが、ポストを開けることはできない。
しかし郵便物は相手に、相手だけに届かなければならない。

我々がポストに郵便物を投函するのは、それが郵便ポストであり、
それを開けて配達する郵便配達員を信頼しているからである。

公開鍵を送るというのは、ポストを設置するような行為だ。

我々はポストがポストであることを、真っ赤であるとか「ポスト」と書いてあるかとかいう程度でしか確かめないが、

電子的に暗号通信、つまり鍵(ポスト)と暗号文(郵便物)を交換する際には、第三者に証明するという方法を使っている。


その一つが、HTTPS通信で使用されるサーバ証明書である。

我々はサーバ証明書を見てそれが信頼できる発行者が発行したものであることを確認してアクセスする。
(実際にはブラウザが証明書の正当性を検証し我々はその結果を信頼している)

HTTPサイトにアクセスすると提示される証明書には公開鍵が含まれている。
アクセスする者は公開鍵を受け取り、それを使ってデータを暗号化して通信する。

そして、送信先のHTTPSサイトだけが、その暗号化したデータを復号できる。



2019/04/27

HTTP通信が安全でない理由


前回のエントリ

で実行したcgiが、もしHTTP通信で実行されていたら、
その通信をキャプチャすると下記のように入力したパスワードがわかる。


これが「暗号化されていない通信(HTTP)は危険」である理由である。

「キャプチャすると通信内容がわかる」ということは、その通信が経由する装置、回線、サーバ、等にアクセスできる人々すべてに内容を把握されるおそれがあるということである。

これを防ぐためには、「暗号化」が必要である。

暗号化していない通信であれば、パスワードそのものでなくハッシュを保存していたとしても、サーバ管理者もユーザが入力したパスワードを知ることができてしまう。


「パスワードを暗号化して保存する」ではダメな理由

perlでcgiを作ってみた。

ハッシュアルゴリズムにmd5を使っているが、
md5もはや安全ではないらしい。

このスクリプトはあくまでもハッシュを作ることを実感するためのサンプルである。
実際にはより安全なアルゴリズムに変える必要がある。



ユーザ名とパスワードを登録するcgi

register_pwd.cgi
---
#!/usr/bin/perl

print "Content-type: text/html\n\n";
print "register password";
print '<form action="save_pwd.cgi" method="post">';
print '<p>';
print 'name:<input type="text" name="username" size="20">';
print '</p>';
print '<p>';
print 'password:<input type="text" name="password" size="20">';
print '</p>';
print '<p>';
print '<input type="submit" value="送信"><input type="reset" value="リセット">';
print '</p>';
print '</form>';
---


ユーザ名とパスワードのソルト付きハッシュを保存するcgi

users.txt というファイルに保存する。
すでに同じユーザ名が登録されているかのチェックもする。

ソルトの作り方がアレかもしれないが、
ここもサンプルなので見逃してほしい。


save_pwd.cgi
---
#!/usr/bin/perl

use Digest::MD5 qw(md5 md5_hex);

if ($ENV{'REQUEST_METHOD'} eq 'POST') {
  read(STDIN, $alldata, $ENV{'CONTENT_LENGTH'});
} else {
  $alldata = $ENV{'QUERY_STRING'};
}
foreach $data (split(/&/, $alldata)) {
  ($key, $value) = split(/=/, $data);

  $value =~ s/\+/ /g;
  $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack('C', hex($1))/eg;
  $value =~ s/\t//g;

  $in{"$key"} = $value;
}

print "Content-Type: text/html; charset=Shift_JIS\n\n";
print "<html>\n";
print "<head><title>save password</title></head>\n";
print "<body>\n";

$my_user = $in{'username'};
$my_pwd = $in{'password'};

print "<p>username: $my_user</p>\n";
print "<p>password: $my_pwd</p>\n";

$digest = md5_hex($my_pwd);

print "md5 digest:".$digest."<br>";

$num = int(rand(1000000));

$salt = '$1$'.$num.'$';

print '<br>'.$salt.'<br>';

$crypt = crypt($my_pwd, $salt);

print "md5 with salt: ".$crypt."<br>";

print '<br>';

$count = 0;

open (IN, "users.txt");
while(<IN>){
        @tmp_user = split(/,/,$_);
        if (@tmp_user[0] eq $my_user){
                $count++;
        }
}

if ($count > 0 ) {
        print("username ".$my_user." has been already registered.<br>");
}else {
        open (OUT,">> users.txt");
        print OUT $my_user.",".$crypt."\n";
        close (OUT);
        print 'registered<br>';
        print '<a href="/index.html">'.'index.html'.'</a>';
}
print "</body>\n";
print "</html>\n";

exit;
---

パスワード確認cgi
(ログインのイメージ)

kensho_pwd.cgi
---
#!/usr/bin/perl

print "Content-type: text/html\n\n";
print "verify password";
print '<form action="hash_kensho.cgi" method="post">';
print '<p>';
print 'name:<input type="text" name="username" size="20">';
print '</p>';
print '<p>';
print 'password:<input type="text" name="password" size="20">';
print '</p>';
print '<p>';
print '<input type="submit" value="送信"><input type="reset" value="リセット">';
print '</p>';
print '</form>';

exit;
---

ユーザの入力したユーザ名を保存したユーザ名から検索し、
保存されているソルトを付加してハッシュを計算し、保存されているハッシュと比較する

users.txtを読んで、同じユーザ名を探す。

実際はDBを使うなどするのだろうがこれは実験なので。

---
#!/usr/bin/perl

use Digest::MD5 qw(md5 md5_hex);

if ($ENV{'REQUEST_METHOD'} eq 'POST') {
  read(STDIN, $alldata, $ENV{'CONTENT_LENGTH'});
} else {
  $alldata = $ENV{'QUERY_STRING'};
}
foreach $data (split(/&/, $alldata)) {
  ($key, $value) = split(/=/, $data);

  $value =~ s/\+/ /g;
  $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack('C', hex($1))/eg;
  $value =~ s/\t//g;

  $in{"$key"} = $value;
}

print "Content-Type: text/html; charset=utf-8\n\n";
print "<html>\n";
print "<head><title>password kensho</title></head>\n";
print "<body>\n";

$my_user = $in{'username'};
$my_pwd = $in{'password'};

print "username: $my_user<br>";
print "password: $my_pwd<br>";

open (IN,"users.txt");
#$record = <IN>;

$count=0;
$tmp_user="";
$tmp_digest="";

while(<IN>){
        @tmp = split(/,/,$_);
        if(@tmp[0] eq $my_user){
                $count++;
                $tmp_user = @tmp[0];
                $tmp_digest = @tmp[1];
        }
}
close (IN);

if($count<1){
        print "unknown username<br>";
        exit;
}

chomp($tmp_digest);


$salt = substr($tmp_digest,0,10);

$crypt = crypt($my_pwd, $salt);

print "md5 with salt: ".$crypt."<br>";
print "saved md5    : ".$tmp_digest."<br>";

if ($tmp_digest eq $crypt ) {
        print "Welcome, ".$my_user." !!<br>";
}else{
        print "Incorrect password<br>";
}

print "</body>\n";
print "</html>\n";

exit;
---

実行例

ユーザ名とパスワードを入力

保存されたソルト付きハッシュ
上に表示されているのは、ソルトなしのハッシュ
$で挟まれているのがソルト。
ハッシュの先頭に付加されるのでそれを含めて保存する。


登録したユーザ名とパスワードを入力する


保存してあるハッシュと一致した。


違うユーザ名で同じパスワードを登録してみる。

jiro, password



ソルトなしのハッシュは、taro, passwordの場合と全く同じ

ソルト付きのハッシュは、同じパスワードから生成したものであっても、
taroのものと異なる。





「暗号化して保存する」と「ハッシュを保存する」は違う。

暗号化したデータは、しかるべき操作をすると元のデータが復号できるが、
ハッシュはできない。

もし悪意あるものがサーバにアクセスしてハッシュを入手したとしても、
できることは、元のデータを推測して、
そのデータのハッシュを計算して比較し、同じであるかどうかを確認することだけである。

ハッシュでできることは、元のデータを復元してそれを入力された値と比較することではない。
比較するのはあくまでもハッシュであり、
サーバ管理者でさえも、ユーザのパスワードが何かはわからないのである。
もしパスワードを暗号化して保存し、それを復号化して比較するのであれば、
サーバ管理者がユーザのパスワードを知ってしまうことになる。

また、もし暗号化されたパスワードを保存しているサーバにアクセスされてしまったら、
高確率で復号化する仕組みにもアクセスされるだろう。

そのことを知らなかったら、「暗号化して保存したってどうせ復号するんだから意味はない」と考えて、平文で保存してしまうのではないだろうか?

だから、「暗号化して保存する」と「ハッシュを保存する」の区別は重要なのである。


・・・と思ったのだが、記事を一回公開してから気づいた。
ハッシュを計算するときに、見ようと思えばユーザのパスワードは知ることができる。

先ほどのperlのスクリプトで、ユーザがフォームに入力したパスワードをprintで表示すればよい。
(実際、している。もちろん、本当に実装するときにそんなことはしないが。)

フォームにパスワードを入力してサーバに送信する前に、ハッシュを計算して、その結果を送信しなければいけないのか?

そんなことは不可能か?

通信が暗号化されていればサーバに到達するまではパスワードは他者に知られないが、

ハッシュ計算時には知ることができる。

それを保存さえしなければいいのか?


またこの時、もしソルトを付加せずに計算したハッシュを保存したらどうなるか。

1万人のユーザのハッシュの中で、同じものが50件あったとすると、
そのパスワードは誰でも思いつくパスワードであると想像でき、元のデータの推測が容易になってしまう。

2019/04/20

パスワードを平文で保存してはいけないならどうやって保存するのか?

あるサービスが「パスワードを平文で保存していた」というニュースに対してある有名な人が「平文で保存するなんてありえない!」と怒っていた。

「平文で保存していた」というニュースは何度か見たことがある。

では、平文で保存してはならないのならば、どうやって保存すればいいのか?

そう聞いたら、多くの人がこう答えるだろう。

「暗号化して保存する」

私もそうだと思っていた。

しかしそれは間違いで、正しくは

「パスワードのハッシュを保存する」

であった。


そういうとおそらく「暗号化もハッシュも同じようなものだ、そんな細かいことはどうでもいい」という人も多いだろう。

でも、そういう、「細かいことはどうでもいい」が積み重なって、「平文でも大丈夫」になってしまうのではないだろうか?

だから、ちょっと「細かいこと」にこだわってみよう。

ハッシュとか、暗号化とか、sha1とかmd5とかいうものの存在は知っていた。

ソフトウェアなどをダウンロードしたときにmd5チェックサムがついていて、
ファイルが壊れていないかを確認することができる、とか、

WEBサーバは、sha1は古いアルゴリズムだから、sha256を使わないといけないとか。


しかし、実際に自分がサーバ管理者やサービス提供者になったとして、
顧客のパスワードを安全に保存するにはどうすればいいかと聞かれたら答えられない。

最近、当たり前のことなのだが、知ってちょっと驚いたことがある。

それは、ハッシュというものは同じ値に対してはだれが計算しても同じ結果になる、
ということだ。


windowsのコマンドプロンプト(またはパワーシェル)で、
ハッシュを計算することができる。

別に何かのアプリなどをインストールする必要はない。

a.txt

というファイルがある。
中身は

あいうえお(改行)

で、文字コードをutf-8で保存した。


PS C:\users\taro\desktop> certutil -hashfile a.txt
SHA1 ハッシュ (対象 a.txt):
471399aeda50586bdc982b558bfde1da642c0fb3
CertUtil: -hashfile コマンドは正常に完了しました。

PS C:\users\taro\desktop> certutil -hashfile a.txt md5
MD5 ハッシュ (対象 a.txt):
0f2f3458c1553eb96d499508dc5184b4
CertUtil: -hashfile コマンドは正常に完了しました。

PS C:\users\taro\desktop> certutil -hashfile a.txt sha256
SHA256 ハッシュ (対象 a.txt):
0007a58ae5789f92155f87d6bc51edd4c9b036277d23154fe06a9daf33d0e514
CertUtil: -hashfile コマンドは正常に完了しました。


sha1(デフォルト)、md5、sha256で計算してみた。


それでは、このファイルを、私がさくらのVPSで使っているcentosのサーバにアップロードし、そこでハッシュを計算してみよう。

centosにもハッシュを計算するコマンドがある。

# sha1sum a.txt
471399aeda50586bdc982b558bfde1da642c0fb3  a.txt

# md5sum a.txt
0f2f3458c1553eb96d499508dc5184b4  a.txt

# sha256sum a.txt
0007a58ae5789f92155f87d6bc51edd4c9b036277d23154fe06a9daf33d0e514  a.txt


結果は全く同じである。

ハッシュというのは元の値に対して、人の目からは無意味である程度長く、元の値を推測も計算することもできないような値を生成する。

それはわかっていた。

しかしもうひとつ肝心なことは、同じアルゴリズム(sha1とかmd5とか)を使用すれば、
同じ値からは同じハッシュ値が生成されるということだ。

そうでなければ、たとえばユーザのパスワードのハッシュ値を保存しても、
ユーザのパスワードが正しいかを検証できない。

当たり前のことであるが。


私もパソコン、スマホ、さまざまなインターネット上のサービスなどを利用していて、
「パスワード」というものを何十個、何百個と設定して使用してきたが、
そのパスワードがテキストで読める状態で保存されているのは見たことがない。
パスワードをメールや郵便物に記載して送付されることはあったが。

あらためて、先ほどのcentosのサーバにパスワードがどのように保存されているかを見てみた。

/etc/shadow

というファイルである。

私は複数のアカウントを作成しているが、そのうちいくつかは異なるアカウントに同じパスワードを設定していた。

たとえば、taroというユーザのパスワードが password、
jiroというユーザのパスワードも password
という感じである。

「同じ値のhash値は誰が計算しても同じ」
ということは、taroとjiroのパスワードから計算したhash値は同じ文字列のはずだ......

しかし、実際には違う文字列であった。

あれ?


調べると、パスワードを保存する際にはhash値をもとの文字列からそのまま計算せず、
「ソルト(塩)」と呼ばれるランダムな値を付加して計算するのだそうだ。

その付加する方法は、文字列としてくっつけるのか、二進数でなんらかの演算をするのかわからないが、とにかくもうひと手間加えて、同じパスワードでも同じハッシュにならないようにする。


同じハッシュ値だからといっても、元の値を知ることは事実上不可能なことにはかわりないが、「複数のアカウントが同じパスワードである」という事実があれば、それらのアカウントに使用されているパスワードは誰もが思いつきそうな単純なパスワードであると推測できる。

また、よく使われそうなパスワード文字列を用意し、そのハッシュ値を計算しておいて、
ハッシュ値同士を比較すれば元の文字列がわかる。(これをレインボーテーブルという)

これらを防ぐために、ソルトというものが使われる。

しかし私はそこでひとつ疑問がわいた。

パスワードは正しい値であることを検証しなければならない。


再訪したユーザが入力したパスワード値にソルトを付加してハッシュ値を計算し、保存されたハッシュ値と同じであるかを比較しなければならない。

ではソルトをハッシュ値と一緒に保存するのか。
それは鍵をかけた金庫の横に鍵を置くようなものだ。

/etc/shadowを見てももちろんソルト値は書かれていない...

調べてみた。

ソルト値の隠し場所があった。

金庫のたとえで言うなら、鍵は金庫の横に置かれてこそいないが、
横にテープで貼り付けるくらいのことしかされていなかった。

ソルト値を使ってパスワードからhashを計算したら、
/etc/shadowに書かれているhash値と一致した。

# cat /etc/shadow | grep hiroshi
hiroshi:$6$9Qpwui9R$jrswaF/U2NRgq7Yhuk2YH7T2POuOYuDq/2eH71KMJkRDNaLxHEa25BLvmAnT3zz.ijxv/e6qUK.cEN24FN.GD0:18005:0:99999:7:::

# perl -e 'print crypt("hiroshidesu", "\$6\$9Qpwui9R");'
$6$9Qpwui9R$jrswaF/U2NRgq7Yhuk2YH7T2POuOYuDq/2eH71KMJkRDNaLxHEa25BLvmAnT3zz.ijxv/e6qUK.cEN24FN.GD0


pythonの例

# python -c 'import crypt; print(crypt.crypt("hiroshidesu", salt="$6$9Qpwui9R"))'
$6$9Qpwui9R$jrswaF/U2NRgq7Yhuk2YH7T2POuOYuDq/2eH71KMJkRDNaLxHEa25BLvmAnT3zz.ijxv/e6qUK.cEN24FN.GD0


そもそも、ハッシュ値で保存するのも、さらにその際にソルトを加えるのも、
サーバにアクセスされてかつサーバの管理アカウントでログインされてしまった場合を想定してのことである。

平文で保存していたのも、サーバにログインされないための対策を十分にしているから、
と考えてのことだろう。


sha512の計算方法とか、saltのかけ方とかはわからないが、
それを使ってますよ、というだけで、パスワードの保存方法というのはそんなに難しいことをしているわけではないというのが、分かった。

ただし、繰り返しになるが、それはあくまでも推測したパスワードからハッシュを計算して一致するかを調べる、ということであって、ハッシュ計算の脆弱性があるとか、ハッシュ値から元の値が算出できるとかいうことではない。


※関連エントリ

https://monqy.blogspot.com/2019/04/blog-post_27.html

2018/10/22

偏差値

ネットワークスペシャリスト試験を受けてきた。

全力は尽くした。


さて、午前1に以下のような問題が出た。

どうやって解くのかわからず、なんとなく答えを選んだ。

---
受験者1000人の4教科のテスト結果は表のとおりであり、いずれの教科の得点分布も正規分布に従っていたとする。90点以上の得点者が最も多かったと推定できる教科はどれか。

教科 平均点 標準偏差
-------------------------
A    45   18
B    60   15
C    70    8
D    75      5
---

昼休みにiPhoneでそれらしきキーワードで検索してみたが
過去には出題されていないようだった。

この問題は3問めだったが、ちょっと読んでわからないので飛ばし、
解ける問題を解いた後でじっくり考えてみた。

90点以上の得点者が最も多いということは平均点が高い教科になりそうに思えるが、
問題は標準偏差である。

もし、標準偏差が示されず、平均点だけを示されたら、Dを選ぶだろう。

そんな単純な問題でないことはわかる。

標準偏差の算出方法は覚えていないが、何を意味するかはわかる。
少し前に自分なりに整理したこともある。

標準偏差が大きいほど、得点のばらつきが大きい。

Dは5でもっとも小さく、Aは18で最も大きい。

Dの得点分布は縦に細長く、Aは横に広くなる。

それはわかるが、「90点以上の得点者が最も多い」ということをどうやって知るのか。


午前1に出るような問題なのでそんなに複雑な計算が必要ではないはずだ。

AとDは両極端なのでたぶん正解ではないだろう。

BかCか。15という標準偏差はかなり大きい、と、Bを選んだ。



昼休みに検索した結果、以下のような解き方でとりあえず正解を出せることがわかった。

偏差値は、「平均点との差に10をかけ標準偏差で割って50を足す」ことで算出できる。
(得点が平均点より高い場合)

90点をとった場合の偏差値を求めてみる。

A
(90-45)*10/18 + 50 = 75

B
(90-60)*10/15 + 50 = 70

C
(90-70)*10/8 + 50 = 75

D
(90-75)*10/5 + 50 = 80


同じ90点でも、教科Dの90点は偏差値が高い。
「偏差値80」はなかなか出るものではない。
そして教科Bの偏差値が最も低い。

この結果からなぜ「90点以上の得点者が最も多い」と言えるのかが
正確に説明できないのだが、

90点をとったときの偏差値が高いということは、
それだけその得点がまれである、
つまり、90点をとった人が少ないということを示す。

正規分布の正確な定義は知らないのだが、
確か中央が高くなって両端がだんだん低くなる分布である。
つまり、90点の偏差値の高低によって、それ以上の得点者の多寡が判断できる、
ということだと思う。

というわけで、問題で問われている90点以上の得点者が最も多いのは、
90点の場合の偏差値が最も低いBである。

その筋の情報によっても、正解はBだった。

というわけで、私がなんとなく選んだBは正解であった。

2013/02/09

セッション管理の必要性

さて、ログインの仕組みを作ってみたいと考えている。

パスワードを画面上で隠す方法はわかった。

次は、それを送信する時に暗号化すればいいのだろうと考えた。

最初はperlのcrypt関数を使おうとしたが、8文字までしかチェックできないので、Digest::MD5を使うことにした。

かんたんなテストプログラムで動作を確認してさあCGIで使おうと思ったところで、止まった。

postというのは、入力した値を処理するスクリプトを指定するだけであり、

入力された値を加工してから、つまり暗号化してから、渡すことができない。

ブラウザ上では見えないが、パケットキャプチャをすると当然平文パスワードが丸見えである。

このパスワードを認証するcgiに渡してからmd5ハッシュを作ってもしょうがない。

だがどう考えても、通信自体が暗号化されていなければパスワードを暗号化して渡すことはできない。

さらっと検索しても少なくともperlでcgiを書くだけではできないようだ。

だからみんなhttpsを使っているというわけなのか。

ただ私はほんのお遊びのサイトなので、簡易的なパスワードでかまわない。

どうせ見えるのなら、平文でやりとりするかな。

apacheのベーシック認証で、アクセス制限することはできる。

が、そもそもやりたいのはアクセス制限ではなく、カスタマイズされたページの表示である。

今はどのサイトでもログインしてそのユーザ用のページを表示する。amazon, twitter, google, yahoo... なんでもそうだ。

そのためにはセッション管理が必要である。