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::GCrypt;
12use Crypt::PBKDF2;
13use Digest::SHA qw (sha1 sha1_hex);
14
15sub module_constraints { [[0, 51], [32, 32], [-1, -1], [-1, -1], [-1, -1]] }
16
17sub module_generate_hash
18{
19  my $word  = shift;
20  my $salt  = shift;
21  my $iter  = shift // 100000;
22  my $iv    = shift // random_hex_string (2 * 8);
23  my $plain = shift // random_hex_string (2 * 1024);
24
25  my $b_iv    = pack ('H*', $iv);
26  my $b_salt  = pack ('H*', $salt);
27  my $b_plain = pack ('H*', $plain);
28
29  my $kdf = Crypt::PBKDF2->new
30  (
31    hash_class => 'HMACSHA1',
32    iterations => $iter,
33    output_len => 16
34  );
35
36  my $pass_hash = sha1 ($word);
37  my $key       = $kdf->PBKDF2 ($b_salt, $pass_hash);
38
39  my $cfb = Crypt::GCrypt->new
40  (
41    type      => 'cipher',
42    algorithm => 'blowfish',
43    mode      => 'cfb'
44  );
45
46  $cfb->start  ('encrypting');
47  $cfb->setkey ($key);
48  $cfb->setiv  ($b_iv);
49
50  my $b_cipher = $cfb->encrypt ($b_plain);
51
52  $cfb->finish ();
53
54  my $cipher   = unpack ('H*', $b_cipher);
55  my $checksum = sha1_hex ($b_plain);
56
57  my $hash = '$odf$' . "*0*0*$iter*16*$checksum*8*$iv*16*$salt*0*$cipher";
58
59  return $hash;
60}
61
62sub module_verify_hash
63{
64  my $line = shift;
65
66  my ($hash, $word) = split (':', $line);
67
68  return unless defined $hash;
69  return unless defined $word;
70
71  my $word_packed = pack_if_HEX_notation ($word);
72
73  # tokenize
74  my @data = split ('\*', $hash);
75
76  return unless scalar @data == 12;
77
78  my $signature   = shift @data;
79  my $cipher_type = shift @data;
80  my $cs_type     = shift @data;
81  my $iter        = shift @data;
82  my $cs_len      = shift @data;
83  my $cs          = shift @data;
84  my $iv_len      = shift @data;
85  my $iv          = shift @data;
86  my $salt_len    = shift @data;
87  my $salt        = shift @data;
88  my $unused      = shift @data;
89  my $cipher      = shift @data;
90
91  # validate
92  return unless $signature   eq '$odf$';
93  return unless $cipher_type eq '0';
94  return unless $cs_type     eq '0';
95  return unless $cs_len      eq '16';
96  return unless $iv_len      eq '8';
97  return unless $salt_len    eq '16';
98  return unless $unused      eq '0';
99  return unless defined $cipher;
100
101  # decrypt
102  my $b_iv     = pack ('H*', $iv);
103  my $b_salt   = pack ('H*', $salt);
104  my $b_cipher = pack ('H*', $cipher);
105
106  my $kdf = Crypt::PBKDF2->new
107  (
108    hash_class => 'HMACSHA1',
109    iterations => $iter,
110    output_len => 16
111  );
112
113  my $pass_hash = sha1 ($word);
114  my $key       = $kdf->PBKDF2 ($b_salt, $pass_hash);
115
116  my $cfb = Crypt::GCrypt->new (
117    type      => 'cipher',
118    algorithm => 'blowfish',
119    mode      => 'cfb'
120  );
121
122  $cfb->start  ('decrypting');
123  $cfb->setkey ($key);
124  $cfb->setiv  ($b_iv);
125
126  my $b_plain = $cfb->decrypt ($b_cipher);
127
128  $cfb->finish ();
129
130  my $plain = unpack ('H*', $b_plain);
131
132  my $new_hash = module_generate_hash ($word_packed, $salt, $iter, $iv, $plain);
133
134  return ($new_hash, $word);
135}
136
1371;
138