1#!/usr/bin/env perl
2
3##
4## Author......: See docs/credits.txt
5## License.....: MIT
6##
7
8use strict;
9use warnings;
10
11use Crypt::PBKDF2;
12use Digest::MD5  qw (md5);
13use Digest::SHA  qw (sha1 sha256);
14use Digest::HMAC qw (hmac hmac_hex);
15use Digest::CMAC;
16
17sub module_constraints { [[8, 63], [-1, -1], [-1, -1], [-1, -1], [-1, -1]] }
18
19sub module_generate_hash
20{
21  my $word   = shift;
22  my $salt   = shift;
23  my $type   = shift // random_number (1, 2);
24  my $macap  = shift;
25  my $macsta = shift;
26  my $essid  = shift;
27  my $anonce = shift;
28  my $eapol  = shift;
29  my $mp     = shift;
30
31  my $hash;
32
33  if ($type == 1)
34  {
35    if (!defined ($macap))
36    {
37      $macap = unpack ("H*", random_bytes (6));
38    }
39
40    if (!defined ($macsta))
41    {
42      $macsta = unpack ("H*", random_bytes (6));
43    }
44
45    if (!defined ($essid))
46    {
47      $essid = unpack ("H*", random_bytes (random_number (0, 32) & 0x1e));
48    }
49
50    my $pbkdf2 = Crypt::PBKDF2->new
51    (
52      hash_class => 'HMACSHA1',
53      iterations => 4096,
54      output_len => 32,
55    );
56
57    my $essid_bin = pack ("H*", $essid);
58
59    my $pmk = $pbkdf2->PBKDF2 ($essid_bin, $word);
60
61    my $macap_bin  = pack ("H*", $macap);
62    my $macsta_bin = pack ("H*", $macsta);
63
64    my $data = "PMK Name" . $macap_bin . $macsta_bin;
65
66    my $pmkid = hmac_hex ($data, $pmk, \&sha1);
67
68    $hash = sprintf ("WPA*%02x*%s*%s*%s*%s***", $type, substr ($pmkid, 0, 32), $macap, $macsta, $essid);
69  }
70  elsif ($type == 2)
71  {
72    if (!defined ($macap))
73    {
74      $macap = random_bytes (6);
75    }
76    else
77    {
78      $macap = pack ("H*", $macap);
79    }
80
81    if (!defined ($macsta))
82    {
83      $macsta = random_bytes (6);
84    }
85    else
86    {
87      $macsta = pack ("H*", $macsta);
88    }
89
90    if (!defined ($mp))
91    {
92      $mp = "\x00";
93    }
94    else
95    {
96      $mp = pack ("H*", $mp);
97    }
98
99    my $keyver;
100
101    my $snonce;
102
103    if (!defined ($eapol))
104    {
105      $keyver = random_number (1, 3); # 1, 2 or 3
106
107      $snonce = random_bytes (32);
108
109      $eapol = gen_random_wpa_eapol ($keyver, $snonce);
110    }
111    else
112    {
113      $eapol = pack ("H*", $eapol);
114
115      my $key_info = unpack ("n*", substr ($eapol, 5, 2));
116
117      $keyver = $key_info & 3;
118
119      $snonce = substr ($eapol, 17, 32);
120    }
121
122    if (!defined ($anonce))
123    {
124      $anonce = random_bytes (32);
125    }
126    else
127    {
128      $anonce = pack ("H*", $anonce);
129    }
130
131    if (!defined ($essid))
132    {
133      $essid = unpack ("H*", random_bytes (random_number (0, 32) & 0x1e));
134    }
135
136    my $pbkdf2 = Crypt::PBKDF2->new
137    (
138      hash_class => 'HMACSHA1',
139      iterations => 4096,
140      output_len => 32,
141    );
142
143    my $essid_bin = pack ("H*", $essid);
144
145    my $pmk = $pbkdf2->PBKDF2 ($essid_bin, $word);
146
147    # Pairwise Transient Key (PTK) transformation
148
149    my $ptk = wpa_prf_512 ($keyver, $pmk, $macsta, $macap, $snonce, $anonce);
150
151    # generate the Message Integrity Code (MIC)
152
153    my $mic = "";
154
155    if ($keyver == 1) # WPA1 => MD5
156    {
157      $mic = hmac ($eapol, $ptk, \&md5);
158    }
159    elsif ($keyver == 2) # WPA2 => SHA1
160    {
161      $mic = hmac ($eapol, $ptk, \&sha1);
162    }
163    elsif ($keyver == 3) # WPA2 => SHA256 + AES-CMAC
164    {
165      my $omac1 = Digest::CMAC->new ($ptk, 'Crypt::Rijndael');
166
167      $omac1->add ($eapol);
168
169      $mic = $omac1->digest;
170    }
171
172    $mic = substr ($mic, 0, 16);
173
174    $hash = sprintf ("WPA*%02x*%s*%s*%s*%s*%s*%s*%s", $type, unpack ("H*", $mic), unpack ("H*", $macap), unpack ("H*", $macsta), $essid, unpack ("H*", $anonce), unpack ("H*", $eapol), unpack ("H*", $mp));
175  }
176
177  return $hash;
178}
179
180sub module_verify_hash
181{
182  my $line = shift;
183
184  my $index1 = index ($line, ":");
185
186  return if $index1 < 1;
187
188  my $word = substr ($line, $index1 + 1);
189
190  my $hash_in = substr ($line, 0, $index1);
191
192  my @data = split ('\*', $hash_in);
193
194  my ($signature, $type, $pmkidmic, $macap, $macsta, $essid, $anonce, $eapol, $mp) = @data;
195
196  return unless defined $signature;
197  return unless defined $type;
198  return unless defined $pmkidmic;
199  return unless defined $macap;
200  return unless defined $macsta;
201  return unless defined $essid;
202
203  return unless ($signature eq "WPA");
204
205  my $word_packed = pack_if_HEX_notation ($word);
206
207  my $new_hash = module_generate_hash ($word_packed, undef, $type, $macap, $macsta, $essid, $anonce, $eapol, $mp);
208
209  return ($new_hash, $word);
210}
211
212sub gen_random_wpa_eapol
213{
214  my $keyver = shift;
215  my $snonce = shift;
216
217  my $ret = "";
218
219  # version
220
221  my $version = 1; # 802.1X-2001
222
223  $ret .= pack ("C*", $version);
224
225  my $type = 3;    # means that this EAPOL frame is used to transfer key information
226
227  $ret .= pack ("C*", $type);
228
229  my $length; # length of remaining data
230
231  if ($keyver == 1)
232  {
233    $length = 119;
234  }
235  else
236  {
237    $length = 117;
238  }
239
240  $ret .= pack ("n*", $length);
241
242  my $descriptor_type;
243
244  if ($keyver == 1)
245  {
246    $descriptor_type = 254; # EAPOL WPA key
247  }
248  else
249  {
250    $descriptor_type = 1; # EAPOL RSN key
251  }
252
253  $ret .= pack ("C*", $descriptor_type);
254
255  # key_info is a bit vector:
256  # generated from these 13 bits: encrypted key data, request, error, secure, key mic, key ack, install, key index (2), key type, key descriptor (3)
257
258  my $key_info = 0;
259
260  $key_info |= 1 << 8; # set key MIC
261  $key_info |= 1 << 3; # set if it is a pairwise key
262
263  if ($keyver == 1)
264  {
265    $key_info |= 1; # RC4 Cipher, HMAC-MD5 MIC
266  }
267  elsif ($keyver == 2)
268  {
269    $key_info |= 2; # AES Cipher, HMAC-SHA1 MIC
270  }
271  elsif ($keyver == 3)
272  {
273    $key_info |= 3; # AES-CMAC
274  }
275
276  $ret .= pack ("n*", $key_info);
277
278  my $key_length;
279
280  if ($keyver == 1)
281  {
282    $key_length = 32;
283  }
284  else
285  {
286    $key_length = 0;
287  }
288
289  $ret .= pack ("n*", $key_length);
290
291  my $replay_counter = 1;
292
293  $ret .= pack ("Q>*", $replay_counter);
294
295  $ret .= $snonce;
296
297  my $key_iv = "\x00" x 16;
298
299  $ret .= $key_iv;
300
301  my $key_rsc = "\x00" x 8;
302
303  $ret .= $key_rsc;
304
305  my $key_id = "\x00" x 8;
306
307  $ret .= $key_id;
308
309  my $key_mic = "\x00" x 16;
310
311  $ret .= $key_mic;
312
313  my $key_data_len;
314
315  if ($keyver == 1)
316  {
317    $key_data_len = 24; # length of the key_data (== WPA info)
318  }
319  else
320  {
321    $key_data_len = 22; # length of the key_data (== RSN info)
322  }
323
324  $ret .= pack ("n*", $key_data_len);
325
326  my $key_data = "";
327
328  if ($keyver == 1)
329  {
330    # wpa info
331
332    my $wpa_info = "";
333
334    my $vendor_specific_data = "";
335
336    my $tag_number = 221; # means it is a vendor specific tag
337
338    $vendor_specific_data .= pack ("C*", $tag_number);
339
340    my $tag_len = 22;     # length of the remaining "tag data"
341
342    $vendor_specific_data .= pack ("C*", $tag_len);
343
344    my $vendor_specific_oui = pack ("H*", "0050f2"); # microsoft
345
346    $vendor_specific_data .= $vendor_specific_oui;
347
348    my $vendor_specific_oui_type = 1; # WPA Information Element
349
350    $vendor_specific_data .= pack ("C*", $vendor_specific_oui_type);
351
352    my $vendor_specific_wpa_version = 1;
353
354    $vendor_specific_data .= pack ("v*", $vendor_specific_wpa_version);
355
356    # multicast
357
358    my $vendor_specific_multicast_oui = pack ("H*", "0050f2");
359
360    $vendor_specific_data .= $vendor_specific_multicast_oui;
361
362    my $vendor_specific_multicast_type = 2; # TKIP
363
364    $vendor_specific_data .= pack ("C*", $vendor_specific_multicast_type);
365
366    # unicast
367
368    my $vendor_specific_unicast_count = 1;
369
370    $vendor_specific_data .= pack ("v*", $vendor_specific_unicast_count);
371
372    my $vendor_specific_unicast_oui = pack ("H*", "0050f2");
373
374    $vendor_specific_data .= $vendor_specific_unicast_oui;
375
376    my $vendor_specific_unicast_type = 2; # TKIP
377
378    $vendor_specific_data .= pack ("C*", $vendor_specific_unicast_type);
379
380    # Auth Key Management (AKM)
381
382    my $auth_key_management_count = 1;
383
384    $vendor_specific_data .= pack ("v*", $auth_key_management_count);
385
386    my $auth_key_management_oui = pack ("H*", "0050f2");
387
388    $vendor_specific_data .= $auth_key_management_oui;
389
390    my $auth_key_management_type = 2; # Pre-Shared Key (PSK)
391
392    $vendor_specific_data .= pack ("C*", $auth_key_management_type);
393
394    $wpa_info = $vendor_specific_data;
395
396    $key_data = $wpa_info;
397  }
398  else
399  {
400    # rsn info
401
402    my $rsn_info = "";
403
404    my $tag_number = 48; # RSN info
405
406    $rsn_info .= pack ("C*", $tag_number);
407
408    my $tag_len = 20;    # length of the remaining "tag_data"
409
410    $rsn_info .= pack ("C*", $tag_len);
411
412    my $rsn_version = 1;
413
414    $rsn_info .= pack ("v*", $rsn_version);
415
416    # group cipher suite
417
418    my $group_cipher_suite_oui = pack ("H*", "000fac"); # Ieee8021
419
420    $rsn_info .= $group_cipher_suite_oui;
421
422    my $group_cipher_suite_type = 4; # AES (CCM)
423
424    $rsn_info .= pack ("C*", $group_cipher_suite_type);
425
426    # pairwise cipher suite
427
428    my $pairwise_cipher_suite_count = 1;
429
430    $rsn_info .= pack ("v*", $pairwise_cipher_suite_count);
431
432    my $pairwise_cipher_suite_oui = pack ("H*", "000fac"); # Ieee8021
433
434    $rsn_info .= $pairwise_cipher_suite_oui;
435
436    my $pairwise_cipher_suite_type = 4; # AES (CCM)
437
438    $rsn_info .= pack ("C*", $pairwise_cipher_suite_type);
439
440    # Auth Key Management (AKM)
441
442    my $auth_key_management_count = 1;
443
444    $rsn_info .= pack ("v*", $auth_key_management_count);
445
446    my $auth_key_management_oui = pack ("H*", "000fac"); # Ieee8021
447
448    $rsn_info .= $auth_key_management_oui;
449
450    my $auth_key_management_type = 2; # Pre-Shared Key (PSK)
451
452    $rsn_info .= pack ("C*", $auth_key_management_type);
453
454    # RSN Capabilities
455
456    # bit vector of these 9 bits: peerkey enabled, management frame protection (MFP) capable, MFP required,
457    # RSN GTKSA Capabilities (2), RSN PTKSA Capabilities (2), no pairwise Capabilities, Pre-Auth Capabilities
458
459    my $rsn_capabilities = pack ("H*", "0000");
460
461    $rsn_info .= $rsn_capabilities;
462
463    $key_data = $rsn_info;
464  }
465
466  $ret .= $key_data;
467
468  return $ret;
469}
470
471sub wpa_prf_512
472{
473  my $keyver = shift;
474  my $pmk    = shift;
475  my $macsta = shift;
476  my $macap  = shift;
477  my $snonce = shift;
478  my $anonce = shift;
479
480  my $data = "Pairwise key expansion";
481
482  if (($keyver == 1) || ($keyver == 2))
483  {
484    $data .= "\x00";
485  }
486
487  #
488  # Min(AA, SPA) || Max(AA, SPA)
489  #
490
491  # compare if greater: Min()/Max() on the MACs (6 bytes)
492
493  if (memcmp ($macsta, $macap, 6) < 0)
494  {
495    $data .= $macsta;
496    $data .= $macap;
497  }
498  else
499  {
500    $data .= $macap;
501    $data .= $macsta;
502  }
503
504  #
505  # Min(ANonce,SNonce) || Max(ANonce,SNonce)
506  #
507
508  # compare if greater: Min()/Max() on the nonces (32 bytes)
509
510  if (memcmp ($snonce, $anonce, 32) < 0)
511  {
512    $data .= $snonce;
513    $data .= $anonce;
514  }
515  else
516  {
517    $data .= $anonce;
518    $data .= $snonce;
519  }
520
521  my $prf_buf;
522
523  if (($keyver == 1) || ($keyver == 2))
524  {
525    $data .= "\x00";
526
527    $prf_buf = hmac ($data, $pmk, \&sha1);
528  }
529  else
530  {
531    my $data3 = "\x01\x00" . $data . "\x80\x01";
532
533    $prf_buf = hmac ($data3, $pmk, \&sha256);
534  }
535
536  $prf_buf = substr ($prf_buf, 0, 16);
537
538  return $prf_buf;
539}
540
541sub memcmp
542{
543  my $str1 = shift;
544  my $str2 = shift;
545  my $len  = shift;
546
547  my $len_str1 = length ($str1);
548  my $len_str2 = length ($str2);
549
550  if (($len > $len_str1) || ($len > $len_str2))
551  {
552    print "ERROR: memcmp () lengths wrong";
553
554    exit (1);
555  }
556
557  for (my $i = 0; $i < $len; $i++)
558  {
559    my $c_1 = ord (substr ($str1, $i, 1));
560    my $c_2 = ord (substr ($str2, $i, 1));
561
562    return -1 if ($c_1 < $c_2);
563    return  1 if ($c_1 > $c_2);
564  }
565
566  return 0;
567}
568
5691;
570