この記事は Cyber Security Advent Calendar 2022 の 20 日目の記事となります。
はじめに
今や Wi-Fi ルータは多くの家で利用されていますが、 どのような仕組みで安全な無線通信が実現しているかを理解して使っている人は多くはいないと思います。
私もこれまであまりわかっていないまま使ってきました。
今回は、Wi-Fi セキュリティのことはじめとして、通信の暗号化に使用する鍵を決定する手続きである 4way-Handshake について調べてたことをまとめようと思います。
4way-Handshake
家庭用の Wi-Fi ルータで広く採用されているセキュリティ規格である WPA2-PSK では、通信の暗号化に使う鍵を決定するのに 4way-Handshake と呼ばれる手続きを行います。
4way-Handshake では、下の図に示すようにアクセスポイント側から始まる二往復のメッセージのやり取りを行います。
クライアントとアクセスポイントはそれぞれ、事前に共有したパスワード(PSK)とアクセスポイントの ESSID から PMK (Pairwise Master Key) を計算します。
初めに、アクセスポイントはランダムな値 ANonce を決めクライアントに送信します(①)。
クライアントも同様にランダムな値 SNonce を決定します。 また、クライアントは PMK, ANonce, SNonce, アクセスポイントの MAC アドレス, クライアントの MAC アドレスから PTK (Pairwise Transient Key) を計算します。 この PTK がユニキャスト通信の暗号化の鍵として利用されます。 PTK を計算した後、クライアントは SNonce と MIC (Message Integrity Code) をアクセスポイントに送信します(②)。 MIC はメッセージのペイロードと PTK から計算されます。
アクセスポイントは SNonce を受け取った後、クライアントと同様に PTK の計算および MIC の検証(MIC を計算し一致するか確認)を行います。 クライアントの PSK が正しくない場合や、メッセージが改ざんされていた場合はここで検知することができます。 その後、ANonce、マルチキャスト通信の暗号化に使用する鍵 GTK (Group Transient Key) および MIC をクライアントに送信します(③)。 GTK は PTK により暗号化された状態で送信されます。
クライアントは ANonce、GTK および MIC を受け取った後、MIC の検証を行います。 この検証により、クライアントは不正なアクセスポイントに接続しようとしていないかを確認することができます。 問題がなければ MIC のみを設定したメッセージ ④ をアクセスポイントに返します。
4way-Handshake 以降の通信は暗号化されて行われます。
4way-Handshake のキャプチャ
実際に 4way-Handshake でやり取りされる通信内容を見てみましょう。
無線デバイスをモニターモードにすることで、ほかのデバイスの無線通信をキャプチャすることができます。 しかし、素の状態でモニターモードが有効化できる無線デバイスはあまり多くなく、価格も安くはないです。
とはいえ、ほかに手段がないわけではなく、 今回は Raspberry Pi 4 Model B に nexmon をインストールし、内蔵の無線デバイスのモニターモードを有効化することで代用しました。
このようなツールを用いて無線デバイスのモニターモードを有効化することは、デバイスの破損の原因にもなるため、試す場合は自己責任でお願いします。 nexmon を使ったモニターモードの有効化手順については省略します。
検証環境として以前使っていた Wi-Fi ルータを引っ張り出してきて ESSID とパスワードを次のように設定しました:
- ESSID:
test2
- パスワード:
KemGvW7K
Wireshark でキャプチャを実行しながら、別のデバイスからこのアクセスポイントに接続してみます。
フィルタで eapol
を指定し 4way-Handshake のやり取りのみを表示させたところ、次の 4 つのメッセージが表示されました。
それぞれのメッセージの内容は次のようになっていました。 前節で確認した通り Nonce や MIC の値が含まれていることが分かります。
MIC の計算
4way-Handshake のメッセージ内容を見てみましたが、これだけだとまあよくわからんという感じです。 より理解を深めるために、PMK、PTK および MIC を計算する Python プログラムを書いて、実際に MIC が一致するかを確認してみます。
下の記事がかなり参考になりました:
PMK は hashlib.pbkdf2_hmac
(https://docs.python.org/ja/3/library/hashlib.html) により導出されます。
ハッシュアルゴリズムは SHA1 を使用し、ESSID はソルトとして使用されます。
ストレッチ回数は 4096 回となっています。
import hashlib def pmk(ssid, password): return hashlib.pbkdf2_hmac('sha1', password, ssid, 4096, 32)
PTK は customPRF512 と呼ばれる専用の疑似乱数関数により生成されます。 customPRF512 は Scapy の実装 (https://github.com/secdev/scapy/blob/0e88e8f6c7fafd8a44d4545ddcf964fd4463e8fa/scapy/modules/krack/crypto.py#L41-L53) を拝借します(一部修正してます)。
import hmac # https://github.com/secdev/scapy/blob/0e88e8f6c7fafd8a44d4545ddcf964fd4463e8fa/scapy/modules/krack/crypto.py#L41https://github.com/secdev/scapy/blob/0e88e8f6c7fafd8a44d4545ddcf964fd4463e8fa/scapy/modules/krack/crypto.py#L41-L53 def customPRF512(key, amac, smac, anonce, snonce): """Source https://stackoverflow.com/questions/12018920/""" A = b"Pairwise key expansion" B = b"".join(sorted([amac, smac]) + sorted([anonce, snonce])) blen = 64 i = 0 R = b'' while i <= ((blen * 8 + 159) // 160): hmacsha1 = hmac.new(key, A + chr(0x00).encode('utf-8') + B + chr(i).encode('utf-8'), 'sha1') i += 1 R = R + hmacsha1.digest() return R[:blen]
MIC は KCK (Key Confirmation Key) と呼ばれる PTK の最初の 16 バイト部分を鍵とした、メッセージのペイロードの HMAC ダイジェスト値となります。
import hmac def mic(kck, payload): return hmac.new(kck, payload, 'sha1').digest()
これらを用いてメッセージ③の MIC を計算してみましょう。
MIC を計算する際、ペイロードは MIC 部分(下画像のマーカー部分)は 0 埋めしたものを使用します。
実行結果は次のようになり、メッセージ③の MIC と一致することが確認できました。
パスワードの総当たり
前節ではパスワードを知った状態で MIC を計算しましたが、逆に MIC の値が一致するようなパスワードを総当たりで探すこともできます。
ただし、PMK の計算はそこそこ負荷が高いので、本気でパスワードを割り出したい場合は GPU を使う実装をした方が良いでしょう。 逆に、総当たり攻撃を防ぐためには、パスワードの長さを十分に長くすることが有効です。
パスワードを共有することの危険性について
フリー Wi-Fi で複数人が同じパスワードを使用する場合、ほかのユーザの 4way-Handshake をキャプチャできれば PTK を計算できるので、ほかのユーザの通信内容を解読することが可能です。 それは自分の通信がほかのユーザに盗聴されるおそれがあるということでもあります。
WPA2-EAP の必要性
前述のとおり、WPA2-PSK では複数人で安全に利用することは難しそうです。
企業の無線 LAN など、複数人で利用することが前提の環境では WPA2-EAP などの、より安全な規格を利用するのがよいです。
まとめ
今回 Wi-Fi セキュリティのことはじめとして 4way-Handshake について調べてみました。 4way-Handshake の仕組みを知ることで、Wi-Fi 関連のリスクをある程度は回避できるようになったと思います。
まだまだ勉強途中なので、間違っているところなどがありましたら教えてください。