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::ECB  qw (encrypt);
12use Digest::MD4 qw (md4);
13use Digest::SHA qw (sha1 hmac_sha1);
14use Encode;
15
16sub module_constraints { [[0, 256], [-1, -1], [-1, -1], [-1, -1], [-1, -1]] }
17
18sub get_random_dpapimk_salt
19{
20  my $version = shift;
21
22  my $salt_buf = "";
23
24  my $context = random_number (1, 2);
25
26  my $cipher_algo = "";
27
28  my $hash_algo = "";
29
30  my $iterations;
31
32  my $SID = sprintf ('S-15-21-%d-%d-%d-%d',
33             random_number (400000000,490000000),
34             random_number (400000000,490000000),
35             random_number (400000000,490000000),
36             random_number (1000,1999));
37
38  my $cipher_len = 0;
39
40  if ($version == 1)
41  {
42    $iterations = random_number (4000, 24000);
43
44    $cipher_algo = "des3";
45
46    $hash_algo = "sha1";
47
48    $cipher_len = 208;
49  }
50  elsif ($version == 2)
51  {
52    $iterations = random_number (8000, 17000);
53
54    $cipher_algo = "aes256";
55
56    $hash_algo = "sha512";
57
58    $cipher_len = 288;
59  }
60
61  my $iv = random_bytes (16);
62  $iv    = unpack ("H*", $iv);
63
64  $salt_buf = $version . '*' .
65              $context . '*' .
66              $SID     . '*' .
67              $cipher_algo   . '*' .
68              $hash_algo     . '*' .
69              $iterations    . '*' .
70              $iv         . '*' .
71              $cipher_len . '*';
72
73  return $salt_buf;
74}
75
76# Thanks to Jochen Hoenicke <hoenicke@gmail.com>
77# (one of the authors of Palm Keyring)
78# for these next two subs.
79
80sub dpapi_pbkdf2
81{
82  my ($password, $salt, $iter, $keylen, $prf) = @_;
83  my ($k, $t, $u, $ui, $i);
84
85  $t = "";
86
87  for ($k = 1; length ($t) <  $keylen; $k++)
88  {
89    $u = $ui = &$prf ($salt . pack ('N', $k), $password);
90
91    for ($i = 1; $i < $iter; $i++)
92    {
93      # modification to fit Microsoft
94      # weird pbkdf2 implementation...
95
96      $ui = &$prf ($u, $password);
97      $u ^= $ui;
98    }
99
100    $t .= $u;
101  }
102  return substr ($t, 0, $keylen);
103}
104
105sub module_generate_hash
106{
107  my $word_buf     = shift;
108  my $salt_buf     = shift;
109  my $dpapimk_salt = shift // get_random_dpapimk_salt (1);
110  my $cipher       = shift;
111
112  my @salt_arr = split ('\*', $dpapimk_salt);
113
114  my $version          = $salt_arr[0];
115  my $context          = $salt_arr[1];
116  my $SID              = $salt_arr[2];
117  my $cipher_algorithm = $salt_arr[3];
118  my $hash_algorithm   = $salt_arr[4];
119  my $iterations       = $salt_arr[5];
120  my $salt             = pack ("H*", $salt_arr[6]);
121  my $cipher_len       = $salt_arr[7];
122
123  # intermediate values
124
125  my $user_hash;
126  my $user_derivationKey;
127  my $encKey;
128  my $expected_hmac;
129  my $cleartext;
130
131  if ($context == 1)
132  {
133     $user_hash = sha1 (encode ("UTF-16LE", $word_buf));
134  }
135  elsif ($context == 2)
136  {
137     $user_hash = md4 (encode ("UTF-16LE", $word_buf));
138  }
139
140  $user_derivationKey = hmac_sha1 (encode ("UTF-16LE", $SID . "\x00"), $user_hash);
141
142  my $hmacSalt = random_bytes (16);
143  my $last_key = random_bytes (64);
144
145  if ($version == 1)
146  {
147    $encKey        = hmac_sha1 ($hmacSalt, $user_derivationKey);
148    $expected_hmac = hmac_sha1 ($last_key, $encKey);
149
150    # need padding because keyLen is 24 and hashLen 20
151    $expected_hmac = $expected_hmac . random_bytes (4);
152  }
153  elsif ($version == 2)
154  {
155    $encKey        = hmac_sha512 ($hmacSalt, $user_derivationKey);
156    $expected_hmac = hmac_sha512 ($last_key, $encKey);
157  }
158
159  $cleartext = $hmacSalt . $expected_hmac . $last_key;
160
161  my $derived_key;
162  my $key;
163  my $iv;
164
165  my $pbkdf2;
166
167  if ($version == 1)
168  {
169    $derived_key = dpapi_pbkdf2 ($user_derivationKey, $salt, $iterations, 32, \&hmac_sha1);
170  }
171  elsif ($version == 2)
172  {
173    $derived_key = dpapi_pbkdf2 ($user_derivationKey, $salt, $iterations, 48, \&hmac_sha512);
174  }
175
176  if (defined $cipher)
177  {
178    $cipher = pack ("H*", $cipher);
179
180    my $computed_hmac = "";
181
182    if ($version == 1)
183    {
184      $key = substr ($derived_key,   0, 24);
185      $iv  = substr ($derived_key,  24,  8);
186
187      my $p1 = Crypt::ECB->new ({
188        key         => substr ($key, 0, 8),
189        cipher      => "DES",
190        literal_key => 1,
191        header      => "none",
192        keysize     => 8,
193        padding     => "none",
194      });
195
196      my $p2 = Crypt::ECB->new ({
197        key         => substr ($key, 8, 8),
198        cipher      => "DES",
199        literal_key => 1,
200        header      => "none",
201        keysize     => 8,
202        padding     => "none",
203      });
204
205      my $p3 = Crypt::ECB->new ({
206        key         => substr ($key, 16, 8),
207        cipher      => "DES",
208        literal_key => 1,
209        header      => "none",
210        keysize     => 8,
211        padding     => "none",
212      });
213
214      # let's compute a 3DES-EDE-CBC decryption
215
216      my $out1;
217      my $out2;
218      my $out3;
219      my $expected_cleartext = "";
220
221      # size of cipherlen is 104 bytes
222      for (my $k = 0; $k < 13; $k++)
223      {
224        $out1 = $p3->decrypt (substr ($cipher, $k * 8, 8));
225        $out2 = $p2->encrypt ($out1);
226        $out3 = $p1->decrypt ($out2);
227
228        $expected_cleartext .= substr ($out3, 0, 8) ^ $iv;
229
230        $iv = substr ($cipher, $k * 8, 8);
231      }
232
233      $last_key      = substr ($expected_cleartext,  length ($expected_cleartext) - 64, 64);
234      $hmacSalt      = substr ($expected_cleartext, 0, 16);
235      $expected_hmac = substr ($expected_cleartext, 16, 20);
236
237      $encKey        = hmac_sha1 ($hmacSalt, $user_derivationKey);
238      $computed_hmac = hmac_sha1 ($last_key, $encKey);
239
240      $cleartext = $expected_cleartext;
241
242      if (unpack ("H*", $expected_hmac) ne unpack ("H*", $computed_hmac))
243      {
244        $cleartext = "0" x 104;
245      }
246    }
247    elsif ($version == 2)
248    {
249      $key = substr ($derived_key,  0, 32);
250      $iv  = substr ($derived_key, 32, 16);
251
252      my $aes = Crypt::CBC->new ({
253        key         => $key,
254        cipher      => "Crypt::Rijndael",
255        iv          => $iv,
256        literal_key => 1,
257        header      => "none",
258        keysize     => 32,
259        padding     => "none",
260      });
261
262      my $expected_cleartext = $aes->decrypt ($cipher);
263
264      $last_key      = substr ($expected_cleartext,  length ($expected_cleartext) - 64, 64);
265      $hmacSalt      = substr ($expected_cleartext, 0, 16);
266      $expected_hmac = substr ($expected_cleartext, 16, 64);
267
268      $encKey        = hmac_sha512 ($hmacSalt, $user_derivationKey);
269      $computed_hmac = hmac_sha512 ($last_key, $encKey);
270
271      $cleartext = $expected_cleartext;
272
273      if (unpack ("H*", $expected_hmac) ne unpack ("H*", $computed_hmac))
274      {
275        $cleartext = "0" x 144;
276      }
277    }
278  }
279
280  if ($version == 1)
281  {
282    $key = substr ($derived_key,   0, 24);
283    $iv  = substr ($derived_key,  24,  8);
284
285    my $p1 = Crypt::ECB->new ({
286      key         => substr ($key, 0, 8),
287      cipher      => "DES",
288      literal_key => 1,
289      header      => "none",
290      keysize     => 8,
291      padding     => "none",
292    });
293
294    my $p2 = Crypt::ECB->new ({
295      key         => substr ($key, 8, 8),
296      cipher      => "DES",
297      literal_key => 1,
298      header      => "none",
299      keysize     => 8,
300      padding     => "none",
301    });
302
303    my $p3 = Crypt::ECB->new ({
304      key         => substr ($key, 16, 8),
305      cipher      => "DES",
306      literal_key => 1,
307      header      => "none",
308      keysize     => 8,
309      padding     => "none",
310    });
311
312    # let's compute a 3DES-EDE-CBC encryption
313
314    # compute first block
315    my $out1 = $p1->encrypt (substr ($cleartext, 0, 8) ^ $iv);
316    my $out2 = $p2->decrypt ($out1);
317    my $out3 = $p3->encrypt ($out2);
318
319    $cipher = substr ($out3, 0, 8);
320
321    # size of cipherlen is 104 bytes
322    for (my $k = 1; $k < 13; $k++)
323    {
324      $iv = $out3;
325
326      $out1 = $p1->encrypt (substr ($cleartext, $k * 8, 8) ^ $iv);
327      $out2 = $p2->decrypt ($out1);
328      $out3 = $p3->encrypt ($out2);
329
330      $cipher .= substr ($out3, 0, 8);
331    }
332  }
333  else
334  {
335    $key = substr ($derived_key,  0, 32);
336    $iv  = substr ($derived_key, 32, 16);
337
338    my $aes = Crypt::CBC->new ({
339      key         => $key,
340      cipher      => "Crypt::Rijndael",
341      iv          => $iv,
342      literal_key => 1,
343      header      => "none",
344      keysize     => 32,
345      padding     => "none",
346    });
347
348    $cipher = $aes->encrypt ($cleartext);
349  }
350
351  my $tmp_hash = sprintf ('$DPAPImk$%d*%d*%s*%s*%s*%d*%s*%d*%s',
352                 $version,
353                 $context,
354                 $SID,
355                 $cipher_algorithm,
356                 $hash_algorithm,
357                 $iterations,
358                 unpack ("H*", $salt),
359                 $cipher_len,
360                 unpack ("H*", $cipher));
361
362  return $tmp_hash;
363}
364
365sub module_verify_hash
366{
367  my $line = shift;
368
369  my ($hash, $word) = split (':', $line);
370
371  return unless defined $hash;
372  return unless defined $word;
373
374  my @tmp_data = split ('\$', $hash);
375
376  my $signature = $tmp_data[1];
377
378  next unless ($signature eq 'DPAPImk');
379
380  my @data = split ('\*', $tmp_data[2]);
381
382  next unless (scalar @data == 9);
383
384  my $version = shift @data;
385
386  next unless ($version == 1 || $version == 2);
387
388  my $context          = shift @data;
389  my $SID              = shift @data;
390  my $cipher_algorithm = shift @data;
391  my $hash_algorithm   = shift @data;
392  my $iteration        = shift @data;
393  my $iv               = shift @data;
394  my $cipher_len       = shift @data;
395  my $cipher           = shift @data;
396
397  next unless (length ($cipher) == $cipher_len);
398
399  if ($version == 1)
400  {
401    next unless ($cipher_len == 208);
402  }
403  elsif ($version == 2)
404  {
405    next unless ($cipher_len == 288);
406  }
407
408  my $dpapimk_salt = substr ($hash, length ('$DPAPImk$'));
409
410  my $word_packed = pack_if_HEX_notation ($word);
411
412  my $new_hash = module_generate_hash ($word_packed, undef, $dpapimk_salt, $cipher);
413
414  return ($new_hash, $word);
415}
416
4171;
418