ハッシュ関数とは:SHA-256、チェックサム、パスワードの保存まで

SHA-256 が常に64桁になる理由、1文字変えるだけで結果がすべて変わる性質、ハッシュ・暗号化・エンコードの違い、ダウンロードしたファイルの確認、パスワードに高速なハッシュを使ってはいけない理由をまとめました。

開発セキュリティ

Linux のインストールイメージや開発ツールのダウンロードページには、SHA256: 3f7a… のような長い文字列が添えられていることがあります。読み飛ばされがちですが、この文字列があれば、何 GB もあるファイルが1ビットも欠けずに届いたかを確かめられます。この文字列を作っているのがハッシュ関数です。

どんな入力でも同じ長さに

ハッシュ関数は、任意の長さのデータを受け取って決まった長さの値を返します。SHA-256 は名前のとおり256ビット、16進数で64桁を出力します。入力が hello の5文字でも 10GB の動画でも、結果の長さは同じです。

SHA-256("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
SHA-256("")      = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

空の文字列にもハッシュ値があります。ハッシュは文字ではなくバイトに対して計算されるため、同じ「こんにちは」でも UTF-8 で保存したか Shift_JIS で保存したかで結果が変わります。改行が LF か CRLF かでも変わります。

hello と Hello の SHA-256 の結果を並べた図。2つの入力は先頭の文字が大文字か小文字かだけの違いですが、64桁の16進数の結果はまったく似ておらず、256ビットのうち125ビットが異なります。下の表では、エンコード(Base64:元に戻せる、鍵なし)、暗号化(AES:鍵があれば戻せる)、ハッシュ(SHA-256:戻せない、鍵なし)を比べ、パスワードには Argon2id のようにわざと遅くしたハッシュを使うと記しています。

hello の h を大文字にするだけで、結果はまったく別物になります。2つの結果をビット単位で比べると、256ビットのうち125ビットが違います。入力がわずかに変わるだけで出力の半分ほどが反転するこの性質をアバランシェ効果と呼びます。結果を見ても、入力が「似ているかどうか」すら推測できないということです。

暗号学的ハッシュに求められること

SHA-256 のような暗号学的ハッシュ関数には、次の3つが求められます。

  • 原像計算困難性:ハッシュ値から元の入力を見つけられないこと。
  • 第二原像計算困難性:ある入力と同じハッシュ値になる別の入力を見つけられないこと。
  • 衝突困難性:同じハッシュ値になる異なる2つの入力を、組み合わせを問わず見つけられないこと。

出力が256ビットに決まっている以上、同じハッシュ値になる入力の組は数学的に必ず存在します。大事なのは、それを現実的な計算量で見つけられないかです。

MD5 と SHA-1 が退いた理由

MD5 は、2004年に王小雲(Wang Xiaoyun)氏らが衝突を作る方法を発表してから、セキュリティ用途では使われなくなりました。2012年に見つかったマルウェア Flame は、MD5 の衝突を利用して Microsoft のコード署名証明書を偽造していました。

SHA-1 は、2017年2月に Google とオランダの CWI が内容の違う2つの PDF が同じ SHA-1 値になる例を公開したことで、事実上役目を終えました(SHAttered)。必要な計算量は SHA-1 約2⁶³回分で、個人には無理でも、大きな組織なら十分に手が届く規模だと示されました。

とはいえ、MD5 や SHA-1 がまったく役に立たなくなったわけではありません。転送中に偶然壊れたファイルを見つける用途なら、今でも十分に機能します。問題は、誰かが意図的に改ざんできる場面です。署名、証明書、パスワードのように攻撃者が割り込める場所では、SHA-256 以上を使います。

ハッシュ、暗号化、エンコードは別物

どれもデータを読めない文字列に変えるので混同されがちですが、役割はまったく違います。

元に戻せるか 鍵が必要か 用途
エンコード(Base64 など) 誰でも戻せる いいえ データを文字として書き写す
暗号化(AES など) 鍵があれば戻せる はい 秘密を守る
ハッシュ(SHA-256 など) 戻せない いいえ 改ざんの確認、パスワードの照合

「パスワードは暗号化して保存しています」という言い方をよく見かけますが、きちんと作られたサービスはパスワードを暗号化ではなくハッシュ化して保存します。暗号化は鍵があれば元に戻せるので、鍵が漏れた瞬間にすべてのパスワードが明らかになってしまいます。

ダウンロードしたファイルを確かめる

チェックサムは OS の標準コマンドで計算できます。

# Windows(PowerShell)
Get-FileHash .\ubuntu.iso -Algorithm SHA256

# macOS
shasum -a 256 ubuntu.iso

# Linux
sha256sum ubuntu.iso

表示された値が配布ページの値と1文字でも違えば、ファイルは壊れているか書き換えられています。ただし限界もあります。ファイルとチェックサムを同じサーバーから取得した場合、そのサーバーを乗っ取った攻撃者は両方を書き換えられます。主要なディストリビューションがチェックサムのファイルに GPG 署名を付けているのはこのためです。

パスワードに SHA-256 を使ってはいけない理由

ハッシュは元に戻せないので、パスワードを SHA-256 で保存すれば安全に思えます。しかし、そうではありません。問題は SHA-256 が速すぎることです。ファイルの確認には長所ですが、データベースを丸ごと盗み出した攻撃者にとっても長所になります。GPU 1枚で1秒間に数十億通りの候補を試せるため、よくあるパスワードはすぐに見つかってしまいます。

そこでパスワードの保存には、2つの工夫を加えます。

1つ目はソルトです。ユーザーごとに異なるランダムな値をパスワードに加えてからハッシュ化します。同じパスワードを使う2人でも保存される値が変わり、あらかじめ計算しておいたハッシュの一覧(レインボーテーブル)が役に立たなくなります。

2つ目はわざと遅くしたハッシュです。OWASP のパスワード保存に関するガイドは、次の設定を勧めています。

アルゴリズム OWASP の推奨最小設定
Argon2id(第一候補) メモリ 19 MiB、反復2回、並列度1
scrypt N=2¹⁷、r=8、p=1
bcrypt ワークファクター10以上(入力は先頭72バイトのみ使用)
PBKDF2-HMAC-SHA256 反復600,000回以上(FIPS 準拠が必要な場合)

1回の照合に0.1秒かかっても、ログインする人はほとんど気づきません。しかし数十億の候補を試したい攻撃者にとっては、致命的な差になります。これらのアルゴリズムの多くはソルトを自動で生成して結果の文字列に含めるため、ソルトを自分で管理する必要もありません。各言語に実績のあるライブラリがあるので、自分で実装しないのが原則です。

ハッシュ生成ツールで確かめる

ハッシュ生成ツールに文字列を入れるかファイルを選ぶと、MD5・SHA-1・SHA-256・SHA-384・SHA-512 を一度に表示します。最後の欄に配布ページのチェックサムを貼り付ければ、どのハッシュと一致するかが分かります。ファイルはブラウザの中だけで計算され、アップロードされません。hello と Hello を順に入れると、上の図と同じ結果が出ます。

参考

  • NIST FIPS 180-4, Secure Hash Standard (SHS)
  • OWASP, Password Storage Cheat Sheet
  • Stevens et al., “The first collision for full SHA-1”(SHAttered、2017年)
  • Wang, Yu, “How to Break MD5 and Other Hash Functions”(EUROCRYPT 2005)

ブログへ