このブログを検索

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

2019/04/27

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

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/03/03

python3.6のCGIでGoogleニュースのヘッドラインを抽出するスクレイピング例


python3.6によるスクレイピング例

Googleニュースのヘッドラインのタイトルを抽出する。

beautifulsoupは使わず、正規表現でゴリゴリする。


-------
#!/usr/bin/python
# -*- coding: utf-8 -*-
import requests
import re
import sys
import cgi
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

my_title = 'what\'s hot'

def print_head(title):
    print('Content-type: text/html')
    print()
    print('<html><head>')
    print('<meta http-equiv="Content-Type" content="text/html; charset=utf-8">')
    print('<title>'+title+'</title>')
    print('</head>')

def print_tail():
    print('</html>')

print_head(my_title)

print('<body>')

res = requests.get('https://news.google.com/?hl=ja&gl=JP&ceid=JP%3Aja')
res.raise_for_status()

print("From Google News JP status:" + str(res.status_code) + "<br><br>")

tmp = re.sub('\n','',res.text)
tmp = re.sub('<a href','\n<a href',tmp)
tmp = re.sub('\/a>','/a>\n',tmp)
tmplist = tmp.split('\n')

result = [ s for s in tmplist if re.match('<a href=\"\.\/articles.*?\/a>', s) or ( 'すべての記事' in s ) ]

for i in result:
    if 'すべての記事' in i:
        break
    s = re.sub(r'<a href.*?<span >(.*?)<\/span><\/a>',r'\1',i)
    print(s+"<br>")

print('</body>')

print_tail()

------

苦労したところ

import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

これを入れないと、抽出した結果をprintするときに下記のようなエラーになる。

UnicodeEncodeError: 'ascii' codec can't encode characters in position 343-391: ordinal not in range(128)

googleニュースのヘッドラインだけを抽出したかったのだが、
htmlソースを見ても特定の条件がみつからない。

'<a href=\"\.\/articles.*?\/a>',

で抽出すれば見出しのリンクが取れるのだが、ヘッドライン以外もすべて取れてしまう。

「すべての記事」というキーワードが区切りになっているので、
それが出てくるまで、という条件で抽出した。


これができればどこからでもなんでも取れる....








2019/03/02

python3のhello world cgi

最小限のpython cgi

-----
#!/usr/bin/python
# -*- coding: utf-8 -*-

my_title = 'greeting from python'
my_message = 'hello, world'

###

def print_head(title):
    print('Content-type: text/html')
    print()
    print('<html><head>')
    print('<meta http-equiv="Content-Type" content="text/html; charset=utf-8">')
    print('<title>'+title+'</title>')
    print('</head>')

def print_body(message):
    print('<body>')
    print(message)
    print('</body>')

def print_tail():
    print('</html>')

###

print_head(my_title)

print_body(my_message)

print_tail()
-----


3系はprintにカッコをつけてつかう

インデントでスペースとタブを混在させてはいけない


2013/03/10

ログ解析

ボットを除いたアクセスを解析してみた。

サーチエンジンはGoogleが6割、yahooが3割、ezが8パーセント、bingが2パーセント。

実はこの4つしか数えていないのだが、私のサイトにはざっと見た感じこれ以外の検索エンジンからのアクセスはない。

ブラウザはIE9がトップで18%。次がAndroidのMobile Safari、つまりiPhoneでないスマホか。16%。firefoxが14%、Safariが13%。IE8が12%、Chromeが10%。MSIE10.0からのアクセスがあった。

Chromeが意外に少ない・・・。私はChromeを使っている。

IEはアドレスバー兼検索窓がたくさんタブを開くと小さくなってしまうのがイヤだ。

あとUIの雰囲気がイヤだ。だいぶ少なくなったがIEじゃないと使えないサイトがけっこうあるのでそのときだけ仕方なく使う。

OSはWin7が34%でトップ。次はXPとAndroidが同点でそれぞれ16%。iPhoneは9%。au携帯が8%、Mac OSが4.5%。Linux,BSDは全部あわせて1%くらい。

twitterなんかだとほとんどの人がマックを使ってるかのようにみんなマックマックと言っているのだが実際のシェアはそうでもないようだ。

ドメインはocnが12%でトップ。その次が spmode.ne.jpで、これはドコモである。ドコモの携帯かスマホでWifi経由じゃないやつか?11%あった。次がezwebで9%、次がpanda-worldつまりソフトバンクのiPhoneで6%。