+ 作る
図を読み込んでいます…
1 / 13
図でつながる 認証と暗号の基礎 / 全11章

ハッシュ関数 ― 戻せない指紋で、改ざんを見つけパスワードを守る

発行
かえで
版
第2版
改訂
分量
13コマ・5,291字

どんな入力からも決まった長さの値を作り、元には戻せないハッシュ関数。ファイルの改ざんの検出と、パスワードの保管(ソルトと、わざと遅い関数)まで、図の動きで追います。

パスワードをどう覚えておくか

前の章では、ログインのときにサーバーがパスワードを確かめて、本人かどうかを判断する流れを見ました。確かめるためには、サーバーは比べる相手を何か覚えておかなければなりません。いちばん素直なのは、登録のときに受け取ったパスワードをそのままデータベースに書いておき、ログインのたびに届いた値と見比べるやり方です。

図の右側が、そのやり方で作ったサーバーの記録です。アオイさん(aoi)の行には「password」がそのまま書かれています。ログインの確認だけを考えれば、これで問題なく動きます。

ところが、この表そのものが外に出てしまったらどうなるでしょうか。この章では、その心配から出発して、ハッシュ関数という道具を見ていきます。

  • パスワードをどう覚えておくか
  • データベース
  • 保存している記録
  • 人
  • サーバー
  • aoi : password
  • ID とパスワード
  • bob : Tk#9vq!2mZ
  • アオイ
  • サーバー
  • ken : password
  • ログインのたびに、届いたパスワードと記録を見比べる

そのまま保存すると、漏れた瞬間に全員分が読める

データベースの中身が外に出る事故は、残念ながら珍しくありません。設定の誤り、バックアップファイルの置き忘れ、ソフトウェアの弱点を突いた侵入など、きっかけはさまざまです。図では、盗み見る人の手に記録がまるごと渡り、赤くなった行がすべて読める状態になっています。

パスワードをそのまま保存していると、漏れた瞬間に全員のパスワードが相手に知られます。しかも多くの人は同じパスワードを別のサービスでも使い回しているので、被害はこのサービスだけで終わりません。

求めたいのは「ログインの確認はできるのに、記録が漏れてもパスワードそのものは分からない」保存の仕方です。それを支えるのがハッシュ関数です。

  • そのまま保存すると、漏れた瞬間に全員分が読める
  • データベース
  • 保存している記録
  • 人
  • サーバー
  • aoi : password
  • bob : Tk#9vq!2mZ
  • アオイ
  • サーバー
  • ken : password
  • 注意
  • 人
  • まるごと流出
  • 盗み見る人
  • 全員のパスワードが、そのまま読めてしまう

ハッシュ関数は、決まった長さの値を作る

ハッシュ関数は、どんな長さの入力を受け取っても、決まった長さの短い値を返す関数です。返ってくる値を ハッシュ値、または ダイジェスト(要約という意味)と呼びます。図では、3文字の「abc」も、8文字の「password」も、「a」を100万個並べた長い入力も、同じ大きさのカードになって出てきます。

ここで使っているのは、いま最もよく使われている SHA-256 です。名前の 256 は、出てくる値が 256 ビット、16進数で 64 桁であることを表します。図には先頭の 16 桁だけを載せていますが、どれも実際の SHA-256 の値です。仕様は NIST の FIPS 180-4 にまとまっています。

ハッシュ値はよく 指紋 にたとえられます。人の指紋が本人を見分ける手がかりになるように、長いデータから取り出した短い値を、データを見分ける手がかりに使います。

  • ハッシュ関数は、決まった長さの値を作る
  • abc
  • ba7816bf8f01cfea…
  • ハッシュ 関数
  • password
  • 5e884898da280471…
  • SHA-256
  • a を100万個 aaaaaa……aaaa
  • cdc76e5c9914fb92…
  • 入力の長さに関係なく、値はいつも 64 桁(256 ビット)

同じ入力なら同じ値、1文字違えば全く別の値

ハッシュ関数には、指紋らしい性質が二つあります。一つ目は、同じ入力からは必ず同じ値が出ることです。図の上の2行はどちらも「password」を入れていて、出てきた値は1桁も違いません。いつ、どのコンピューターで計算しても同じになるので、あとから計算し直して比べることができます。

二つ目は、入力が少しでも違うと、出てくる値が全く違うものになることです。3行目は先頭の p を大文字の P に変えただけですが、値は「5e88…」から「e7cf…」へと、似ても似つかないものに変わりました。入力が似ていても値は似ないように作られているので、値を見比べて「入力はこのあたりだろう」と見当をつけることもできません。

この二つがそろうと、「値が同じなら、入力も同じだと考えてよい」という使い方ができます。この章の後半で見る改ざんの検出もパスワードの確認も、この使い方の上に成り立っています。

  • 同じ入力なら同じ値、1文字違えば全く別の値
  • password
  • 5e884898da280471…
  • 同じ
  • ハッシュ 関数
  • password
  • 5e884898da280471…
  • SHA-256
  • Password
  • e7cf3ef4f17c3999…
  • 別物
  • 大文字の P に変えただけで、値は似ても似つかない

ハッシュ値から元の入力へは戻せない

ハッシュ関数は一方向の計算です。「password」から「5e88…」を計算するのは一瞬ですが、「5e88…」から「password」を計算で求める方法はありません。図の赤い矢印のように、逆向きにたどる道が用意されていないのです。256 ビットの短い値に長い入力の情報が全部入っているわけではないので、元のデータを組み立て直す材料がそもそもありません。

ここで、ハッシュを暗号化と取り違えないようにしましょう。暗号化は、鍵を使って中身を読めない形に変え、鍵があれば元に戻せる仕組みです。ハッシュ関数には鍵がなく、元に戻す「復号」という手順もありません。誰が計算しても同じ値になり、誰にも戻せません。

「戻せないなら、ハッシュ値だけ取っておいても役に立たないのでは」と思うかもしれません。役に立つのは、元の入力を 持っている人 が計算し直して、取っておいた値と比べる場面です。

  • ハッシュ値から元の入力へは戻せない
  • ハッシュ 関数
  • password
  • 5e884898da280471…
  • SHA-256
  • 元へ戻す計算はない
  • 暗号化:鍵があれば戻せる
  • ハッシュ:鍵もなく、戻せない

安全なハッシュ関数に求める性質

暗号やセキュリティに使うハッシュ関数は 暗号学的ハッシュ関数 と呼ばれ、三つの性質を求められます。一つ目は、ハッシュ値から、その値になる入力を見つけられないこと(原像困難性)。二つ目は、ある入力が与えられたとき、同じ値になる別の入力を作れないこと(第二原像困難性)。三つ目は、どんな組み合わせでもよいので、同じ値になる二つの入力を見つけられないこと(衝突困難性)です。

出てくる値の長さは決まっているので、同じ値になる別の入力は理屈の上では必ずあります。ここでの「できない」は、今のコンピューターで現実的な時間をかけても見つからない、という意味です。

古い MD5 と SHA-1 は、三つ目の性質が破られました。MD5 では2004年に、SHA-1 では2017年に、同じ値になる異なる入力が実際に作られています。そのため、安全のための用途にはもう使いません。今の代表は、SHA-256 を含む SHA-2 の仲間と、仕組みから作り直された SHA-3(FIPS 202) です。

  • 安全なハッシュ関数に求める性質
  • 値から元の入力を 求められない
  • 同じ値になる 別の入力を作れない
  • 同じ値になる組を 見つけられない
  • 原像困難性
  • 第二原像困難性
  • 衝突困難性
  • 衝突が実際に作られた
  • 今の代表
  • MD5
  • SHA-1
  • SHA-2
  • SHA-3
  • 安全のための用途には使わない
  • SHA-256 は SHA-2 の一つ

使い道1:ファイルの改ざんを見つける

ハッシュ値の使い道として分かりやすいのが、ダウンロードしたファイルの確認です。ソフトウェアの配布元は、ファイルと一緒にその SHA-256 の値を公開していることがあります。ダウンロードしたファイルのハッシュ値を手元で計算し、公式の値と比べて一致すれば、途中で中身が変わっていないと分かります。

図の上の段では、手元で計算した値が公式の値と一致しました。下の段は、途中でどこか一部が書き換えられたファイルです。1文字違うだけで値が全く変わる性質のおかげで、わずかな書き換えでも値が一致しなくなり、すぐに気づけます。(図の値は説明のために短く縮めたものです。)

大きなファイル同士を1バイトずつ比べなくても、64 桁の値を比べるだけで済む点も便利です。ここで衝突困難性が効いてきます。同じ値になる別のファイルを作れるなら、悪いファイルを正しいものに見せかけられてしまうからです。

  • 使い道1:ファイルの改ざんを見つける
  • インターネット
  • 公開されている値
  • 公式 a3f9…c21e
  • 配布元
  • =
  • ファイル
  • ハッシュ関数
  • 手元 a3f9…c21e
  • OK
  • 一致:改ざんなし
  • app.zip
  • ファイル
  • ハッシュ関数
  • 手元 77d0…9b4a
  • NG
  • 不一致:使わない
  • 書き換え版

ハッシュ値ごと差し替えられると見抜けない

ただし、この確認には前提があります。比べる相手の「公式の値」が正しいことです。図では、書き換える人が配布元のファイルを差し替え、同時に公開されている値も、差し替えたファイルのハッシュ値に書き換えました。手元で計算すると、値はきちんと一致してしまいます。

ハッシュ関数には鍵がないので、誰でも、どんなファイルのハッシュ値でも計算できます。ファイルと値が同じ経路で届き、その経路ごと書き換えられるなら、ハッシュ値を比べても何も守れません。値だけは別の信頼できる経路から取る、という運用で補うことはできますが、仕組みとして解決するには「その値を作れたのは誰か」まで確かめる必要があります。

そのために使うのが、鍵を混ぜてハッシュ値のような値を作る MAC(次の章)と、秘密鍵で作る電子署名(第6章)です。どちらも、鍵を持たない人には正しい値を作れないようにする仕組みです。

  • ハッシュ値ごと差し替えられると見抜けない
  • インターネット
  • 人
  • 注意
  • 公開されている値
  • 公式 77d0…9b4a
  • 配布元
  • =
  • 書き換える人
  • ファイル
  • ハッシュ関数
  • 手元 77d0…9b4a
  • 注意
  • 一致してしまう
  • 書き換え版
  • 守るには
  • 鍵を使う MAC(次の章)
  • 電子署名(第6章)

使い道2:パスワードをハッシュ値で保管する

もう一つの使い道が、最初の問題だったパスワードの保管です。サーバーは登録のときにパスワードのハッシュ値を計算し、パスワードそのものではなくハッシュ値だけを記録します。図の右の記録には「password」の代わりに「5e88…」が並んでいます。

ログインのときは、アオイさんから届いたパスワードをその場でハッシュ関数にかけ、記録の値と比べます。同じ入力からは必ず同じ値が出るので、正しいパスワードなら一致します。サーバーはパスワードを保存していなくても、本人かどうかを確かめられるわけです。

この形なら、記録が漏れても相手の手に渡るのはハッシュ値だけで、そこから元のパスワードを計算で戻すことはできません。これで安心、と言いたいところですが、まだ抜け道があります。図の aoi と ken の行が同じ値になっていることに気づいたでしょうか。

  • 使い道2:パスワードをハッシュ値で保管する
  • データベース
  • 保存している記録
  • 人
  • サーバー
  • aoi : 5e884898da280471…
  • password
  • bob : 9e952ac9f7367487…
  • アオイ
  • サーバー
  • ken : 5e884898da280471…
  • 5e884898da280471…
  • 一致すればログイン成功
  • 記録には、パスワードそのものを置かない

よくあるパスワードは一覧で引かれる

計算で戻せないなら、攻撃者は逆の方向から攻めます。よく使われるパスワードを先に並べ、それぞれのハッシュ値をあらかじめ計算した一覧を作っておくのです。図の左がその一覧で、「123456」や「password」と、それぞれの SHA-256 の値が並んでいます。漏れた記録と一覧を突き合わせれば、値が一致した行のパスワードがそのまま分かります。

こうした一覧は 辞書 と呼ばれ、何億件もの候補を収めたものが出回っています。同じ考え方で、保存の仕方を工夫して大きな一覧を小さくまとめたものを レインボーテーブル といいます。どちらも「同じ入力からは必ず同じ値」という性質を逆手に取っています。

さらに、同じパスワードを使っている人はみんな同じ値になるので、図の aoi と ken は一度に見つかってしまいます。一方、bob のように一覧に載っていない長くて珍しいパスワードは、この方法では当たりません。

  • よくあるパスワードは一覧で引かれる
  • よくあるパスワードの一覧
  • データベース
  • 漏れた記録
  • 123456 → 8d969eef6ecad3c2…
  • aoi : 5e884898da280471…
  • password → 5e884898da280471…
  • bob : 9e952ac9f7367487…
  • qwerty → 65e84be33532fb78…
  • ……何億件も続く
  • ken : 5e884898da280471…
  • 人
  • 注意
  • aoi と ken は「password」と判明
  • bob は一覧になく、当たらない
  • 盗み見る人

ソルトで、同じパスワードも別の値に

この攻め方を崩すのが ソルト(塩)です。ソルトは、利用者ごとに作るランダムな値です。サーバーは登録のときにソルトを作り、ソルトとパスワードをつなげたものをハッシュ関数にかけて、ソルトとハッシュ値を並べて記録します。ログインのときは、記録してあるソルトを取り出して同じ計算をし、値を比べます。

図の記録では、aoi と ken のパスワードはどちらも「password」のままですが、ソルトが「x7Qp」と「M2k9」で違うため、記録の値は「c519…」と「ac68…」に分かれました(ソルトとパスワードをつなげた文字列の SHA-256 です)。あらかじめ作った一覧の「5e88…」とも一致しません。攻撃者は一覧を使い回せず、利用者一人ひとりについて、そのソルトで候補を計算し直さなければなりません。

ソルトは秘密にする値ではなく、ハッシュ値の隣にそのまま保存して構いません。役目は、同じパスワードから同じ値が出ることを防ぎ、前もって作った一覧を役に立たなくすることです。

  • ソルトで、同じパスワードも別の値に
  • よくあるパスワードの一覧
  • データベース
  • 名前 : ソルト : 値
  • 123456 → 8d969eef6ecad3c2…
  • aoi : x7Qp : c51926f5…
  • password → 5e884898da280471…
  • bob : Rz4w : 563a288c…
  • qwerty → 65e84be33532fb78…
  • ……何億件も続く
  • ken : M2k9 : ac68a2a3…
  • 人
  • 注意
  • 同じ password でも値は別々
  • 一覧の値とは一致しない
  • 盗み見る人
  • 記録する値 = ハッシュ(ソルト+パスワード)

パスワードには、わざと遅い関数を使う

ソルトを足しても、攻撃者が一人ずつ候補を試すこと自体は止められません。そこで効いてくるのが計算の速さです。SHA-256 はファイルの確認などに使うため速く計算できるように作られていて、高性能な GPU を使えば1秒に数十億回以上の計算ができます。よくあるパスワードなら、ソルト付きでもすぐに当てられてしまいます。

そこでパスワードの保管には、わざと計算を重くした パスワード用のハッシュ関数 を使います。図の上下の棒は、攻撃者が1秒に試せる回数のイメージです。正しい利用者が1回ログインするときの少しの待ち時間は気になりませんが、何億回も試したい攻撃者には大きな負担になります。Argon2id や scrypt は時間に加えて多くのメモリも使うので、GPU で大量に並べて計算するのも難しくなります。

OWASP のパスワード保管のチートシート は Argon2id(仕様は RFC 9106)を第一に勧め、使えなければ scrypt、古い仕組みでは bcrypt、FIPS-140 への準拠が必要なら PBKDF2 を挙げています。どれもソルトまで扱ってくれるライブラリがあるので、自分で組み立てずにそれを使いましょう。

  • パスワードには、わざと遅い関数を使う
  • 攻撃者が1秒に試せる回数(イメージ)
  • SHA-256
  • 速い:1秒に数十億回以上
  • Argon2id
  • 重い:1回ごとに時間とメモリを使う
  • パスワード用の関数(OWASP の推奨順)
  • Argon2id
  • scrypt
  • bcrypt
  • PBKDF2
  • ソルトまで扱うライブラリを使い、自分で組み立てない

まとめ:ハッシュ値を保存していれば、漏れても安全?

ハッシュ関数は、どんな長さの入力からも決まった長さの値を作り、同じ入力なら同じ値を、少しでも違えば全く別の値を返します。値から元の入力へは戻せず、鍵も復号もない点で暗号化とは別のものです。この性質を使って、ファイルの改ざんを見つけたり、パスワードそのものを持たずにログインを確かめたりできます。

では、パスワードのハッシュ値を保存していれば、漏れても安全でしょうか。答えは「それだけでは足りない」です。SHA-256 のような速いハッシュ関数でそのまま保存すると、よくあるパスワードは一覧との照合ですぐに分かり、同じパスワードの人はまとめて見つかります。利用者ごとのソルトを足し、Argon2id のようなわざと遅い関数を使って、ようやく「漏れても簡単には破られない」状態になります。それでも短く単純なパスワードはいずれ当てられるので、長く、使い回さないパスワードであることも欠かせません。

ハッシュは「戻せない」ことに価値がありました。次の章の共通鍵暗号は反対に、鍵を使って中身を隠し、同じ鍵があれば元に戻せる仕組みです。

  • まとめ:ハッシュ値を保存していれば、漏れても安全?
  • ハッシュ関数
  • 共通鍵暗号(次の章)
  • 鍵
  • password
  • 5e884898…
  • 会議は10時
  • 暗号文
  • 戻せない
  • 鍵
  • 鍵があれば戻せる
  • 改ざんの検出 パスワードの保管
  • 中身を隠して届け、 受け取った側で戻す
  • ハッシュは戻せない、暗号化は鍵があれば戻せる

0 件のコメント

読み込み中…

権利侵害・不適切な内容の連絡: [email protected]