ハッシュ化とは、あるデータを元に戻せない別のデータに変換する技術です。例えば、「password123」という文字列から「e10adc3949ba59abbe56e057f20f883e」のような、一見ランダムな文字列である「ハッシュ値」を生成します。同じ入力からは必ず同じハッシュ値が得られるのが特徴です。
これにより、システム管理者は元のパスワードを知なくても、ユーザーが入力したパスワードが正しいかについて、ハッシュ値が一致するかを検証することで確認できます。
ハッシュ値生成の仕組みについても少し触れておきましょう。ハッシュ値の計算プロセスは、主に以下の3ステップで構成されています。
・ステップ1:データのブロック分割
まず、入力されたデータを、アルゴリズムが処理できるように決まった大きさの「ブロック」に均等に分割します。長さが足りない場合は、決まったルールでデータを付け足し(パディング)、サイズを揃えます。
・ステップ2:連鎖的な圧縮計算
ここがハッシュ化の心臓部です。分割したブロックを、圧縮関数と呼ばれる計算ロジックを使い先頭から順番に処理していきます。圧縮関数は、データのブロックと「一つ前の計算結果」をもとに、ビットの入れ替えや数学的な足し算など、複雑な計算を行います。この計算結果が、次のブロックを処理する際の新たなインプットとなり、処理が連鎖していきます。この連鎖的な仕組みにより、入力データが1ビットでも違うと、最終結果が予測不能なほど大きく変わります。
・ステップ3:最終ハッシュ値の出力
すべてのブロックを処理し終えた最後の計算結果がハッシュ値です。SHA-256なら256ビットといった、アルゴリズムごとに決まった固定長のハッシュ値となります。
ハッシュ化が普及する以前、パスワードは平文のまま、あるいは容易に元に戻せる暗号化を施しただけで保存されているシステムも少なくありませんでした。
そんな中、1991年に暗号学の権威であるロナルド・リベスト氏によって開発されたのがMD5です。パスワードを元に戻せないハッシュ値に変換して保存するこの方法は、データベースが盗まれてもパスワードそのものは漏洩しない、画期的なセキュリティ向上策でした。
では、かつて広く使われたハッシュ関数「MD5」について、Pythonのコードで試してみましょう。
・ソースコード
・実行結果
ハッシュ化の登場は画期的でしたが、MD5には最大の弱点がありました。それは計算速度が速すぎたことです。
MD5は、現代のコンピュータなら、1秒間に何十億回ものハッシュ計算が可能です。そのため、よく使われるパスワードのハッシュ値を事前に計算してリスト化しておく「レインボーテーブル攻撃」が非常に有効になってしまいました。
攻撃者は盗んだハッシュ値とリストを照合するだけで、元のパスワードを瞬時に特定できてしまうのです。
同じパスワードからは同じハッシュ値が生成される。ならば、ハッシュ化する元の文字列をユーザーごとに変えてしまえばいい! という発想を実現するのがソルト(Salt)です。ソルトとは、パスワードに付加するユーザー固有のランダムな文字列のことです。
パスワード + ソルト → ハッシュ化
この方法により、たとえ2人のユーザーが同じ「password123」を使っていても、ソルトが違うため、保存されるハッシュ値は全くの別物になります。これが、レインボーテーブル攻撃への対策となるのです。
MD5より強力なハッシュ関数「SHA-256」とソルトを組み合わせてみましょう。
・ソースコード
・実行結果
ソルトはレインボーテーブル攻撃を無力化する、大きな進歩でした。しかし、もしデータベースそのものが漏洩した場合、攻撃者の手には、ユーザー名、ソルト、ハッシュ値の3点セットが渡ってしまいます。
そうなると攻撃者は、特定のユーザーに狙いを定め、そのユーザーのソルトを使って、ブルートフォース攻撃(総当たり攻撃)を仕掛けることができてしまいます。SHA-256のようなハッシュ関数は、この一対一の攻撃に対しては計算が速すぎた、というのが残された課題でした。
攻撃者の最大の武器が計算速度なら、それを奪ってしまえばいい。この逆転の発想から生まれたのが、パスワード専用に設計されたハッシュ関数です。代表的なものにbcryptがあります。
bcryptは、意図的に計算に時間がかかるように作られています。さらに、「コスト係数」を設定することで、その計算量を調整できるのが最大の特徴です。将来コンピュータが高速になっても、コスト係数を上げることで、ハッシュ化にかかる時間を一定に保ち、安全性を維持できるのです。
Pythonのbcryptライブラリを使ってみましょう。ソルトの生成も自動で行ってくれます。
・ソースコード
※bcryptはPythonに標準で含まれているライブラリではないため、初めて使用する際には「pip install bcrypt」コマンドで別途インストールを実行してください。
・出力結果
上記のコードで生成されたbcryptハッシュ値は、実行ごとに変わります。bcryptの出力結果には、以下の情報が含まれています。
・$2b$:bcryptのバージョン
・$12$:コスト係数(2の12乗回)
・z0hOldsQ8QsbfRHDXY5G0e:ソルト(22文字)
・ncq90… 3u:ハッシュ本体(31文字)
ハッシュ値自体が検証方法(アルゴリズム、コスト、ソルト)の情報を持っているため、この情報のみでパスワードの検証が完結します。bcryptや、さらに後発のArgon2といった「意図的に遅くする」パスワード専用ハッシュ関数を使うことが、現在のスタンダードとなっています。
パスワード保護の歴史は、攻撃者の「計算速度」との戦いの歴史だったといえるでしょう。私たちが現在使っているハッシュ化は、過去の戦いから生まれた技術の上に成り立っているのです。
次にシステムを検討・評価する機会があれば、パスワード保管の方式について、その歴史に思いを馳せながら、詳しく確認してみてはいかがでしょうか。
木曜日の夕方に仕事で疲れた脳みそをリフレッシュしたいとき、一人でゆっくりしたい夜、ちょっとした空き時間に、気軽に「つまめる」ソースコードの話題をお届けします。
まるで隠れ家バーでマスターが語るウンチクのように、普段は見過ごしがちなコードの奥深さや、思わず「へぇ〜!」と唸るようなユニークなアイデア、クスッと笑える小ネタを、ソースコードの世界を熟知した「情シスのじかん」がご紹介します。
コードを書くのが大好きなエンジニアさんも、ソースコードはちょっと苦手…という情シス部門の方も、この特集を読めば、きっとソースコードの新たな一面を発見できるはず。
業務効率化のヒントになるTipsや、セキュリティ対策に役立つ情報も盛りだくさん。
さあ、あなたも「おつまみとしてのソースコード」で、技術の世界をもっと身近に、もっと楽しく感じてみませんか?
本特集はこちら30秒で理解!フォローして『1日1記事』インプットしよう!