PHPにHKDF関数、hash_hkdf()が追加されましたが、そのシグニチャは褒められるモノではありません。
hash_hkdf()が脆弱なAPI仕様になってしまった主な原因は、開発者がハッシュ関数を利用して鍵を導出する場合に知っておくべきFS/PFSの概念を知らなかったことにあります。(秘密鍵のセキュリティ維持にSaltが必須であるとの理解が足りなかったことも原因)
FS/PFSはハッシュ関数を利用した安全な鍵導出に必須の知識です。簡単な概念なので直ぐに理解できると思います。
FSとPFS
FSとはForward Secrecy、PFSとはPerfect Forward Secrecyの頭文字です。日本語では前方秘匿性と言われています。Wikipediaの解説だと
forward secrecy(perfect forward secrecy、略してFSあるいはPFSとも呼ばれる[1]。日本語で前方秘匿性とも[2])は、長期的な鍵対からセッションキーを生成した際に、のちに長期鍵の安全性が破れたとしてもセッションキーの安全性が保たれるという、鍵交換プロトコルの持つ性質である。この特性を守るためには、データを暗号化するための鍵から別の鍵を生成してはならないし、そしてデータを暗号化する鍵の素材となる秘密は一度だけの使い捨てにしなければならない。そうすることで、1つの鍵が破れたとしても被害がほかの鍵に及ばないようになる。
Wikipediaの紹介だけでは身も蓋もないので解説します。
FS/PFSとは
長期間利用するマスター鍵を直接利用し、マスター鍵が漏洩したり、解析されてしまった場合、被害は計り知れないです。要するにAPIキーなどのキーを直接使うのはアンチプラクティスです。
Forward Secrecyとか前方秘匿性と言うと解りづらいですが、
マスター鍵を直接利用せず
- ハッシュ関数
- マスター鍵
- ソルト(ランダムな使い捨て鍵として利用)
で一時的な鍵を導出(生成)&利用し、マスター鍵の安全性を高めることをForward Secrecyと言います。1
元々はFSは不十分な前方秘匿性、PFSは完全な前方秘匿性と区別されていたようですが、現在ではFSもPFSを指す概念として利用されています。つまり、半端な前方秘匿性はNGで、常に完全な前方秘匿性を維持すべき、ということでしょう。
言葉よりコードで書いた方が分かりやすいです。
<?php // 比較的、長期間保存される鍵 - APIキーやCSRFトークン、暗号化のマスターキーなど $master_key = 'my_super_secret_master_key'; // 利用するハッシュ関数と同じbit幅のsaltが最も効率が良く、安全性も最適化される // SHA3-512は512 bits == 64 bytes // saltは短い期間で捨てる、使い捨ての導出鍵として利用する $salt = random_bytes(64); $HMAC_key_derived_with_forward_secrecy = hash_hmac('sha3-512', $master_key, $salt); $HKDF_key_derived_with_forward_secrecy = bin2hex(hash_hkdf('sha3-512', $master_key, 0, null, $salt)); // 上記の2つの導出鍵が漏洩したとしても、$master_keyは保護される。 // $salt(事前共有鍵と呼ばれる事もある)を変えれば、また安全に暗号化などが行える。 // ただし、このコードの場合は有効期限が定義されていない。導出鍵には必ず有効期限が設定 // されている必要がある。HKDFの場合、APIが有効期限などの追加情報保存も行えるよう設計 // されている。 $stupid_weak_key = bin2hex(hash_hkdf('sha3-512', $master_key)); // HKDFをsalt引数なしで使用するのは、愚かな行為です。 // 普通は、salt引数と有効期限もinfo引数に指定して使用すべきです。
このように鍵を利用すれば、導出鍵が漏れても将来は別の導出鍵を利用して、安全性を確保できる仕組みを作れます。
FS/PFSは元々は暗号鍵の処理で使われた概念ですが、Webアプリケーションでも使える、というより使うべき概念です。
WebアプリケーションではCSRFトークン、APIキーなどのアクセストークンを使います。これらは将来に渡って同じ鍵を使うべきではありません。Webシステムの場合、これらの鍵情報はJavaScriptインジェクションや不正なプラグインなどで比較的簡単に漏洩します。
この為、これらの鍵は比較的短い時間で新しい鍵に更新すべきです。(そして不正な鍵の利用を検出&対応すべきです、可能な限り)
以下はFS/PFSの概念を取り入れたCSRFトークン、APIキーの使い方の紹介です。
まとめ
FS/PFSの概念がない鍵管理でも”安全”とされた牧歌的な時代もありました。しかし、現在ではFS/PFSの概念がない鍵管理は脆弱で危険な管理方法です。
鍵を使う場合、壊れた脆弱な設計を利用しなければならない等どうしようもない場合を除いて、マスター鍵になる情報は直接利用せず、ランダムなsaltを利用して導出した一時鍵を利用します。
HKDF, HMACで最適なsaltは
- 完全にランダムな値(random_bytes()を利用)
- 利用中のハッシュ関数と同じビット数(SHA3-512は64バイト、SHA3-256なら32バイト)
です。
saltを利用するだけでは不十分で、有効期限も設定するべき2です。hash_hkdf()なら以下のようにします。
$導出鍵 = hash_hkdf(‘sha3-512’, $マスター鍵, 0, $有効期限, $ソルト);
FS/PFS概念のない鍵管理は危険な方法です。3
ハッシュ関数の利用のベストプラクティスは「利用可能な最強のハッシュ関数を利用する」です。4 特に理由がなければ、SHA3-512を利用しましょう。
- 暗号方式には1つの暗号化ブロックを解読できたとしても、次の暗号ブロックはその鍵では解読できないようにする仕組みもあります。ここではハッシュの事だけ考慮することにします。 ↩
- 確実に導出鍵を無効化するためには有効期限がある方が容易かつ確実。こういった情報を鍵導出の際に付与できるようにすることがHKDF標準の目的です。つまり、$infoを使わないHKDF利用は推奨されないです。 ↩
- PHPのhash_hkdf()はPHP 7.1から追加された新しい関数ですが、必須のsalt引数が最後のオプション引数です。最後のオプション引数ですが、必ず指定しなければならない引数です。注意してください。 ↩
- PHP 7.2のSHA3はSHA2と同等の速度です。PHP 7.2からは利用すべきハッシュはSHA3-512です。(7.2未満はSHA2のSHA512。古いPHPのSHA3は3倍くらい遅い) ↩