先日、日経XTECHから「ハッシュ化したから安全」と主張するのをそろそろやめようかという記事が出て物議を醸していますが、今回はハッシュ化の安全性について考えてみたいと思います。
はじめに
暗号化や認証において、「絶対に安全」なものは存在しません。
例を挙げてみましょう。3桁のダイヤル錠がよく販売されていますが、これが安全ではないということは直感的にわかると思います。
3桁のダイヤル錠の組み合わせは僅か1000通りですから、試行を繰り返せば最悪でも1000回目には必ず解錠できてしまいます。
この桁数が増えていっても、安全性は高まるものの、「自分以外が絶対に解錠できない」と言い切ることはできません。情報セキュリティにおいても同じことが言えます。
どんなに強固なセキュリティだとしても、それは「途方もない桁数を持つダイヤル錠」なのです。無限に時間があれば突破されるのは確実なのですが、時間が有限であることを根拠に、安全だと言えるわけです。
これは平文のパスワード認証に限らず、あらゆる認証方法で同じことが言えます。公開鍵認証は当然のこと、指紋や虹彩などの生体認証でもそうです。
あらゆるセキュリティは、コストとの兼ね合いでの相対的な程度問題といえます。セキュリティに絶対安全は存在しませんが、絶対安全に限りなく近付けることはできます。その方法論のうち、とりわけポピュラーな手法であるハッシュ化について考えてみましょう。
ハッシュ化とは
文字列などを、不可逆の別の文字列に変換する方法のひとつで、変換元の文字列が同一なら、変換後のハッシュも同一になるという性質(冪等性)があります。
「元の文字列を復元出来ないんだから意味がないじゃないか」と思うかもしれませんが、上述した性質により、元の文字列を復元する必要のない、パスワード認証に使われることが多いです。
ハッシュは不可逆であるという特性があるため、ハッシュ化を元に戻すような計算方法やツールは存在しません。
何故ハッシュ化が必要なのか
ハッシュ化は、たとえば、ブルートフォースやディクショナリーアタックなどの攻撃手法に有効な対抗策とはなりません。つまり、ハッシュ化をしたところでシステムそのもののセキュリティが向上するわけではない、ということに注意が必要です。
では、何故ハッシュ化の必要性がこれほど説かれているのでしょうか。
情報セキュリティでは、全てのリスクは「起こるもの」として扱います。すなわち、ログインIDおよびパスワードが書き込まれたテーブルが全て流出するものとして考えたとき、生のパスワードが流出してしまっては、サービスへの不正ログインを防ぐことはできません。
また、仮に、すぐにデータベースの流出を検知してサービスを停止できたとしても、顧客の情報を大きな危険にさらすことになります。パスワードを使い回している人も多く、他のサービスに不正ログインされ、犯罪などの被害に遭うこともあるでしょう。
このリスクは、大抵の場合回避しなければならないリスクです。そのために、一部のフレームワークでは、パスワードをハッシュ化して保存するような関数が用意されています。
ハッシュの危険性
レインボーテーブル
記事中でレインボーテーブルの手法が取り上げられていましたが、これはハッシュ化された文字列に対して、確かに有効な手段のひとつです。
レインボーテーブルとは、ハッシュ化された値を予め計算しておき、高速なデータベースに格納しておくことで、ハッシュから生のパスワードを瞬時に割り出す手法です。
仮に、闇雲に計算すると1年かかるようなパスワードであったとしても、1年前から事前計算を続けていれば、瞬時にパスワードが判明します。パスワードの流出が判明してからパスワード変更などの対策をされる前に、悪意のある行動を行うことが出来るというわけです。
衝突
ハッシュは、その性質上、衝突する危険が常にあります。JavaScriptで簡単なハッシュ関数を実装してみましょう。なお、このコードでは説明を分かり易くするため、入力値は数値とします。
const SIZE = 10;
function hash(src){
return src % SIZE;
}
このコードは、与えられた数値をサイズで割った余りを返すだけのシンプルなものです。実は、このコードがハッシュ関数の最も簡易な実装なのです。実は、我々が普段よく使うハッシュ関数は、この構造を充分に複雑化しただけにすぎません。
上述のハッシュ関数では、例えば15と25、あるいは105など、下一桁が合っていれば同じハッシュ値が返却されます。同じことが、世に浸透しているハッシュアルゴリズムにも言えるわけです。
対策
ソルト
レインボーテーブルに対しては、いくつかの対抗策が生み出されてきました。中でも有名かつ有効なものが、「ソルト」と呼ばれる、ハッシュ化前の文字列に対してランダムな文字列を付加する方法です。
しかも、ソルトそのものは、ハッシュ化された文字列と、大抵の場合は同じ場所に平文で保存されます。つまり、パスワードが流出した場合、ソルトも同時に流出するわけです。問題ではないのでしょうか?
結論から言うと、ソルトが同時に流出したところで、元の文字列を復元する何の手掛かりにもなりません。レインボーテーブルでは、ソルトが無い状態のハッシュを予め計算しておくので、ソルトが付加された文字列は、また最初から計算し直す必要があります。これでは、レインボーテーブル手法を使っていないのと同じですから、充分な効果を発揮したと言えます。
それでも、ソルト込みの文字列を予め計算しておく、という力業が無いわけでもありません。が、仮にソルトがアルファベット1文字だとしても、膨大な組み合わせの考えられるパスワードに、アルファベット文字数分、つまり26を掛けたパターンの計算が必要ですし、ソルトがアルファベット1文字であるという保証もありませんから、それほど有効であるとは言えません。
ストレッチング
ハッシュ関数を複数回適用することをストレッチングと呼びます。ストレッチングは、レインボーテーブル的手法を用いた攻撃に対する有効な手段です。複数回ハッシュ関数を適用することによって、元の文字列を分かりづらくします。
ただ、ソルトを用いた方法に比べると、変数はハッシュ関数を適用する回数のみになるため、対策としては単調と言わざるを得ません。上述したような、ソルトと組み合わせて使うことで、より効果を発揮する手法といえます。
ハッシュ関数は、システムごとにオリジナルのアルゴリズムを用いるのがベターです。例えば、ソルトを加えた文字列に複数回ハッシュ関数を適用することや、ハッシュ関数を適用した文字列にソルトを加えてもう一度ハッシュ関数を適用するなどすることで、複雑性を上げることが可能です。
衝突の対策
通常、余程脆弱なハッシュアルゴリズムを使用していない限り、衝突に対して過度な心配をする必要はありません。
有名なハッシュアルゴリズムである、md5(ファイルの正当性チェックに使われることが多い)は、衝突耐性に脆弱性があることがすでに知られています。仮にmd5のハッシュが流出した場合、元の文字列を復元することはできないものの、同一のハッシュを生成する文字列を得ることが容易なので、流出の判明から対策を打つまでの僅かな時間でサービスへの攻撃が成立することになります。
現在主流なのはSHA-256ですが、md5の例のように、脆弱性が発見されないとも限らないため、常に脆弱性情報に気を配りながら、すぐに対応できる体制を整えておくのが大切です。
まとめ
「ハッシュ化したから絶対安全」ということはありませんが、ソルトなどの手法を利用して正しくハッシュ化されていれば、元の文字列を割り出すことは極めて困難です。
コストもかからず、すぐに出来ることが強味なので、パスワードなどを格納する際は、必ずハッシュ化することをお勧めします。