2017年2月にGoogleがSHA1ハッシュの衝突に成功した、とアナウンスしました。1
暗号学的に安全なハッシュ関数な場合、SHA2-256を使っていると思います。SHA3が利用可能になのでSHA3を利用している場合も多いと思います。SHA2もSHA3も暗号学的ハッシュ関数です。ざっくりとこれらのハッシュ関数を安全に使う方法を紹介します。
暗号学的ハッシュ関数とは?
理想的な暗号学的ハッシュ関数には、次の性質が求められます。
- ハッシュ値の計算が容易であること。
- 同じハッシュ値を持つメッセージの計算が不可能なほど複雑であること。
- 同じハッシュ値となるメッセージは事実上存在しないこと。
※ 英語版Wikipediaの定義です。
これらの要素を満すハッシュ関数が暗号学的ハッシュ関数と考えられています。SHA2とSHA3は暗号学的なハッシュ関数2です。
SHA2とSHA3
SHA3はSHA2の後継ハッシュ関数として開発されました。要するにSHA3はSHA2より強いハッシュ関数を目指して開発されました。より強いとは言っても、暗号学的ハッシュ関数の性質
- ハッシュ値の計算が容易であること。
から、SHA3がSHA2に較べてあまり遅いようでは問題です。最適化した実装の場合、SHA3はSHA2とほぼ同等の速度でハッシュ値を計算します。3
つまり総当たり攻撃的にはSHA2とSHA3は同じくらい攻撃しやすい(=攻撃しづらい)と言えます。
SHA3が開発された理由はSHA2の基本構造がAESに似ていることにあります。万が一、SHA2またはAESに対する攻撃方法が見つかった場合、ハッシュと暗号の両方が同時に攻撃される可能性が高くなっています。このような状況となるリスクを軽減する為にSHA3が作られました。
現在のところSHA2もSHA3も同じく安全だとされています。無理にSHA3に移行する必要はありません。しかし、少しでもリスクを軽減したい場合はSHA3の方がリスクが少ないかも知れません。4
雪崩効果
雪崩効果(Avalanche Effect)とは入力の1bitが変更されると、平均して出力の半分以上のbitが変わることを言います。
本当に理想的なハッシュ関数なら入力に1bitの違いがあれば、全く異なる一見ランダムな値になるべきです。しかし、SHA2もSHA3もそれほど異なる出力を行いません。
「秘密情報と非秘密情報を連結してハッシュ値を取得してはならない」と言われているのは、SHA2/3でも雪崩効果はそれほど効果が大きくないからです。
HMACの必要性
一言で言うと「鍵などの秘密情報とデータなどの非秘密情報を一緒にハッシュ化した場合に秘密情報の安全性を確保するため」です。5
”雪崩効果は大きくない”ので、秘密情報とデータを一緒にハッシュ化し、データが制御できると総当たり攻撃で本当に総当たりをする必要はありません。近いハッシュ値を得た場合に少しづつ変えて”当り”がでないかチェックできます。完全に総当たり攻撃するより遥かに効率的な攻撃ができます。
<?php //単純に 文字列連結 をしてしまうと統計処理を利用した攻撃に脆弱になる $message_authentication_code = hash('sha3-256', $data . $key); ?>
SHA2/3ともに完璧なハッシュではないのでHMACが必要になります。HMACは単純な文字列連結で$dataと$keyをつなげません。PHPのHMAC関数の場合、
string hash_hmac ( string
$algo
, string$data
, string$key
[, bool$raw_output
=FALSE
] )
このように$dataと$keyが別の引数となっています。
ここでは説明を省略しますが、HMACは$algoで指定したハッシュを複数回適用し、$dataが微妙に変わっても結果のハッシュ値が大きく変わり、効率的に$keyを解析できないようになっています。
メッセージ($data)が改ざんされていないことを保証するMAC(Message Authentication Code)を生成する場合、必ずHMACを使います。
HKDFとFS/PFSの必要性
HMACを利用した安全な鍵導出も可能です。しかし、API/標準として決まった方式があった方が便利です。
HKDFでは先ず秘密鍵(IKM)を強いランダムsaltとHMACハッシュ値を取り、秘密鍵の安全性を保障したPRKを生成します。
2.2. Step 1: Extract HKDF-Extract(salt, IKM) -> PRK Options: Hash a hash function; HashLen denotes the length of the hash function output in octets Inputs: salt optional salt value (a non-secret random value); if not provided, it is set to a string of HashLen zeros. IKM input keying material Output: PRK a pseudorandom key (of HashLen octets) The output PRK is calculated as follows: PRK = HMAC-Hash(salt, IKM)
次に追加情報(info – 鍵の有効期限やバージョンを記録)とSpep 1で生成したPRKでHMACハッシュ値を取得します。鍵の長さ(L)を拡張する必要がある場合は単純ループでHMACハッシュ値を生成し長い鍵を生成します。
2.3. Step 2: Expand HKDF-Expand(PRK, info, L) -> OKM Options: Hash a hash function; HashLen denotes the length of the hash function output in octets Krawczyk & Eronen Informational [Page 3] RFC 5869 Extract-and-Expand HKDF May 2010 Inputs: PRK a pseudorandom key of at least HashLen octets (usually, the output from the extract step) info optional context and application specific information (can be a zero-length string) L length of output keying material in octets (<= 255*HashLen) Output: OKM output keying material (of L octets) The output OKM is calculated as follows: N = ceil(L/HashLen) T = T(1) | T(2) | T(3) | ... | T(N) OKM = first L octets of T where: T(0) = empty string (zero length) T(1) = HMAC-Hash(PRK, T(0) | info | 0x01) T(2) = HMAC-Hash(PRK, T(1) | info | 0x02) T(3) = HMAC-Hash(PRK, T(2) | info | 0x03) ... (where the constant concatenated to the end of each T(n) is a single octet.)
安全な鍵導出と導出鍵の利用にはFS/PFSと有効期限管理 6が欠かせません。つまり、事前共有鍵と有効期限を適切に管理する必要があります。HKDFでは既にセキュアにしたPRKとinfoのHMACハッシュ値により、infoにどのような情報が設定されても秘密鍵(IKM)の安全性が保障できるように設計されています。7
HMACを使って以下のように、有効期限情報を文字列連結する方法はベストプラクティスではありません。
// 事前共有鍵: $salt // 有効期限: $expire // $salt . $expire と文字列連結になっている点に注目 // このような文字列連結はHMACを使わずに // hash('sha2-256', $message . $salt) // とすることがNGであるのと同じ理由でNG // ※ $master_keyは暗号学的に強い鍵であることが前提。e.g. random_bytes(32) $derived_key = hash_hmac('sha2-256', $master_key, $salt . $expire);
これを避ける為にHKDFは事前共有鍵($salt)と有効期限や鍵バージョンを保存する追加情報($info)の引数を持っています。
string hash_hkdf ( string
$algo
, string$ikm
[, int$length
= 0 [, string$info
= ” [, string$salt
= ” ]]] )
HKDFを使うと以下のようになります。
// 事前共有鍵: $salt // 有効期限: $expire // hash_hkdf() APIは他のhash関数と整合性が取れない // & 暗号学的に脆弱な鍵を作るAPIになっています。 // $saltはFS/PFSの為に常に必須です。$info引数も同様です。 // $info引数で有効期限管理を行わず、追加情報も必要ない場合、 // HMACでも必要とする鍵導出が可能です。 // $length引数は特に理由がない限り変えるべきではありません。 $derived_key = bin2hex( hash_hkdf('sha2-256', $master_key, 0, $expire, $salt) );
hash_hkdf()の$salt引数は、公開可能情報8ですが、第三者によって改変可能であってはなりません。(第三者が$saltを変更し、HKDFハッシュ値を取得できる状態にしてはならない)
$info(上記サンプルでは$expire)引数は公開情報かつ第三者が変更しても構わない情報です(第三者が改ざんした$infoを使ってHKDFハッシュ値を得ても構わない)9
まとめ
暗号学的なハッシュ関数(SHA2/3など)でも、適切な使い方をしないと安全に使えません。必要に応じてHMAC/HKDFと組み合わせて使う必要があります。
HMAC/HKDFも正しい使い方でなければ安全ではありません。
- これ以前からSHA1ハッシュは安全でないと考えられいました。SHA1は脆弱です。MD5は話にならないくらい脆弱です。SHA1/MD5はセキュリティに関係しない用途にのみ利用できます。弱く脆弱なハッシュでも使い道はあります。例えば、Linuxカーネルにはファイルの同一性を確認する為にMD5より更に脆弱なMD4のサイズを更に半分(つまり物凄く脆弱)にしたハーフMD4を利用しているファイルシステムも在ります。セキュリティが必要でなく、速い動作が必要な場合は脆弱なハッシュを使っても問題ありません。 ↩
- SHA2/3以外にも多くの暗号学的なハッシュ関数が存在します。ここではNIST標準のSHA2/3だけ取り上げます。 ↩
- PHPの場合、PHP 7.2からリトルエンディアン(Intel/AMD)CPU向けの最適化実装のSHA3が利用されています。それ以前のSHA3はSHA2の3倍程遅かったです。 ↩
- SHA3のアルゴリズムの方が先に破られる、という可能性がない訳でもありません。 ↩
- 基本的には同じ意味(秘密情報の安全性を確保するため)ですが、HMACを使うと多少脆弱なハッシュ関数であっても安全に利用することができます。例えば、既にSHA-1は安全なハッシュ関数とは言えませんが、今のところHMAC+SHA-1で取得したMACが攻撃可能になって、という話は耳にしていません。 ↩
- API引数でなくても、FS/PFS(将来の鍵の安全性)に必要な有効期限情報は管理可能です。しかし、有効期限情報がAPI引数として管理できる方が便利です。 ↩
- IKMは強い鍵である必要はありません。しかし、IKMが弱い鍵である場合、saltを強いランダム値かつ秘密のsalt、にしないとIKMの安全性は保てません。 ↩
- $master_keyが暗号学的に強い鍵場合、例えば$master_key = random_bytes(32)だと、$saltを公開しても問題にならない。しかし、弱い鍵、例えば人間ユーザーが入力した”mySecretPassword”の場合、$saltも公開してはなりません(この場合、$saltは秘密情報です)。RFCでは弱い$saltでも$master_keyの保護を飛躍的に強固にする、と記載されていますが$saltは基本として強い鍵にします。 ↩
- HKDFアルゴリズムでは$length引数を設定するとHMACを使って整数をインクリメントしながら長いハッシュ値を取得します。整数を1づつ増やすので、攻撃者がハッシュを統計学的に解析する際に利用する手順と同じです。このような処理をしても安全であるとする根拠は、少なくとも$saltが暗号学的に強い鍵であれば、その値を利用して取得したHMACハッシュ値は安全($master_keyと$saltのハッシュ値から$master_keyを推測/解析できない)だからです。$infoはどのような値を設定して、どのような使われ方をしても安全なようにHKDFは設計されています。$saltには色々と条件があります。$saltには注意が必要です。 ↩