File Coverage

blib/lib/Crypt/PBKDF2.pm
Criterion Covered Total %
statement 129 140 92.1
branch 32 48 66.6
condition 12 21 57.1
subroutine 30 31 96.7
pod 9 10 90.0
total 212 250 84.8


line stmt bran cond sub pod time code
1             package Crypt::PBKDF2;
2             # ABSTRACT: The PBKDF2 password hashing algorithm.
3             our $VERSION = '0.161520'; # VERSION
4             our $AUTHORITY = 'cpan:ARODLAND'; # AUTHORITY
5 6     6   89317 use Moo 2;
  6         59248  
  6         31  
6 6     6   9118 use strictures 2;
  6         7606  
  6         197  
7 6     6   3450 use namespace::autoclean;
  6         62580  
  6         19  
8 6     6   3639 use MIME::Base64 ();
  6         2891  
  6         149  
9 6     6   31 use Carp qw(croak);
  6         7  
  6         278  
10 6     6   22 use Module::Runtime;
  6         8  
  6         32  
11 6     6   171 use Try::Tiny;
  6         5  
  6         277  
12 6     6   3110 use Type::Tiny;
  6         86364  
  6         281  
13 6     6   3573 use Types::Standard 1.000005 qw(Str Int HashRef ConsumerOf);
  6         207391  
  6         67  
14 6     6   4769 use Scalar::Util qw(blessed);
  6         9  
  6         10065  
15              
16             sub BUILD {
17 1044     1044 0 142276 my ($self) = @_;
18 1044         15309 $self->hasher; # Force instantiation, so we get errors ASAP
19             }
20              
21              
22             has hash_class => (
23             is => 'ro',
24             isa => Str,
25             default => 'HMACSHA1',
26             predicate => 'has_hash_class',
27             );
28              
29              
30             has hash_args => (
31             is => 'ro',
32             isa => HashRef,
33             default => sub { +{} },
34             predicate => 'has_hash_args',
35             );
36              
37              
38             has hasher => (
39             is => 'ro',
40             isa => ConsumerOf['Crypt::PBKDF2::Hash'],
41             lazy => 1,
42             default => sub { shift->_lazy_hasher },
43             );
44              
45             has _lazy_hasher => (
46             is => 'ro',
47             isa => ConsumerOf['Crypt::PBKDF2::Hash'],
48             lazy => 1,
49             init_arg => undef,
50             predicate => 'has_lazy_hasher',
51             builder => '_build_hasher',
52             );
53              
54             sub _build_hasher {
55 20     20   2900 my ($self) = @_;
56 20         59 my $class = $self->hash_class;
57 20 50       76 if ($class !~ s/^\+//) {
58 20         47 $class = "Crypt::PBKDF2::Hash::$class";
59             }
60 20         44 my $hash_args = $self->hash_args;
61              
62 20         72 return Module::Runtime::use_module($class)->new( %$hash_args );
63             }
64              
65              
66             has iterations => (
67             is => 'ro',
68             isa => Int,
69             default => 1000,
70             );
71              
72              
73             has output_len => (
74             is => 'ro',
75             isa => Int,
76             predicate => 'has_output_len',
77             );
78              
79              
80             has salt_len => (
81             is => 'ro',
82             isa => Int,
83             default => 4,
84             );
85              
86             sub _random_salt {
87 1023     1023   956 my ($self) = @_;
88 1023         905 my $ret = "";
89 1023         2727 for my $n (1 .. $self->salt_len) {
90 4680         4994 $ret .= chr(int rand 256);
91             }
92 1023         1841 return $ret;
93             }
94              
95              
96             has encoding => (
97             is => 'ro',
98             isa => Str,
99             default => 'ldap',
100             );
101              
102              
103             has length_limit => (
104             is => 'ro',
105             isa => Int,
106             predicate => 'has_length_limit',
107             );
108              
109              
110             sub generate {
111 1023     1023 1 418424 my ($self, $password, $salt) = @_;
112 1023 50       3022 $salt = $self->_random_salt unless defined $salt;
113              
114 1023 100 100     2667 if ($self->has_length_limit and length($password) > $self->length_limit) {
115 1         191 croak "Password exceeds length limit";
116             }
117              
118 1022         1591 my $hash = $self->PBKDF2($salt, $password);
119 1022         1886 return $self->encode_string($salt, $hash);
120             }
121              
122              
123             sub validate {
124 1023     1023 1 4299 my ($self, $hashed, $password) = @_;
125              
126 1023 100 100     2260 if ($self->has_length_limit and length($password) > $self->length_limit) {
127 1         75 croak "Password exceeds length limit";
128             }
129              
130 1022         1424 my $info = $self->decode_string($hashed);
131              
132             my $hasher = try {
133 1022     1022   28024 $self->hasher_from_algorithm($info->{algorithm}, $info->{algorithm_options});
134             } catch {
135 0 0   0   0 my $opts = defined($info->{algorithm_options}) ? " (options ''$info->{algorithm_options}'')" : "";
136 0         0 croak "Couldn't construct hasher for ''$info->{algorithm}''$opts: $_";
137 1022         7408 };
138              
139             my $checker = $self->clone(
140             hasher => $hasher,
141             iterations => $info->{iterations},
142 1022         18027 output_len => length($info->{hash}),
143             );
144              
145 1022         20484 my $check_hash = $checker->PBKDF2($info->{salt}, $password);
146              
147 1022         6511 return ($check_hash eq $info->{hash});
148             }
149              
150              
151             sub PBKDF2 {
152 5053     5053 1 5788 my ($self, $salt, $password) = @_;
153 5053         7197 my $iterations = $self->iterations;
154 5053         90738 my $hasher = $self->hasher;
155 5053   66     34468 my $output_len = $self->output_len || $hasher->hash_len;
156              
157 5053         7627 my $hLen = $hasher->hash_len;
158 5053         6755 my $l = int($output_len / $hLen);
159 5053         4907 my $r = $output_len % $hLen;
160              
161 5053 50 33     13140 if ($l > 0xffffffff or $l == 0xffffffff && $r > 0) {
      33        
162 0         0 croak "output_len too large for PBKDF2";
163             }
164              
165 5053         3331 my $output;
166              
167 5053         7778 for my $i (1 .. $l) {
168 5050         8856 $output .= $self->_PBKDF2_F($hasher, $salt, $password, $iterations, $i);
169             }
170              
171 5053 100       7945 if ($r) {
172 9         26 $output .= substr( $self->_PBKDF2_F($hasher, $salt, $password, $iterations, $l + 1), 0, $r);
173             }
174              
175 5053         14765 return $output;
176             }
177              
178              
179             sub PBKDF2_base64 {
180 1000     1000 1 932 my $self = shift;
181              
182 1000         1698 return MIME::Base64::encode( $self->PBKDF2(@_), "" );
183             }
184              
185              
186             sub PBKDF2_hex {
187 1009     1009 1 2460 my $self = shift;
188 1009         1853 return unpack "H*", $self->PBKDF2(@_);
189             }
190              
191             sub _PBKDF2_F {
192 5059     5059   5708 my ($self, $hasher, $salt, $password, $iterations, $i) = @_;
193 5059         16511 my $result =
194             my $hash =
195             $hasher->generate( $salt . pack("N", $i), $password );
196              
197 5059         6799 for my $iter (2 .. $iterations) {
198 209650         260453 $hash = $hasher->generate( $hash, $password );
199 209650         206164 $result ^= $hash;
200             }
201              
202 5059         10713 return $result;
203             }
204              
205              
206             sub encode_string {
207 1022     1022 1 1318 my ($self, $salt, $hash) = @_;
208 1022 100       2609 if ($self->encoding eq 'crypt') {
    50          
209 511         867 return $self->_encode_string_cryptlike($salt, $hash);
210             } elsif ($self->encoding eq 'ldap') {
211 511         842 return $self->_encode_string_ldaplike($salt, $hash);
212             } else {
213 0         0 die "Unknown setting '", $self->encoding, "' for encoding";
214             }
215             }
216              
217             sub _encode_string_cryptlike {
218 511     511   483 my ($self, $salt, $hash) = @_;
219 511         8062 my $hasher = $self->hasher;
220 511         3196 my $hasher_class = blessed($hasher);
221 511 50 33     2814 if (!defined $hasher_class || $hasher_class !~ s/^Crypt::PBKDF2::Hash:://) {
222 0         0 croak "Can't ''encode_string'' with a hasher class outside of Crypt::PBKDF2::Hash::*";
223             }
224              
225 511         985 my $algo_string = $hasher->to_algo_string;
226 511 100       761 $algo_string = defined($algo_string) ? "{$algo_string}" : "";
227              
228 511         3292 return '$PBKDF2$' . "$hasher_class$algo_string:" . $self->iterations . ':'
229             . MIME::Base64::encode($salt, "") . '$'
230             . MIME::Base64::encode($hash, "");
231             }
232              
233             sub _encode_string_ldaplike {
234 511     511   457 my ($self, $salt, $hash) = @_;
235 511         8687 my $hasher = $self->hasher;
236 511         3271 my $hasher_class = blessed($hasher);
237 511 50 33     2427 if (!defined $hasher_class || $hasher_class !~ s/^Crypt::PBKDF2::Hash:://) {
238 0         0 croak "Can't ''encode_string'' with a hasher class outside of Crypt::PBKDF2::Hash::*";
239             }
240              
241 511         1149 my $algo_string = $hasher->to_algo_string;
242 511 100       825 $algo_string = defined($algo_string) ? "+$algo_string" : "";
243              
244 511         1513 return '{X-PBKDF2}' . "$hasher_class$algo_string:"
245             . $self->_b64_encode_int32($self->iterations) . ':'
246             . MIME::Base64::encode($salt, "") . ':'
247             . MIME::Base64::encode($hash, "");
248             }
249              
250              
251             sub decode_string {
252 1022     1022 1 840 my ($self, $hashed) = @_;
253 1022 100       2659 if ($hashed =~ /^\$PBKDF2\$/) {
    50          
254 511         827 return $self->_decode_string_cryptlike($hashed);
255             } elsif ($hashed =~ /^\{X-PBKDF2}/i) {
256 511         839 return $self->_decode_string_ldaplike($hashed);
257             } else {
258 0         0 croak "Unrecognized hash";
259             }
260             }
261              
262             sub _decode_string_cryptlike {
263 511     511   431 my ($self, $hashed) = @_;
264 511 50       1016 if ($hashed !~ /^\$PBKDF2\$/) {
265 0         0 croak "Unrecognized hash";
266             }
267              
268 511 50       2547 if (my ($algorithm, $opts, $iterations, $salt, $hash) = $hashed =~
269             /^\$PBKDF2\$([^:}]+)(?:\{([^}]+)\})?:(\d+):([^\$]+)\$(.*)/) {
270             return {
271 511         2647 algorithm => $algorithm,
272             algorithm_options => $opts,
273             iterations => $iterations,
274             salt => MIME::Base64::decode($salt),
275             hash => MIME::Base64::decode($hash),
276             }
277             } else {
278 0         0 croak "Invalid format";
279             }
280             }
281              
282             sub _decode_string_ldaplike {
283 511     511   695 my ($self, $hashed) = @_;
284 511 50       1136 if ($hashed !~ /^\{X-PBKDF2}/i) {
285 0         0 croak "Unrecognized hash";
286             }
287              
288 511 50       2738 if (my ($algo_str, $iterations, $salt, $hash) = $hashed =~
289             /^\{X-PBKDF2}([^:]+):([^:]{6}):([^\$]+):(.*)/i) {
290 511         948 my ($algorithm, $opts) = split /\+/, $algo_str;
291             return {
292 511         800 algorithm => $algorithm,
293             algorithm_options => $opts,
294             iterations => $self->_b64_decode_int32($iterations),
295             salt => MIME::Base64::decode($salt),
296             hash => MIME::Base64::decode($hash),
297             }
298             } else {
299 0         0 croak "Invalid format";
300             }
301             }
302              
303              
304             sub hasher_from_algorithm {
305 1022     1022 1 1188 my ($self, $algorithm, $args) = @_;
306 1022         2976 my $class = Module::Runtime::use_module("Crypt::PBKDF2::Hash::$algorithm");
307              
308 1022 100       20865 if (defined $args) {
309 21         52 return $class->from_algo_string($args);
310             } else {
311 1001         2832 return $class->new;
312             }
313             }
314              
315              
316             sub clone {
317 1034     1034 1 5109 my ($self, %params) = @_;
318 1034         1144 my $class = ref $self;
319              
320             # If the hasher was built from hash_class and hash_args, then omit it from
321             # the clone. But if it was set by the user, then we need to copy it. We're
322             # assuming that the hasher has no state, so it doesn't need a deep clone.
323             # This is true of all of the ones that I'm shipping, but if it's not true for
324             # you, let me know.
325              
326 1034 50       9758 my %new_args = (
    50          
    50          
    100          
327             $self->has_hash_class ? (hash_class => $self->hash_class) : (),
328             $self->has_hash_args ? (hash_args => $self->hash_args) : (),
329             $self->has_output_len ? (output_len => $self->output_len) : (),
330             $self->has_lazy_hasher ? () : (hasher => $self->hasher),
331             iterations => $self->iterations,
332             salt_len => $self->salt_len,
333             %params,
334             );
335            
336 1034         20842 return $class->new(%new_args);
337             }
338              
339             sub _b64_encode_int32 {
340 511     511   530 my ($self, $value) = @_;
341 511         1694 my $b64 = MIME::Base64::encode(pack("N", $value), "");
342 511         1443 $b64 =~ s/==$//;
343 511         2642 return $b64;
344             }
345              
346             sub _b64_decode_int32 {
347 511     511   473 my ($self, $b64) = @_;
348 511         426 $b64 .= "==";
349 511         3447 return unpack "N", MIME::Base64::decode($b64);
350             }
351              
352             __PACKAGE__->meta->make_immutable;
353             1;
354              
355             __END__