2022年10月17日
一方向ハッシュ関数とは
ハッシュとは「元のデータから特定のサイズの別データを、計算によって作ること」である。 計算に使う関数をハッシュ関数(hash function)、入力をメッセージ(message)、出力をハッシュ値(hash value)と呼ぶ。
windowsの場合、「CertUtil」コマンドで、ハッシュを試すことが出来る。
CertUtil -hashfile test.txt MD5
あらかじめ中身が「a」の一文字だけのファイルを用意しておいて、上記コマンドを走らせると以下のような反応が得られる。 「-hashfile」は使いたい機能の指定。 「MD5」は計算に使うハッシュ関数の種類を指定している。
MD5は1991年に作られた一方向ハッシュ関数で、強衝突性はすでに破られている。 つまり、同じハッシュ値を持つ2つのメッセージを作り出す攻撃が可能になっているので安全ではない。
MD5 ハッシュ (対象 test.txt):
0cc175b9c0f1b6a831c399e269772661
CertUtil: -hashfile コマンドは正常に完了しました。
「0cc175b9c0f1b6a831c399e269772661」の部分がハッシュ値で、たった一文字の「a」という入力から長いハッシュ値が計算された。 ハッシュ関数で計算されたハッシュ値は、一定の長さ(特定のサイズ)の値になる。MD5の場合は、128ビットと決まっている。
ビットは情報量の基本単位で、0か1かを表すことができ、こうした情報を表現するには2進数が適している。 MD5で計算されるハッシュ値は128ビットだから、2進数で表現すると128個の0か1が並ぶことになる。
これだと桁が多くなりすぎて、読み間違いや書き間違えをしやすいので人間にとっては都合が悪い。 だから、一般的な10進数に変換したくなるところだが、2進数と10進数はあまり相性が良くないのだ。そこで16進数が用いられる。
2進数 | 10進数 | 16進数 |
---|---|---|
0 | 0 | 0 |
1 | 1 | 1 |
10 | 2 | 2 |
11 | 3 | 3 |
100 | 4 | 4 |
101 | 5 | 5 |
110 | 6 | 6 |
111 | 7 | 7 |
1000 | 8 | 8 |
1001 | 9 | 9 |
1010 | 10 | a |
1011 | 11 | b |
1100 | 12 | c |
1101 | 13 | d |
1110 | 14 | e |
1111 | 15 | f |
10000 | 16 | 10 |
2進数において4桁となる部分は、16進数においては全て1桁となっている。 2進数の4桁は16進数の1桁で表せるのなら、書く時に必要なスペースは4分の1に減るはずだ。
MD5が計算したハッシュ値は128ビットで、2進数だと128桁になるところ、出力された「0cc175b9c0f1b6a831c399e269772661」は32桁で、ちょうど4分の1。 つまり、128ビットの出力が16進数で表現されている。
同様のコマンドをもう1度走らせてみると、同じハッシュ値が計算されることを確認できる。 これはハッシュ関数の重要な性質の1つで、「使うハッシュ関数が同じならば、同じ元のデータからは同じハッシュ値が作られる」ということである。 当たり前ではないかと思うかもしれないが、これがハッシュ関数の用途に決定的な影響を及ぼすのだ。
次にアルゴリズムを変えてハッシュ値を計算してみる。
CertUtil -hashfile test.txt SHA1
コマンドの末尾のアルゴリズムの指定を「MD5」から「SHA1」に変更すると、以下のような結果を得る。
SHA1 ハッシュ (対象 test.txt):
86f7e437faa5a7fce15d1ddcb9eaeaea377667b8
CertUtil: -hashfile コマンドは正常に完了しました。
出力されたハッシュ値は「86f7e437faa5a7fce15d1ddcb9eaeaea377667b8」で、ハッシュ関数の種類を変えたことで異なる結果を得た。 桁数も違っていることが分かる。これはSHA1が160ビットのハッシュ値を出力するためだ。桁数を数えてみると160の4分の1で40桁になっていることが分かる。
- 入力するデータを「a」1文字から文章に変えてもハッシュ値のサイズは変わらない
- 入力の文章の1文字だけを変えると、ハッシュ値は全く異なるものになる
このような特性を持つ関数は、ハッシュ関数の中でも特別なものとして暗号的ハッシュ関数(cryptographic hash functions)と呼んで区別する。 MD5やSHA1は暗号的ハッシュ関数である。
改ざん検知
元データのサイズが巨大なものであった場合、改ざんの痕跡を発見するのは至難の業である。 しかし、ハッシュ関数の特性を利用することで簡単に検知できる。
元データから1文字でも変更されていると、そこから計算されるハッシュ値は変更される前のデータから計算されたハッシュ値とは全く違ったものになる特性を利用して:
- あらかじめ元データのハッシュ値を計算して保存しておく
- あらためてはハッシュ値を計算する
- ハッシュ値が一致しなければ改ざんされている
暗号との違い
データを暗号化や圧縮した場合、当然だが元の状態に戻す方法が用意されている。 しかし、暗号的ハッシュ関数によって得られたハッシュ値はそうではない。
「0cc175b9c0f1b6a831c399e269772661」からは、どうやっても元の「a」という情報を逆算することはできないのだ。 この点が、暗号アルゴリズムと鍵さえ知っていれば、元のデータを復元できる暗号との決定的な違いである。
こういった特徴を持つハッシュ関数は一方向ハッシュ関数(one-way hash function)と呼ばれる。 元のデータに戻せないのは欠点ともなりうるが、これが活きる状況も多い。ブロックチェーンもこの特性を最大限に利用している。
シノニム
異なるデータをハッシュ関数の入力としたのに、同じハッシュ値を作り出してしまうことをシノニム(synonym)と言う。 大きいデータから固定サイズの小さなデータに変換するので、これは原理的に仕方ない。 しかし、ハッシュ関数をパスワードのチェックなどに使うなら、シノニムが発生する確率は極限まで下げなければならない。
不可逆性の高さやシノニムの発生確率が低さから長期にわたって使われ続けているハッシュ関数でも、脆弱性が発見されて使えなくなってしまうこともある。 SHA1が正にそれで、脆弱性が見つかって、利用を停止するよう勧告が行われた。