Beefy Boxes and Bandwidth Generously Provided by pair Networks
XP is just a number
 
PerlMonks  

[SOLVED] HMAC_SHA1 Implementation for WPA

by return0 (Acolyte)
on Jun 20, 2014 at 16:06 UTC ( [id://1090649]=perlquestion: print w/replies, xml ) Need Help??

return0 has asked for the wisdom of the Perl Monks concerning the following question:

Hello monks! :) I am currently working on a WPA implementation in Perl, - just as a way for me to learn about not only the protocol, but Perl too, because that's what life's all about!

Anyways, I have read loads of documentation and source code, including that from Aircrack-NG. Across the board, the docs don't really match up really and are very vague, at best. So this is what I came up with. I used Net::Pcap with WiFi to capture a perfect EAPOL and beacon with which I passed to Aircrack-NG with a word list and my password inside that list. For those unfamiliar with aircrack-ng, it produces the PTK transient Key, EAPOL, and PMK. I can do the PMK, that's incredibly easy, and you will see from my simple code below. The PTK part is where I am having trouble it seems. I have hard-coded the Anonce, Snonce, AP-MAC (BSSID), and Station MAC into the code as well.

The way it seems to work in practice is that the PMK is used to create the PTK, using a function like so (taken directly from Aircrack-NG):
/* pre-compute the key expansion buffer */ memcpy( pke, "Pairwise key expansion", 23 ); if( memcmp( ap->wpa.stmac, ap->bssid, 6 ) < 0 ) { memcpy( pke + 23, ap->wpa.stmac, 6 ); memcpy( pke + 29, ap->bssid, 6 ); } else { memcpy( pke + 23, ap->bssid, 6 ); memcpy( pke + 29, ap->wpa.stmac, 6 ); } if( memcmp( ap->wpa.snonce, ap->wpa.anonce, 32 ) < 0 ) { memcpy( pke + 35, ap->wpa.snonce, 32 ); memcpy( pke + 67, ap->wpa.anonce, 32 ); } else { memcpy( pke + 35, ap->wpa.anonce, 32 ); memcpy( pke + 67, ap->wpa.snonce, 32 ); } // then to get the PTK, they used the string above, like so: /* compute the pairwise transient key and the frame MIC */ for (i = 0; i < 4; i++) { pke[99] = i; HMAC(EVP_sha1(), pmk[j], 32, pke, 100, ptk[j] + i * 20 +, NULL); /* unsigned char *HMAC(const EVP_MD *evp_md, const vo +id *key, int key_len, const unsigned char *d, int n, unsigned char *md, unsigned int *md_len); */ }


I have placed in comments a definition of that simple HMAC() function. I tried to make my code mimic, as closely as I could, this function (and as I said, I have hard coded my PMK (which I know is correct)) but simply fall short and cannot replicate the correct PTK given to me from Aircrack-NG or CowPatty :( Below is my code:

#!/usr/bin/perl -w use strict; use Crypt::PBKDF2; use Digest::HMAC_SHA1 qw(hmac_sha1 hmac_sha1_hex); use List::Util qw( min max ); my $usage = "Usage: perl wpa2hex <ESSID> <PASSPHRASE>\n"; my $essid = shift || die $usage; my $passwd = shift || die $usage; # pack the next few registers up my $smac = pack("H*","489d2477179a"); # station MAC (RIM) my $amac = pack("H*","001dd0f694b0"); # AP MAC (Arris) my $snonce = pack("H*","143fbb4333341f36e17667f88aa02c5230ab82c508cc4b +d5947dd7e50475ad36"); my $anonce = pack("H*","87f2718bad169e4987c94255395e054bcaf77c8d791698 +bf03dc85ed3c90832a"); my $pbkdf2 = Crypt::PBKDF2->new( hash_class => 'HMACSHA1', # HMAC-SHA1 hash_args => { sha_size => 512, },iterations => 4096, salt_len => length($essid), output_len => 32 ); my $pmk = uc($pbkdf2->PBKDF2($essid, $passwd)); my $pbkdf2b = Crypt::PBKDF2->new( hash_class => 'HMACSHA1', # HMAC-SHA1 hash_args => { sha_size => 512, },iterations => 4096, salt_len => length($pmk), output_len => 20 ); print "Master Key: ",uc(unpack("H*",$pmk)),"\n"; prf512(); sub prf512{ my $a = "Pairwise key expansion"; # application-specific data my $b = $amac.$smac.$snonce.$anonce; my $r = ""; for(my $i=0;$i<4;$i++){ my $data = $a."\x00".$b.$i; $r .= $pbkdf2b->PBKDF2($pmk,$data); } print "Transient Key: ",uc(unpack("H*",$r)),"\n"; return; }

I simply used Wikipedia and this site: http goo.gl UVXhGi as a reference. That site, however states that you need to use a "0 byte" between the "Pairwise key expansion" string and the first MAC address in the data passed to the function. Aircrack-NG's source code, as you see above, does not! Anyways, I am not trying to re-invent the wheel here, I am just a student of 802.11, Perl, C and life.

If anyone has any idea as to where I am wrong, please, the help would be greatly appreciated. Thank you in advance, and peace be with you monks <3

Replies are listed 'Best First'.
Re: HMAC_SHA1 Implementation for WPA
by cord-bin (Friar) on Jun 23, 2014 at 09:43 UTC
    Hi, The IEEE 802.11i-2004 says:
    The PTK is generated by concatenating the following attributes: PMK, A +P nonce (ANonce), STA nonce (SNonce), AP MAC address, and STA MAC add +ress. You should better check this as the order is important.
    This isn't exactly what you are doing here :
    my $a = "Pairwise key expansion"; # application-specific data my $b = $amac.$smac.$snonce.$anonce; my $r = ""; for(my $i=0;$i<4;$i++){ my $data = $a."\x00".$b.$i;
    The product is then put through PBKDF2-SHA1 as the cryptographic hash function. The PBKDF2 key derivation function has five input parameters:
    DK = PBKDF2(PRF, Password, Salt, c, dkLen)
    where:
  • PRF is a pseudorandom function of two parameters with output length hLen (e.g. a keyed HMAC)
  • Password is the master password from which a derived key is generated
  • Salt is a cryptographic salt
  • c is the number of iterations desired
  • dkLen is the desired length of the derived key
  • DK is the generated derived key

  • For more on pbkdf2 used in perl check this link PBKDF2 crypt
    Here's some code used to do this pbkdf2.pl
      Hi, there actually have been quite a few amendments since 2004, thankfully. You may want to look into a more updated document. As far as "five input parameters" goes, can you not see my 5 parameters? Also, I know that the order is important, as all documentation on building the string data to send to the HMAC-SHA1 uses min() max() functions in its concatentation syntax. This is why I have them hard-coded at the moment.
      In a similar way, the computation of the pairwise temporal keys is wri +tten: PRF-512(PMK, "Pairwise key expansion", MAC1||MAC2||Nonce1||Nonce2) Here MAC1 and MAC2 are the MAC addresses of the two devices where MAC1 + is the smaller (numerically) and MAC2 is the larger of the two addre +sses. Similarly, Nonce1 is the smallest value between ANonce and SNon +ce, while Nonce2 is the largest of the two values.

      During my more recent research however, I did find a Python implementation which shows similar difficulties, and might shed some insight on your documentation problem - as he did successfully calculate the PTK in Python in a *very* similar way. http-stackoverflow.com-questions-12018920/
      Maybe this is a key, but I do use the pack() function, which seems to be the same. I am going to read the .cap file directly using Net::Pcap now and check if the "string" is somehow causing the issue.

      Thanks.
      I have added code into aircrack-ng.c from the latest sauce which writes the pke[] array to a file as so:
      FILE *pkef; pkef=fopen("pke.txt", "a"); for(j=0; j<nparallel; ++j) { /* compute the pairwise transient key and the +frame MIC */ for (i = 0; i < 4; i++) { pke[99] = i; HMAC(EVP_sha1(), pmk[j], 32, pke, 100, + ptk[j] + i * 20, NULL); int kk = 0; fprintf(pkef,"==> "); for(kk = 0;kk<=99;kk++){ fprintf(pkef, "%i ",pke[kk]); } fprintf(pkef,"\n---------------------\ +n"); }
      After which I converted it to hex, and the output was exactly that which it should be: "Pairwise key expansion" . 0x00 . $mac1 . $mac2 . $nonce1 . $nonce2 . 0x00; # where $mac1 < $mac2 && $nonce1 < $nonce2. Now, I used this hex in Perl and tried hmac_sha1 and even PBKDF2 again, to no avail (using the pre-computed PMK as the key just like Aircrack-ng). I have tried 1 and 4096 passes, I know I am using CCMP-WPA2, I know what cowpatty and aircrack-ng produce, i have also tried reading the raw bytes directly from Net::Pcap but I cannot seem to get the correct first 128 bits of the PTK.. I bet it has to do with not being able to replicate that danged HMAC() C function properly. There's a description of it in the openSSL documentation as:
      unsigned char *HMAC(const EVP_MD *evp_md, const void *key, int key_len, const unsigned char *d, int n, unsigned char *md, unsigned int *md_len);
      Could it simply not be possible? Thanks monks! :)
      Okay, so I feel like I am getting a bit closer. I got the Python program from the link (http-stackoverflow.com-questions-12018920) to work, even though I don't know python, and watched exactly what it was doing.
      import hmac import hashlib from hashlib import sha1 import binascii import sys A = "Pairwise key expansion" APmac = binascii.a2b_hex("001dd0f694b0") Clientmac = binascii.a2b_hex("489d2477179a") ANonce = binascii.a2b_hex("87f2718bad169e4987c94255395e054bcaf77c +8d791698bf03dc85ed3c90832a") SNonce = binascii.a2b_hex("143fbb4333341f36e17667f88aa02c5230ab82 +c508cc4bd5947dd7e50475ad36") B = min(APmac,Clientmac)+max(APmac,Clientmac)+min(ANonce,SNo +nce)+max(ANonce,SNonce) 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),sha1) i+=1 R = R+hmacsha1.digest() print "R: ",binascii.b2a_hex(hmacsha1.digest()),"\n" return R[:blen] pmk = binascii.a2b_hex("9051ba43660caec7a909fbbe6b91e4685f1457b5a2e236 +60d728afbd2c7abfba") ptk = customPRF512(pmk,A,B) print "pmk:\t\t",binascii.b2a_hex(pmk),"\n" print "ptk:\t\t",binascii.b2a_hex(ptk[0:16]),"\n" print "A: ",binascii.b2a_hex(A),"\n" print "CHR(0x00): ",chr(0x00),"\n" print "B: ",binascii.b2a_hex(B),"\n" print "key: ",binascii.b2a_hex(pmk),"\n" print "CHR(0): ",chr(0),"\n" i = 0 string = A+chr(0x00)+B+chr(i) print "STRING: ",binascii.b2a_hex(string);

      It produced the correct PTK from my PMK using the hmac_sha1 class. You can see it from "directly from python loop" in the code.
      #!/usr/bin/perl -w use strict; use Digest::HMAC_SHA1 qw(hmac_sha1 hmac_sha1_hex); my $pmk = pack("H*","9051ba43660caec7a909fbbe6b91e4685f1457b5a2e23660d +728afbd2c7abfba"); my $a; foreach(split("","Pairwise key expansion")){ $a .= sprintf("%x",ord($_)); } # 5061697277697365206b657920657870616e73696f6e OK my $i = 0x00; # 00 in hex my $smac = pack("H*","489d2477179a"); my $amac = pack("H*","001dd0f694b0"); my $snonce = pack("H*","143fbb4333341f36e17667f88aa02c5230ab82c508cc4b +d5947dd7e50475ad36"); my $anonce = pack("H*","87f2718bad169e4987c94255395e054bcaf77c8d791698 +bf03dc85ed3c90832a"); my $b = $amac.$smac.$snonce.$anonce; # Directly from Python code in for loop (without spaces): # 5061697277697365206b657920657870616e73696f6e 00 001dd0f694b0 + 489d2477179a1 # 43fbb4333341f36e17667f88aa02c5230ab82c508cc4bd5947dd7e50475a +d36 # 87f2718bad169e4987c94255395e054bcaf77c8d791698bf03dc85ed3c90 +832a 00 my $hd = $a.$i.$b.$i; my $digest = hmac_sha1($hd,$pmk); print unpack("H*",$digest)."\n"; # according to docs: $digest = hmac_s +ha1($data, $key); # does not come out as 9287f887faade9257f5a806309a2bac8956fcbec like +hmac_sha1 from Python ?
      I am almost sure that the pack() fucntion in Perl is similar to his a2b_hex() method as I have altered his Python code to print it for each iteration. As you can see the "Pairwise key expansion" is turned into hex via each character's ascii code, which I did with sprintf and ord. That comes out to:

      5061697277697365206b657920657870616e73696f6e

      which is correct. Is the pack() function argument wrong? is the hmacsha1 not the same as in Python? I am so lost right now. :( Thanks monks!
        I am trying to generate hmac_sha256 PTK for 11w client . Can anybody provide similar function for sha256_prf ?
Re: HMAC_SHA1 Implementation for WPA
by return0 (Acolyte) on Jun 28, 2014 at 04:06 UTC

    Eureka!

    $pmk = pack("H*","9051ba43660caec7a909fbbe6b91e4685f1457b5a2e23660d728 +afbd2c7abfba"); $hd = pack("H*","5061697277697365206b657920657870616e73696f6e00001dd0f +694b0489d2477179a143fbb4333341f36e17667f88aa02c5230ab82c508cc4bd5947d +d7e50475ad3687f2718bad169e4987c94255395e054bcaf77c8d791698bf03dc85ed3 +c90832a00"); my $digest = hmac_sha1($hd,$pmk); print unpack("H*",$digest)."\n";
    calculating these values is easy. Not sure where I was going so wrong, but the PTK outpout now matches Aircrack-NG and Cowpatty! I'll mark this as solved.
    May peace be with you monks!

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1090649]
Front-paged by Arunbear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others musing on the Monastery: (3)
As of 2024-04-25 10:16 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found