HMACの仕組み

# 1. はじめに
Hash-based Message Authentication Code (以後HMACと呼ぶ) とは、二者間でメッセージを送受信する際に、利用されるメッセージ認証コード（MAC）の１つである。この技術により、

- メールの送信者が第三者のなりすましでないか
- メッセージが改竄されていないか

を保証することができる。

本記事では、HMACがどのようにして送信者の正当性確認や改竄防止を実現しているのか、その仕組みを説明する。

# 2. HMACとは何か
はじめに、2者間でチャットアプリを通じてメッセージを送ることを考える。

AさんからBさんにメッセージ$m$を送信するとしよう。ここで受信者であるBさんは、Aさんから届いたメッセージ$m$をチャットアプリの受信ボックスを通じて閲覧することができる。

```mermaid
flowchart LR
    Sender["A"]
    Receiver["B"]

    Sender -- メッセージ（m） --> Receiver
```

このとき、悪意をもった第三者CがAになりますまして、Bにメッセージを送ることを考える。このとき、Ｂにはそれが本当にＡから送られたメッセージなのかを判断する手段がない。さらに、仮に送信者が本当にＡであったとしても、メッセージの改竄が行われた場合に、Bはそれを見抜く手段がない。


```mermaid
flowchart LR
    A["A"]
    C["攻撃者: C"]
    B["B"]

    A -- メッセージ m --> C
    C -- 改竄 m → m' --> B
```

```mermaid
flowchart LR
    A["本物の送信者: A"]
    C["C（なりすまし）"]
    B["受信者: B"]

    A -- メッセージ m --> B
    C -- メッセージ m'（Aを装う） --> B
```

このような「なりすまし」や「メッセージの改竄」を見抜くためには、送信者の正当性とメッセージ内容の整合性を保証する必要がある。

このような場合に用いられる技術が、今回の記事の主題でもある**HMAC**である。HMACを使うことで、

- メッセージの送信者がなりすましでないこと
- メッセージの改竄が行われていないこと

を保証する。


予め送信者は受信者と暗号鍵$K$を共有する。送信者はメッセージ本文と、暗号鍵からMAC値（=HMACを使って計算した値）を算出して、本文と共に送信する。受信者は共有された暗号鍵$K$を使って、同様に受信した本文からMAC値を計算する。

受信者が計算したMAC値と、添付されたMAC値が等しい時、メッセージの伝送途中で**本文が改竄されていない**こと、そして送信者がなりすましでないことを保証することができる。

```mermaid
flowchart TD
    subgraph 送信者
        A1[メッセージ m]
        A2[共有鍵 K]
        A3[HMAC を計算]
        A4[メッセージ m と MAC を送信]
    end

    subgraph ネットワーク
        N1[インターネットなど]
    end

    subgraph 受信者
        B1[受信: メッセージ m と MAC]
        B2[共有鍵 K]
        B3[HMAC を再計算]
        B4[MAC値を比較]
        B5{一致する？}
        B6[→ OK: 改竄なし・正当な送信者]
        B7[→ NG: 改竄またはなりすまし]
    end

    A1 --> A3
    A2 --> A3
    A3 --> A4
    A4 --> N1 --> B1
    B1 --> B3
    B2 --> B3
    B3 --> B4 --> B5
    B5 -->|Yes| B6
    B5 -->|No| B7
```


# 3. HMACの仕組み
HMACは、以下のように定義される。

$$
HMAC_K(m) = H(K \oplus \text{opad} \,\|\, H(K \oplus \text{ipad} \,\|\, m)) \tag*{･･･（1）}
$$

- $K$：秘密鍵（shared key）
- $m$：メッセージ（message）
- $\text{opad}$：outer padding（外部パディング定数）
- $\text{ipad}$：inner padding（内部パディング定数）
- $H(x)$：ハッシュ関数（SHA-256など）

ここで、$ipad$ はバイト値 `0x36`(=10進数で54）を繰り返した文字列であり、$opad$ はバイト値 `0x5C`（=10進数で92）を繰り返した文字列である。秘密鍵は、2. HMACで述べたように、予め2者間で共有している鍵である。

ハッシュ関数は、SHA-256やSHA-512等の暗号学的に安全なハッシュ関数であれば**任意**である。SHA1やMD4のように、既に深刻な脆弱性が報告されている関数の使用は、セキュリティ上の理由から避けるべきである。

## 3.1. ipad / opad の選定理由（論文より）
2つのバイト値 `0x36`、`0x5C` が採用された理由は、BellareらによるHMACの元論文「Keying Hash Function for Message Authentication」のセクション5.2 で次のように述べられている。


> The above particular values of opad and ipad were chosen 
> 	- "to have a very simple representation
> 	  (to simplify the function's specification and minimize the potential of implementation errors),"
> 	
> 	- "and to provide a high Hamming distance between the pads.
> 	  (The latter is intended to exploit the mixing properties attributed to the compression function underlying the hash schemes in use.) 
> 	  These properties are important in order to provide computational independence between the two derived keys.


上記の `opad` と `ipad` の値は、次の理由に基づいて選ばれている：
- 単純なビット表現であること
	- $\rightarrow$ 実装の使用が簡潔になり、実装ミスを減らすため
- パッド間のハミング距離を大きくする為
	- $\rightarrow$ ハッシュ関数の圧縮関数が持つ混合性を活かし、導出された2つのキー間に計算上の独立性を与えるのに重要である。（2つの鍵が同じものから作られたとは思えないくらい異なって見える事）

## 3.2. HMACの内部処理の一連の流れ
下記に、HMACの内部処理の流れを示す。これは3. HMACの仕組みで示した数式（1）を図解したものである。


```mermaid
flowchart TD
    K["秘密鍵 K"]
    BlockSize["ハッシュ関数のブロックサイズ"]
    PaddedK["鍵の前処理:<br>短ければ0パディング<br>長ければハッシュ化"]
    M["メッセージ M"]

    XOR1["XOR: K_ipad = K ⊕ ipad"]
    XOR2["XOR: K_opad = K ⊕ opad"]

    InnerConcat["結合: K_ipad || M"]
    InnerHash["一方向ハッシュ関数<br>（例: SHA-256）<br><br> innerHash = H(K_ipad || M)"]

    OuterConcat["結合: K_opad || InnerHash"]
    OuterHash["一方向ハッシュ関数<br> （例:SHA-256）<br><br>MAC = H(K_opad || InnerHash)"]
    MAC["最終MAC値"]

    K --> PaddedK
    BlockSize --> PaddedK
    PaddedK --> XOR1
    PaddedK --> XOR2

    XOR1 --> InnerConcat
    M --> InnerConcat
    InnerConcat --> InnerHash
    InnerHash --> OuterConcat
    XOR2 --> OuterConcat
    OuterConcat --> OuterHash
    OuterHash --> MAC
```


事前に共有された秘密鍵$K$は、使用するハッシュ関数のブロック長（以降これを $l$ とする）に合わせて、$l$ より短い場合は0でパディングされ、$l$ より長い場合はハッシュ化される。

$\rightarrow$ これで使用される鍵はブロック長が $l$ の鍵を生成する。

ここから、鍵$K$ と $ipad$ の `0x36` をブロック長が $l$ になるように繰り返しされたものと XOR 演算したものと、メッセージを結合する。（図の「結合: K_ipad || M」の部分）これを一方向ハッシュ関数に通す。（ハッシュ関数に通した結果を innserHash とする。）

次に鍵$K$ と $opad$ の`0x5C` を同様の手法で結合する。この結果を innserHash と結合し、再度一方向ハッシュ関数に通す。これを最終のMAC値とする。