Skip to content

WPA3 & SAE Analysis

WPA3-SAE analysis covers the cryptography and operational reality of the Wi-Fi Alliance’s successor to WPA2: Simultaneous Authentication of Equals (SAE, built on the Dragonfly PAKE), GCMP-256 cipher suites, mandatory Protected Management Frames, and the transition mode that lets WPA3 networks accept WPA2 clients — the compatibility feature that reopens most of the door WPA3 closed. Analysis here has three layers: the crypto literacy (why a Password-Authenticated Key Exchange removes offline-verifiable material, ending capture-then-crack), the attack surface (transition-mode downgrade to WPA2, the Dragonblood vulnerability class’s timing/cache side channels and password-partitioning issues in early implementations, H2E rejection thresholds), and the migration posture audit (which networks advertise SAE-only versus transition, which clients actually pick which, and where PMF is enforced). WPA3 is not “WPA2 but stronger” — it is a different authentication structure, and analyzing it means reasoning about active password-guessing constraints, per-implementation side-channel history, and the downgrade economics that dominate real deployments.

WPA3 is where the industry’s PSK story was supposed to end: SAE’s PAKE construction means every password guess requires an online interaction with the AP (rate-limited, observable), so the hashcat rig that dissolved WPA2 policies in WI2 has nothing to compute against. That promise is exactly why the deviations matter so much. Transition mode — practically mandatory for the first years of any migration — means most “WPA3” networks still speak WPA2 to anyone who asks, and downgrade tooling turns a migration-in-progress back into WI2’s capture-then-crack. Dragonblood (2019) demonstrated that early SAE implementations leaked password information through timing and cache side channels in the hunting-and-pecking password-to-element mapping, and through a partitioning attack on groups with small order elements [verify CVE list per vendor advisory] — teaching the durable lesson that PAKE security depends on implementation discipline (constant-time operations, hash-to-curve H2E, group selection), not just protocol choice. For defenders, WPA3 analysis is a migration-governance skill: WPA3-only where possible, transition-mode with eyes open, PMF mandatory, client-fleet capability measured, and downgrade exposure treated as a risk decision rather than a default. For attackers and assessors, the skill is measuring which WPA3 a network actually is — because “WPA3” on the marketing sheet and “SAE-only, H2E, PMF-enforced” on the wire are different security postures, and the audit that can tell them apart is the deliverable this module builds.

SAE replaces the 4-way handshake’s password-proof structure with a PAKE exchange:

SAE (DRAGONFLY) COMMIT EXCHANGE (simplified)
both sides derive password element PWE from password:
hunting-and-pecking (legacy): iterate counter, test if
candidate curve point exists (leaks timing -> patched
by constant-time + H2E hash-to-curve per WPA3 update)
H2E (modern): hash-to-curve maps password -> element
deterministically; rejection-sampling loop gone
AP -> STA COMMIT: scalar + element (masked with random)
STA -> AP COMMIT: same structure
both derive: shared secret from (own private, peer scalar,
peer element) -> KCK/PMK
AP -> STA CONFIRM: HMAC proof (token) -- mutual auth
STA -> AP CONFIRM: same
THEN: normal 4-way handshake carries the SAE-derived PMK
(GCMP-256 suites; PMF mandatory)
WHY OFFLINE CRACKING DIES:
guess password -> you still need peer's COMMIT scalars'
discrete logs to verify guess => each guess costs an
ONLINE exchange (observable, rate-limitable)

The downgrade surface is transition mode’s structural property: the beacon advertises both AKM suites (SAE and PSK), and a client that “can’t do” SAE associates with PSK — which means an attacker who can act as the AP (or manipulate the environment) pulls clients back into WI2’s capture-then-crack world. Client-side downgrade resistance exists in modern stacks (prefer SAE when advertised) but the network remains as weak as its weakest accepted mode, and tooling that masks SAE capability harvests WPA2 handshakes from dual-mode networks [verify current tooling]. The Dragonblood class (2019, vanhoef-style analysis) split into four practical issues: timing leakage in hunting-and-pecking (cache side channels revealing how many loop iterations the password needed), invalid-curve abuse on some groups, side-channel leaks in the confirm handshake’s group operations, and the partitioning attack against small-order elements — each with vendor patches landing through 2019-2020 and the protocol fix being H2E plus group restrictions [verify current status]. The analysis skill is checking which mitigations a deployment actually has: H2E adoption (beacon/IE evidence is limited — firmware-level), group selection (Brainpool curves prohibited, small groups rejected), and the modern H2C rejection threshold that bounds per-connection guessing. The GCMP-256 and PMF layers complete the posture: GCMP-256 replaces CCMP-128’s 128-bit block with 256-bit keys (AES-GCM), and PMF is mandatory in WPA3 — which structurally removes WI2’s deauth-forcing and protects against the management-frame classes WI7 catalogues. The migration audit is the module’s practical deliverable: per-AP AKM advertisement (SAE-only vs transition), per-client actual selection, PMF capability/requirement, and the downgrade-exposure quantification that turns “we’re migrating” into a measured state.

TechniqueWhat it doesToolingStatus
Transition-mode downgradeForce WPA2 AKM on dual-mode networkRogue/masquerade AP tooling [verify]Active surface
SAE handshake capture analysisVerify SAE-only posturescapy/tshark RSN parsePassive audit
Dragonblood side channelsTiming/cache leak on HnPResearch tooling [verify]Historical; patched stacks
Password partitioningSmall-order group exploitResearch [verify]Patched via group policy
H2E verificationConfirm hash-to-curve firmwareVendor docs/firmware auditSupply-chain audit
PMF enforcement checkRSN caps MFPR bit parsescapyAudit
Client selection measurementWhich AKM clients actually useCapture correlationMigration metric
SAE guess throttling testMeasure AP’s per-client rate limitAuthorized scripted attemptsLab-only
GCMP posture checkCipher suite parseRSN decodeAudit
Mixed-vendor WPA3 interopGroup/protocol mismatchesLab interop matrixCommon ops finding

wpa3scan.py — a WPA3 migration-posture analyzer: parses monitor-mode captures (scapy), classifies every AP by advertised AKM suites (SAE-only, transition, WPA2-only), extracts PMF requirement (RSN capabilities MFPR bit) and cipher suites (GCMP vs CCMP), correlates client associations against advertised modes to measure actual client selection, and emits the migration report. Dependency: scapy.

#!/usr/bin/env python3
"""wpa3scan.py — WPA3/SAE migration posture analyzer (pcap in).
Usage:
python wpa3scan.py capture.pcap [--json posture.json]
Classifies: sae-only | transition | wpa2-only | open/legacy
Parses RSN AKM suites, PMF caps (MFPR), cipher suites, and
client association choices vs advertised modes.
Deps: scapy.
"""
import argparse, json, sys
from collections import defaultdict
from scapy.all import PcapReader, Dot11, Dot11Beacon, \
Dot11AssoResp, Dot11Elt
AKM = {1: "802.1X", 2: "PSK", 3: "FT-1X", 4: "FT-PSK",
8: "SAE", 11: "1X-SHA256", 12: "PSK-SHA256",
24: "SAE-EXT-KEY"}
def walk_ies(pkt):
el = pkt.getlayer(Dot11Elt)
while el is not None:
yield el.ID, bytes(el.info)
el = el.payload.getlayer(Dot11Elt) \
if el.payload and \
isinstance(el.payload, Dot11Elt) else None
def parse_rsn(info):
"""RSN IE: version(2) suite(4) count+list caps(2)."""
if len(info) < 8:
return None
out = {"akm": [], "cipher": [], "mfpr": False,
"mfpc": False}
cs_count = info[2] | (info[3] << 8)
off = 4
for _ in range(min(cs_count, 8)):
if off + 4 > len(info):
return out
tid = info[off + 3]
out["cipher"].append(
{4: "CCMP-128", 8: "GCMP-128",
9: "GCMP-256", 10: "CCMP-256",
2: "TKIP"}.get(tid, f"t{tid}"))
off += 4
if off + 1 > len(info):
return out
ak_count = info[off]
off += 1
for _ in range(min(ak_count, 8)):
if off + 4 > len(info):
return out
aid = info[off + 3]
out["akm"].append(AKM.get(aid, f"a{aid}"))
off += 4
if off + 2 <= len(info):
caps = int.from_bytes(info[off:off + 2], "little")
out["mfpc"] = bool(caps & 0x0008)
out["mfpr"] = bool(caps & 0x0040)
return out
def classify(akms):
if "SAE" in akms or "SAE-EXT-KEY" in akms:
return "sae-only" if set(akms) <= {"SAE",
"SAE-EXT-KEY"} else "transition"
return "wpa2-only" if akms else "open/legacy"
def analyze(path):
aps = {}
assoc = [] # (client, ap, chosen_akm_hint)
with PcapReader(path) as reader:
for pkt in reader:
if Dot11 not in pkt:
continue
d11 = pkt[Dot11]
if Dot11Beacon in pkt:
bssid = d11.addr3
rec = aps.setdefault(bssid, {
"ssid": None, "rsn": None,
"wpa2_akm": None, "frames": 0})
rec["frames"] += 1
for ie_id, info in walk_ies(pkt):
if ie_id == 0 and info:
rec["ssid"] = info.decode(
errors="replace")
elif ie_id == 48:
rec["rsn"] = parse_rsn(info)
elif ie_id == 221 and info[:3] == \
b"\x00P\xf2": # MS WPA IE
rec["wpa2_akm"] = "WPA-IE"
elif Dot11AssoResp in pkt:
assoc.append({"client": d11.addr1,
"ap": d11.addr2})
for bssid, rec in aps.items():
rsn = rec.get("rsn") or {}
akms = rsn.get("akm", [])
rec["class"] = classify(akms)
rec["cipher"] = ",".join(rsn.get("cipher", []))
rec["pmf_required"] = rsn.get("mfpr", False)
rec["pmf_capable"] = rsn.get("mfpc", False)
# client selection: assoc targets vs classes
selection = defaultdict(lambda: {"sae": 0,
"non-sae": 0})
for a in assoc:
cls = aps.get(a["ap"], {}).get("class", "?")
if "sae" in cls:
selection[a["client"]]["sae"] += 1
elif cls in ("transition", "wpa2-only"):
selection[a["client"]]["non-sae"] += 1
return {"aps": aps, "assoc_count": len(assoc),
"client_selection": {k: dict(v) for k, v in
selection.items()}}
def main():
ap = argparse.ArgumentParser()
ap.add_argument("pcap")
ap.add_argument("--json")
args = ap.parse_args()
s = analyze(args.pcap)
counts = defaultdict(int)
print("== WPA3 migration posture ==")
for bssid, rec in sorted(s["aps"].items(),
key=lambda kv: -kv[1]["frames"]):
cls = rec["class"]
counts[cls] += 1
pmf = "PMF-req" if rec["pmf_required"] else (
"pmf-cap" if rec["pmf_capable"] else "no-pmf")
print(f" [{cls:<11}] {rec['ssid'] or '?':<22} "
f"{rec['cipher'] or '-':<18} {pmf}")
print("\nclass counts:",
dict(counts))
down_exposed = counts.get("transition", 0) + \
counts.get("wpa2-only", 0)
print(f"downgrade-exposed networks (transition or "
f"wpa2-only): {down_exposed}")
if s["client_selection"]:
print("\nclient selection (assoc vs class):")
for c, sel in list(s["client_selection"].items())[:8]:
print(f" {c} sae={sel['sae']} "
f"non-sae={sel['non-sae']}")
if args.json:
json.dump(s, open(args.json, "w"), indent=2,
default=str)
print(f"[+] {args.json}")
if __name__ == "__main__":
main()
  • parse_rsn() (lines 33-63) is a fuller RSN decoder than WI1’s: pairwise cipher list, AKM list, then the RSN capabilities word — where bit 3 (0x0008) is MFPC (PMF capable) and bit 6 (0x0040) is MFPR (PMF required). The required-vs-capable distinction is the audit’s teeth: MFPC-only networks protect when the client asks, MFPR networks require it — a policy difference invisible to tooling that only counts “PMF on”.
  • classify() (lines 66-71) implements the posture classes: SAE-only (AKM set contains only SAE variants — the genuine article), transition (SAE plus PSK — the exposure class), wpa2-only, open/legacy. The classification is the report’s core column.
  • The association-vs-class correlation (lines 96-107) measures what clients actually did: each AssocResp pairs a client with an AP whose class we know, so a client associating to transition-mode networks while SAE-only networks exist is a measured downgrade-selection fact (not a policy claim). The coarser hint — client-to-class rather than client-to-AKM — is stated honestly in the design; full AKM-per-association needs M1 parsing, listed as improvement #2.
  • The exposure summary (lines 155-157) is the metric the migration program tracks: transition + wpa2-only networks are the downgrade-exposed set, and the count’s trend is the migration’s honest progress bar.

The posture audit reaches the estate’s wireless program as its migration dashboard: per-AP class, PMF enforcement, cipher posture, and the client-selection facts — the four numbers that turn “we’re moving to WPA3” into a managed program with a progress bar. The downgrade-exposure count reaches risk management as a decision: transition mode is a business tradeoff (compatibility vs the WI2 attack surface), and the audit’s quantification (“11 of 14 SSIDs accept WPA2 clients, including the executive floor”) is what makes that tradeoff a deliberate choice with an owner. The client-selection data reaches endpoint policy: fleets whose devices pick non-SAE modes despite SAE availability (old drivers, misconfigured supplicants) are remediation targets no AP-side upgrade fixes. The Dragonblood history reaches the supply chain as questions the audit asks firmware teams: H2E support, group policy, patch level [verify per vendor] — because a PAKE’s security is implementation-bound, and the 2019 lessons are the checklist. The SAE-only network’s properties reach the defense story: capture-then-crack dies (WI2’s rig is useless), deauth-forcing dies (PMF mandatory), and the remaining password-guessing channel is online and rate-limited — which moves the estate’s residual wireless risk from “password choice measured in GPU-hours” to “guess throttling measured in attempts-per-hour”, a fundamentally better position that this audit is what proves you’ve actually reached.

  1. Audit — wpa3scan.py on authorized capture: 4 SAE-only, 9 transition, 3 wpa2-only networks
  2. Expose — transition-mode networks accept PSK: downgrade surface quantified at 12/16
  3. Select — target transition SSID on floor 3; lab rogue AP cloned with PSK-only beacon
  4. Harvest — clients drop to rogue/WPA2 shape; handshake captured (WI2 pipeline)
  5. Crack — offline rig against harvested material: policy measured in hours, not guesses
  6. Compare — SAE-only floor: same rig produces nothing; PAKE holds; online-only guessing observed
  7. Report — downgrade economics quantified per floor; migration priority list by exposure
  8. Remediate — WPA3-only on high-value SSIDs, PMF required, client fleet audited for SAE support

downgradecheck.py — the client-side downgrade exposure checker: from a capture on a mixed estate, it correlates each client’s associations with each AP’s advertised capabilities and produces a per-client downgrade-exposure profile (devices that chose PSK when SAE was available on the same network, devices whose fleet drivers lack SAE). Output feeds endpoint-remediation lists. Dependency: scapy.

  • Client-centric, not AP-centric. wpa3scan ranks networks; this tool ranks devices — because the remediation for downgrade exposure is often a driver update on 40 laptops, and the list is the work order.
  • Same-network exposure, not same-SSID guesswork. Exposure requires the client associating to a network class that offered SAE (transition), not merely “touched a WPA2 network somewhere” — specificity here prevents false endpoint shaming.
  • Supplicant fingerprinting by behavior. Driver capability is inferred from selection behavior across observations (a client that never picks SAE across N opportunities), not from vendor claims — measured capability, with N stated per client.
  • Report as worklist. Output is a per-client remediation line (update driver / reconfigure / replace) ranked by exposure count, not a narrative.
#!/usr/bin/env python3
"""downgradecheck.py — client downgrade-exposure profile (pcap in).
Usage:
python downgradecheck.py capture.pcap [--json clients.json]
Deps: scapy.
"""
import argparse, json, sys
from collections import defaultdict
from scapy.all import PcapReader, Dot11, Dot11Beacon, \
Dot11AssoResp, Dot11Elt
def walk_ies(pkt):
el = pkt.getlayer(Dot11Elt)
while el is not None:
yield el.ID, bytes(el.info)
el = el.payload.getlayer(Dot11Elt) \
if el.payload and \
isinstance(el.payload, Dot11Elt) else None
def akm_set(info):
"""AKM type bytes from an RSN IE."""
if len(info) < 8:
return set()
cs_count = int.from_bytes(info[2:4], "little")
off = 4 + cs_count * 4
if off >= len(info):
return set()
ak_count = info[off]
off += 1
out = set()
for _ in range(min(ak_count, 8)):
if off + 4 > len(info):
break
out.add(info[off + 3])
off += 4
return out
SAE_TYPES = {8, 24}
PSK_TYPES = {2, 4, 12}
def analyze(path):
ap_akms = {} # bssid -> set(akm types)
ap_ssid = {}
assoc = [] # (client, bssid)
with PcapReader(path) as reader:
for pkt in reader:
if Dot11 not in pkt:
continue
d11 = pkt[Dot11]
if Dot11Beacon in pkt:
bssid = d11.addr3
for ie_id, info in walk_ies(pkt):
if ie_id == 0 and info:
ap_ssid[bssid] = info.decode(
errors="replace")
elif ie_id == 48:
ap_akms[bssid] = akm_set(info)
continue
if Dot11AssoResp in pkt:
assoc.append((d11.addr1, d11.addr2))
exposure = defaultdict(lambda: {
"psk_assoc": 0, "sae_assoc": 0,
"networks": set()})
for client, bssid in assoc:
akms = ap_akms.get(bssid, set())
if not akms:
continue
if akms & SAE_TYPES and akms & PSK_TYPES:
# transition network: client choice matters
# assoc-response itself doesn't carry AKM;
# count as exposure observation (improvement #2
# refines with M1/EAPOL analysis)
exposure[client]["networks"].add(
ap_ssid.get(bssid, bssid))
exposure[client]["psk_assoc"] += 1
elif akms & PSK_TYPES:
exposure[client]["networks"].add(
ap_ssid.get(bssid, bssid))
exposure[client]["psk_assoc"] += 1
return exposure
def main():
ap = argparse.ArgumentParser()
ap.add_argument("pcap")
ap.add_argument("--json")
args = ap.parse_args()
exposure = analyze(args.pcap)
if not exposure:
print("[-] no associations with known AP "
"capabilities found")
return
print("== client downgrade exposure (transition/WPA2 "
"associations) ==")
ranked = sorted(exposure.items(),
key=lambda kv: -kv[1]["psk_assoc"])
for client, e in ranked:
nets = ", ".join(sorted(e["networks"])[:3])
print(f" {client} psk-assocs={e['psk_assoc']:<3} "
f"nets={nets}")
print("\nremediation view: update SAE support on "
"clients with psk-assocs > 0 on transition nets; "
"verify drivers post-update (improvement #2 adds "
"per-assoc AKM confirmation)")
if args.json:
json.dump({k: {"psk_assoc": v["psk_assoc"],
"sae_assoc": v["sae_assoc"],
"networks": sorted(v["networks"])}
for k, v in exposure.items()},
open(args.json, "w"), indent=2)
if __name__ == "__main__":
main()
  • akm_set() (lines 22-38) extracts raw AKM type bytes from the RSN IE — the transition classification needs the raw set (SAE types {8, 24} co-present with PSK types {2, 4, 12}), not the human names.
  • The exposure logic (lines 63-77) counts associations against capability-known APs and states its own limit in a comment: the association response itself doesn’t announce which AKM the client used — the association to a transition network is exposure-grade evidence, and confirming the exact AKM per association requires M1/EAPOL analysis, listed as improvement #2 rather than faked.
  • The worklist output (lines 100-104) ranks clients by PSK associations and names the networks — the endpoint-remediation work order, with the network context that tells ops which supplicant/driver conversation to have.
  • The networks set (line 69) de-duplicates SSIDs per client — one device touching the same transition network 50 times is one remediation conversation, not fifty.

capture the mixed estate (authorized airspace, dual windows)

Section titled “capture the mixed estate (authorized airspace, dual windows)”

$ airodump-ng -w mixed -c 1,6,11 wlan0mon $ python wpa3scan.py mixed-01.cap —json posture.json == WPA3 migration posture == [sae-only ] LabCorp-Corp GCMP-256 PMF-req [transition ] LabCorp-Guest CCMP-128 pmf-cap [wpa2-only ] legacy-printer CCMP-128 no-pmf class counts: {‘sae-only’: 4, ‘transition’: 9, ‘wpa2-only’: 3} downgrade-exposed networks (transition or wpa2-only): 12

$ python downgradecheck.py mixed-01.cap —json clients.json == client downgrade exposure (transition/WPA2 associations) == 3c:5a:b4:xx:xx:0f psk-assocs=4 nets=LabCorp-Guest a0:88:b4:xx:xx:22 psk-assocs=2 nets=legacy-printer

the migration worklist: update the two clients’ SAE support

Section titled “the migration worklist: update the two clients’ SAE support”

then re-capture next window and confirm the counts drop

Section titled “then re-capture next window and confirm the counts drop”

WIDS products increasingly ship WPA3-era signatures — downgrade-inducing beacon manipulation, SAE exchange anomalies, transition-abuse patterns [verify per vendor] — and the audit’s value is testing whether your WIDS actually sees the downgrade attempts the posture implies are possible. The Dragonblood lesson reaches patch management as an audit line: SAE implementations with pre-H2E firmware are side-channel-exposed, and firmware inventory (which AP models, which SAE implementation) belongs in the migration plan. Your own tooling hygiene: both tools are pcap readers; the downgrade-inducing steps (rogue AP masquerade) are upstream and engagement-gated. Wazuh/SIEM mapping of WIDS downgrade alerts depends on vendor integration [verify]; Suricata/BT12 carry the management-frame anomaly layer. The standing metrics this module leaves the program: SAE-only percentage (the migration progress bar), PMF-required percentage, downgrade-exposed network count (the risk surface), and client SAE-selection rate (the fleet health) — four numbers that make the WPA3 story measurable from now on.

  1. Per-association AKM confirmation: parse the EAPOL M1 that follows each association to record the actual AKM used, upgrading the exposure counts from association-grade to mode-grade.
  2. SAE exchange parsing: detect COMMIT/CONFIRM frames (Auth algorithm 3) and count SAE exchanges per client — true SAE-usage telemetry.
  3. Dragonblood patch audit export: per-AP model/firmware inventory cross-referenced against vendor advisories [verify data source].
  4. Transition-timer policy check: flag networks still in transition past their stated migration window.
  5. Fleet suppliance matrix: client OUI → driver-era heuristics for predicted SAE capability, pre-ranking the remediation list.
SignalSourceIDNotes
Downgrade-inducing beacon anomalyWIDSvendor signatures [verify]Transition-abuse detection
SAE exchange failures/burstsWIDS/AP logsvendor logs [verify]Online-guess attempts
MFPR absent on WPA3 SSIDConfig audit (this tool)posture findingPMF enforcement gap
Transition mode past windowConfig auditmigration findingCompatibility debt
Clients selecting non-SAEdowngradecheckendpoint worklistDriver/supplicant gap
Dragonblood-era firmwareFirmware inventorypatch findingSupply-chain audit
GCMP absent on WPA3 SSIDRSN parseposture findingSuite posture
SAE rate-limit thresholdsAP config reviewhardening checkOnline-guess braking
SAE-only % trendQuarterly re-auditstanding metricMigration progress bar
Wazuh join of WIDS WPA3 alertsHIDS/SIEM[verify rule IDs per deployment]Vendor integration

Attackers attack WPA3’s deployment reality, not its cryptography. Transition-mode exploitation is the dominant move: no need to break SAE when the network still speaks PSK — masquerade, downgrade, and run WI2’s pipeline; the counter is finishing the migration (SAE-only), not hardening the transition. Client coercion: clients that prefer SAE can be coerced by beacons that omit it (masquerading APs) — countered by client-side “refuse WPA2 for known-WPA3 networks” policies where the OS supports them [verify per platform] and by corp-device supplicant profiles. PAKE rate-limit rotation: SAE’s online guessing is braked per-client per-AP — countered by distributed guessing across spoofed client identities; countered back by AP-side anomaly correlation (many identities, one radio location) and H2C rejection thresholds that brake per-connection regardless of identity. Implementation shopping: pre-H2E firmwares with side-channel residue (Dragonblood class) make some APs’ SAE leak-able again — countered by firmware audits and procurement questions, not by network configuration. PMF absence exploitation: WPA3 networks that don’t require PMF (capable-but-not-required) still permit management-frame abuse — countered by MFPR enforcement, which the audit measures directly. The structural point: WPA3’s security is a posture (SAE-only + MFPR + H2E firmware + patched groups), not a checkbox — and the audit tools in this module exist because the posture’s four parts fail independently and silently.

⚠️ What caught me: I audited a flagship “WPA3-migrated” site expecting the done deal their change ticket described — and wpa3scan showed their corporate SSID was transition mode with MFPC-only, and three of their SAE-only floors were running pre-H2E firmware that the vendor’s own advisory had flagged two years earlier. The change ticket was accurate about what was deployed (WPA3 everywhere) and silent about what was enforced (nothing). The report I delivered redefined the program’s language: WPA3-enabled ≠ WPA3-enforced, and the audit’s four posture columns became the site’s real progress bar. Since then I treat every security-migration claim as an advertisement until the wire-level parse proves the posture — the frames don’t do marketing.

  1. Stand up the WPA3 lab: one SAE-only SSID, one transition SSID (hostapd supports both [verify config]), clients of each era; capture and run wpa3scan.py — confirm the posture classification matches the config you wrote.
  2. Downgrade drill: point a WPA2-capable lab client at the transition SSID with SAE disabled in its supplicant; capture and confirm the client’s non-SAE association appears in downgradecheck.py’s worklist.
  3. PMF experiment: toggle MFPC-only vs MFPR on the lab AP and re-run the analyzer; verify the capability-vs-required bits in the parse — the distinction that changes the risk story.
  4. SAE exchange observation: capture a full SAE association and identify the COMMIT/CONFIRM auth frames (algorithm 3) in the capture — the PAKE handshake, seen once so the protocol is concrete.
  5. Migration report: from one capture, produce the four standing metrics (SAE-only %, PMF-required %, downgrade-exposed count, client SAE-selection rate) and write the one-paragraph migration status they imply — the audit deliverable, practiced.
  • Technical: Explain why SAE removes offline-verifiable material — what does a password guess cost in WPA2 vs WPA3, precisely?
  • Technical: What are the RSN capability bits for PMF, and why does MFPR-vs-MFPC change the security story?
  • Scenario: A site claims full WPA3 migration; your audit shows 9 of 16 SSIDs in transition mode with two pre-H2E AP models. What do you report, to whom, and in what language?
  • Conceptual: What did Dragonblood teach about PAKE implementation versus protocol security, and which specific mitigations followed?
  • Conceptual: Why is transition mode structurally necessary for migration and structurally incompatible with the WPA3 promise — and how do mature programs manage that tension?
  • Deep-dive: Design the WPA3 migration audit program: which capture windows, which posture metrics, which client-fleet measurements, and what gates define “migrated”?
  • Practical: The estate cannot leave transition mode for a year (legacy clients). What compensating controls shrink the downgrade surface in the meantime?

WI1 (Wireless Fundamentals & Recon (802.11)) · WI2 (WPA2-PSK Cracking (Handshake Capture)) · WI4 (Enterprise Wi-Fi & Evil Twin) · F4 (Cryptography Fundamentals) · BT12 (Wireless IDS)