###### tags: `memo` `security` # メッセージ認証コード(MAC)勉強メモ ## 効果 A が B にメッセージを送信することを考えた場合、MACは以下を保証 1. メッセージが改ざんされていない(改ざん防止) 2. メッセージは A が送ったものである(なりすまし防止) ただし、2. は再送攻撃がない前提 ## 前提条件 A, B 間で秘密鍵を共有する必要がある ## ざっくりとした動作 1. A, B 間で秘密鍵を共有 2. A はメッセージ M と秘密鍵 K からタグ T を生成する 3. A は M, T を B に送信する 4. B は M と自身が持つ秘密鍵 K からタグ T' を生成する 5. B と T と T' を比べ、一致していたら改ざんはない ## 再送攻撃 通信路上の M, T を攻撃者がキャッチして、再度 B に投げたとき、B は M, T が A から送られたのか、攻撃者の再送攻撃によって送られたのか確かめるすべはない ## 例. HMAC (Hash function-based MAC) ### 基本的な発想 ハッシュ関数に K とメッセージをぶち込んだったら、いい感じにタグ作れるだろというモチベ $$ MAC_K(M) = h(K, M) $$ h はハッシュ関数をつかって K, M をごにょごにょする関数 ### ナイーブな発想 秘密鍵 K とメッセージ M を連結して、ハッシュ関数に入れる $$ h(K, M) = H(K || M)\ or\ H(M || K) $$ H はハッシュ関数、|| は連結 #### K が頭に来る場合 --- secret prefix 伸長攻撃ができる 攻撃者 Oscar は通信路上から M と T = H(K || M) を盗める。なので、テキトーなメッセージ M' を使って、H(K || M || M') を作れる。これで攻撃者は、MAC の検証をパスできるメッセージとタグのペア(M || M', K)を生成できたことになる これは padding を考慮していないので、padding を考慮するともうすこしややこしくなる #### M が頭に来る場合 --- secret suffix ハッシュ関数 H の衝突困難性が破れたとする。つまり、M != M', H(M) == H(M') なる衝突ペア (M, M') が見つかったとする H(M) == H(M') なので、反復型ハッシュ関数の性質上 H(M || K) == H(M' || K) になる。 このとき、攻撃者は異なるメッセージ(M, M')に対して同一のタグを生成できたことになる **secret suffix の場合 MAC の強さはハッシュ関数の衝突困難性に依存する** ### HMAC の定義 FIPS PUB 198-1 によると ``` MAC(text) = HMAC(K, text) = H((K0 ⊕ opad) || H((K0 ⊕ ipad) || text)) ------------ プログラムっぽく書くと HMAC(K, text) { K0 = pre_process(K) H1 = H((K0 ⊕ ipad) || text) // inner hash H2 = H((K0 ⊕ opad) || H1) // outer hash return H2 } ipad, opad は固定値 ``` 簡単にいうと ``` secret prefix を 2 回繰り返す ``` HMAC で伸長攻撃をするためには H1 を知る必要があるが、H1 は内部ハッシュ値で攻撃者にはわからないので、攻撃は難しくなるということなのかなー… #### 実装 ```python import hmac from secrets import token_bytes from hashlib import sha256 from Crypto.Util.number import bytes_to_long as b2l from Crypto.Util.number import long_to_bytes as l2b def _hmac(key: bytes, m: bytes, H) -> bytes: B = H().block_size L = H().digest_size # key を長さ B バイトに揃える if len(key) > B: key = H(key.ljust(B, b"\x00")).digest() elif len(key) < B: key = key.ljust(B, b"\x00") # 1 回目のハッシュ化 ipad = b2l(b"\x36" * B) h1 = H(l2b(b2l(key) ^ ipad) + m).digest() # 2 回目のハッシュ化 opad = b2l(b"\x5c" * B) h2 = H(l2b(b2l(key) ^ opad) + h1).digest() # h2 が tag になる(場合によっては、先頭数バイトだけ使うことも) return h2 m = "ニイタカヤマノボレ".encode("utf-8") H = sha256 key = token_bytes(H().digest_size // 2) print(_hmac(key, m, H)) print(hmac.new(key, m, H).digest()) ``` 出力 ``` $ python m_hmac.py b'\xad8\xbc\x94\xa9r\xa9\xf4\x89\x9bv \xb93\xeeK\xc7\x07.\xd8\xce\x1a\x0fT(*\xba_\xd2\x16\xa7\xd0' b'\xad8\xbc\x94\xa9r\xa9\xf4\x89\x9bv \xb93\xeeK\xc7\x07.\xd8\xce\x1a\x0fT(*\xba_\xd2\x16\xa7\xd0' ``` ## MAC にできないこと ### メッセージの暗号化 MAC では、タグを使ったメッセージの検証が主な目的なので、メッセージの暗号化については考慮されていない。このあたりは別の要素技術で補う必要がある ### 否認防止 秘密鍵を使う性質上、メッセージからタグを作ることは送信側も受信側もできる。なので、受信側が勝手に作ったタグだと、送信側が言い張れば、それを否認することはできない ### 第三者に対する完全性を保証できない 秘密鍵を使うのでもちろん、秘密鍵を共有していない第三者に対してMACは使えない ## 参考 - CIPHER and CODE 暗号技術のすべて - https://www.cryptrec.go.jp/report/cryptrec-gl-2001-2013r1.pdf - https://tools.ietf.org/html/rfc2104 - https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.198-1.pdf - https://www.youtube.com/watch?v=DiLPn_ldAAQ