The few weaknesses inherent within the authentication handshake process for WPA/WPA2 PSKs have been known for a long time. This blog post does not serve anything that is new or has not been previously seen in the wild or conference talks and actually references other sites (such as RFCs) that can supply further information. It does, however, provide some clarity in to what is actually performed during the authentication and thus cracking process, but was mainly an exercise for me to learn how everything works at a lower level. Perhaps it will be useful to someone else in the same scenario.
During the authentication process the supplicant (client) and authenticator (access point) each attempt to prove that they independently know the pre-shared-key (`PSK`) passphrase without disclosing the key directly. This is done by each encrypting a message using the Pairwise-Master-Key (`PMK`) that they have generated, transmitting each way, and then decrypting the message they've each received. The four-way handshake is used to establish a new key called the Pairwise-Transient-Key (`PTK`), which is comprised of the following concatenated data:
- Pairwise Master Key
- Authenticator Nonce
- Supplicant Nonce
- Authenticator MAC Address
- Supplicant MAC Address
The result is then processed through a Pseudo-Random-Function (PRF). Another key that is used for decrypting multicast traffic, named the Group-Temporal-Key, is also created during this handshake process.
Actual Handshake Process
- Initially the access point transmits an ANonce key to the client within the first handshake packet.
- The client then constructs its SNonce, along with the Pairwise-Transient-Key (PTK), and then submits the SNonce and Message Integrity Code (MIC) to the access point. [!]
- Next the access point constructs the Group-Temporal-Key, a sequence number that is used to detect replay attacks on the client, and a Message Integrity Code (MIC).
- Lastly the client then sends an acknowledgement (ACK) to the access point.
[!] At this point an attacker would have been able to intercept enough of the handshake to perform a password cracking attack.
Construction of the PMK
Pairwise-Master-Keys are used during the creation of the Pairwise-Transient-Keys and are never actually transmitted across the network. They are derived from the Pre-Shared-Keys (Enterprise WiFi uses a key created by EAP, but that is out of scope for this article) along with the other information such as SSID, SSID Length. The PMKs are created using the Password-Based Key Derivation Function #2 (PBKDF2), with the SHA1 hashing function used with HMAC as the message authentication code:
PMK = PBKDF2(HMAC−SHA1, PSK, SSID, 4096, 256)
HMAC-SHA1 is the Pseudo Random Function used, whilst 4096 iterations of this function are used to create the 256 bit PMK. The SSID is used as a salt for the resulting key, and of course the PSK (passphrase in this instance) is used as the basis for this entire process.
The HMAC function used:
H(K XOR opad, H(K XOR ipad, passphrase))
Further information on HMAC-SHA1 from RFC2104 can be seen below, but is out of my depth:
opad = 0x5C * B ipad = 0x36 * B (1) append zeros to the end of K to create a B byte string (e.g., if K is of length 20 bytes and B=64, then K will be appended with 44 zero bytes 0x00) (2) XOR (bitwise exclusive-OR) the B byte string computed in step (1) with ipad (3) append the stream of data 'text' to the B byte string resulting from step (2) (4) apply H to the stream generated in step (3) (5) XOR (bitwise exclusive-OR) the B byte string computed in step (1) with opad (6) append the H result from step (4) to the B byte string resulting from step (5) (7) apply H to the stream generated in step (6) and output the result
Here is a simple Python script that can be used to compute the raw key from the SSID and PSK passphrase. Within the Python module I've used (can be installed via python-pip) the default MAC and hash algorithm is HMAC-SHA1:
#! /usr/bin/env python from pbkdf2 import PBKDF2 ssid = raw_input('SSID: ') passphrase = raw_input('Passphrase: ') print ("Pairwise Master Key: " + PBKDF2(passphrase, ssid, 4096).read(32).encode("hex"))
Construction of the PTK
The creation of the Pairwise-Transient-Keys is performed via a another PRF (using an odd combination of SHA1, ending in a 512 bit string), which uses a combination of the PMK, AP MAC Address, Client MAC Address, AP Nonce, Client Nonce. The result is this 512 bit Pairwise-Transient-Key, which is actually a concatenation of five separate keys and values, each with their own purpose and use:
- Key Confirmation Key (`KCK`) - Used during the creation of the Message Integrity Code.
- Key Encryption Key (`KEK`) - Used by the access point during data encryption.
- Temporal Key (`TK`) - Used for the encryption and decryption of unicast packets.
- MIC Authenticator Tx Key (`MIC Tx`) - Only used with TKIP configurations for unicast packets sent by access points.
- MIC Authenticator Rx Key (`MIC Rx`) - Only used with TKIP configurations for unicast packets sent by clients.
The resulting order:
- 128 bits -.- 128 bits -.- 128 bits -.- 64 bits -.- 64 bits - | KCK | KEK | TK | MIC Tx | MIC Rx |
The only reference to a usable PRF512 function within Python was an excerpt of code from a question on Stack Overflow from back in 2012:
def customPRF512(key,A,B): blen = 64 i = 0 R = '' while i<=((blen*8+159)/160): hmacsha1 = hmac.new(key,A+chr(0x00)+B+chr(i),hashlib.sha1) i+=1 R = R+hmacsha1.digest() return R[:blen]
Some sample code just to get a visualisation of what happens in the background:
#! /usr/bin/env python import hmac,hashlib,binascii,sha from pbkdf2 import PBKDF2 def customPRF512(key,A,B): blen = 64 i = 0 R = '' while i<=((blen*8+159)/160): hmacsha1 = hmac.new(key,A+chr(0x00)+B+chr(i),hashlib.sha1) i+=1 R = R+hmacsha1.digest() return R[:blen] ssid = raw_input('SSID: ') passphrase = raw_input('Passphrase: ') ap_mac = binascii.a2b_hex(raw_input("AP MAC: ")) s_mac = binascii.a2b_hex(raw_input("Client MAC: ")) anonce = binascii.a2b_hex(raw_input("ANonce: ")) snonce = binascii.a2b_hex(raw_input("SNonce: ")) key_data = min(ap_mac,s_mac) + max(ap_mac,s_mac) + min(anonce,snonce) + max(anonce,snonce) pke = "Pairwise key expansion" key_data = min(ap_mac,s_mac) + max(ap_mac,s_mac) + min(anonce,snonce) + max(anonce,snonce) pmk = PBKDF2(passphrase, ssid, 4096).read(32) ptk = customPRF512(pmk,pke,key_data).encode('hex') print ("\nPMKey: " + pmk) print ("PTKey: " + ptk) print ("KCK: " + ptk[0:16])
The PMK and PTK are then printed to the terminal, with the first 16 bytes of the PTK being the KCK.
What is actually computed for cracking?
Once the second packet of the handshake has been captured an attacker has enough information to attempt to compute the Pairwise-Transient-Key (using an assumed PSK passphrase), which can then be used to extract the Key-Confirmation-Key and compute the Message Integrity Code. It is this MIC that is used during the comparison with the genuine MIC to determine the validity of the assumed PSK.
This whole process is re-run for every dictionary entry (or brute force attempt) in during password cracking, which is the reason why for the slow performance of Hashcat, Cowpatty, and John The Ripper (although I still manage 100k hashes P/s with Oclhashcat, which goes to show how fantastically optimised Atom's code is).
The MIC is calculated using HMAC_MD5, which takes its input from the KCK Key within the PTK. Unfortunately I wasn't able to come up with some Python code to compute the MIC, even after reviewing aircrack-ng and Cowpatty source code (my C skills are severely lacking). Expand on the above and let me know if anyone has an idea!
If anything, knowing the amount of computation that is performed for each attempt at comparing the MICs puts me at somewhat ease regarding the security of using PSK auth on personal networks, however it does prove how invaluable random passphrases are within various cryptographic implementations such as this, especially passphrases that are longer and contain more entropy. Use a 15 character passphrase for your PSK, which includes a combination of upper and lower alpha, numeric, and special characters, which isn't a dictionary word. Oh, and also change it regularly. If I or anyone else happen to crack your passphrase, then an attacker wouldn't get much use of it is void should they go back there in a months time can't connect because it's changed to a new value.
More importantly, don't use PSK authentication for your corporate networks. Whilst there are some vulnerabilities within certain EAP configurations they are a lot easier to squash than an offline attack such as what is capable against PSKs.
If any of the aforementioned information is incorrect please feel free to drop me an email and I'll make the necessary amendments and credit appropriately.
I have used some external sources to further understand these processes. They are in no particular order: