File Coverage

blib/lib/Crypt/PBKDF2.pm
Criterion Covered Total %
statement 133 145 91.7
branch 30 50 60.0
condition 12 21 57.1
subroutine 32 33 96.9
pod 9 10 90.0
total 216 259 83.4


line stmt bran cond sub pod time code
1             package Crypt::PBKDF2;
2             # ABSTRACT: The PBKDF2 password hashing algorithm.
3             our $VERSION = '0.261630'; # VERSION
4             our $AUTHORITY = 'cpan:ARODLAND'; # AUTHORITY
5 6     6   595466 use Moo 2;
  6         42530  
  6         31  
6 6     6   10629 use strictures 2;
  6         9277  
  6         239  
7 6     6   4883 use namespace::autoclean;
  6         107849  
  6         22  
8 6     6   3058 use MIME::Base64 ();
  6         4117  
  6         164  
9 6     6   2660 use Crypt::URandom ();
  6         42073  
  6         178  
10 6     6   35 use Carp qw(croak);
  6         9  
  6         320  
11 6     6   31 use Module::Runtime;
  6         9  
  6         44  
12 6     6   190 use Try::Tiny;
  6         11  
  6         269  
13 6     6   4175 use Type::Tiny;
  6         143848  
  6         290  
14 6     6   4088 use Types::Standard 1.000005 qw(Str Int HashRef ConsumerOf);
  6         495257  
  6         72  
15 6     6   14648 use Scalar::Util qw(blessed);
  6         10  
  6         11782  
16              
17             sub BUILD {
18 1044     1044 0 452240 my ($self) = @_;
19 1044         13603 $self->hasher; # Force instantiation, so we get errors ASAP
20             }
21              
22              
23             has hash_class => (
24             is => 'ro',
25             isa => Str,
26             default => 'HMACSHA2',
27             predicate => 'has_hash_class',
28             );
29              
30              
31             has hash_args => (
32             is => 'ro',
33             isa => HashRef,
34             default => sub { +{} },
35             predicate => 'has_hash_args',
36             );
37              
38              
39             has hasher => (
40             is => 'ro',
41             isa => ConsumerOf['Crypt::PBKDF2::Hash'],
42             lazy => 1,
43             default => sub { shift->_lazy_hasher },
44             );
45              
46             has _lazy_hasher => (
47             is => 'ro',
48             isa => ConsumerOf['Crypt::PBKDF2::Hash'],
49             lazy => 1,
50             init_arg => undef,
51             predicate => 'has_lazy_hasher',
52             builder => '_build_hasher',
53             );
54              
55             sub _build_hasher {
56 20     20   252 my ($self) = @_;
57 20         69 my $class = $self->hash_class;
58 20 50       103 if ($class !~ s/^\+//) {
59 20         66 $class = "Crypt::PBKDF2::Hash::$class";
60             }
61 20         57 my $hash_args = $self->hash_args;
62              
63 20         87 return Module::Runtime::use_module($class)->new( %$hash_args );
64             }
65              
66              
67             has iterations => (
68             is => 'ro',
69             isa => Int,
70             default => 600000,
71             );
72              
73              
74             has output_len => (
75             is => 'ro',
76             isa => Int,
77             predicate => 'has_output_len',
78             );
79              
80              
81             has salt_len => (
82             is => 'ro',
83             isa => Int,
84             default => 4,
85             );
86              
87             sub _random_salt {
88 1023     1023   1864 my ($self) = @_;
89 1023         4538 return Crypt::URandom::urandom($self->salt_len);
90             }
91              
92              
93             has encoding => (
94             is => 'ro',
95             isa => Str,
96             default => 'ldap',
97             );
98              
99              
100             has length_limit => (
101             is => 'ro',
102             isa => Int,
103             predicate => 'has_length_limit',
104             );
105              
106              
107             sub generate {
108 1023     1023 1 725388 my ($self, $password, $salt) = @_;
109 1023 50       4259 $salt = $self->_random_salt unless defined $salt;
110              
111 1023 100 100     29156 if ($self->has_length_limit and length($password) > $self->length_limit) {
112 1         188 croak "Password exceeds length limit";
113             }
114              
115 1022         2798 my $hash = $self->PBKDF2($salt, $password);
116 1022         2516 return $self->encode_string($salt, $hash);
117             }
118              
119              
120             sub validate {
121 1023     1023 1 5276 my ($self, $hashed, $password) = @_;
122              
123 1023 100 100     2374 if ($self->has_length_limit and length($password) > $self->length_limit) {
124 1         82 croak "Password exceeds length limit";
125             }
126              
127 1022         1976 my $info = $self->decode_string($hashed);
128              
129             my $hasher = try {
130 1022     1022   38951 $self->hasher_from_algorithm($info->{algorithm}, $info->{algorithm_options});
131             } catch {
132 0 0   0   0 my $opts = defined($info->{algorithm_options}) ? " (options ''$info->{algorithm_options}'')" : "";
133 0         0 croak "Couldn't construct hasher for ''$info->{algorithm}''$opts: $_";
134 1022         8645 };
135              
136             my $checker = $self->clone(
137             hasher => $hasher,
138             iterations => $info->{iterations},
139 1022         50017 output_len => length($info->{hash}),
140             );
141              
142 1022         11319 my $check_hash = $checker->PBKDF2($info->{salt}, $password);
143              
144 1022         2504 return _secure_compare($check_hash, $info->{hash});
145             }
146              
147             # Constant-time string comparison, to avoid timing attacks on the hash check.
148             sub _secure_compare {
149 1022     1022   1543 my ($a, $b) = @_;
150              
151 1022         1547 my $r = length($a) != length($b);
152 1022 50       1656 $a = $b if $r;
153              
154 1022         13215 $r |= ord(substr($a, $_)) ^ ord(substr($b, $_)) for 0 .. length($a) - 1;
155              
156 1022         8617 return $r == 0;
157             }
158              
159              
160             sub PBKDF2 {
161 5053     5053 1 11256 my ($self, $salt, $password) = @_;
162 5053         11644 my $iterations = $self->iterations;
163 5053         120865 my $hasher = $self->hasher;
164 5053   66     49267 my $output_len = $self->output_len || $hasher->hash_len;
165              
166 5053         10258 my $hLen = $hasher->hash_len;
167 5053         10477 my $l = int($output_len / $hLen);
168 5053         7723 my $r = $output_len % $hLen;
169              
170 5053 50 33     15483 if ($l > 0xffffffff or $l == 0xffffffff && $r > 0) {
      33        
171 0         0 croak "output_len too large for PBKDF2";
172             }
173              
174 5053         5671 my $output;
175              
176 5053         11770 for my $i (1 .. $l) {
177 5050         10273 $output .= $self->_PBKDF2_F($hasher, $salt, $password, $iterations, $i);
178             }
179              
180 5053 100       8805 if ($r) {
181 9         62 $output .= substr( $self->_PBKDF2_F($hasher, $salt, $password, $iterations, $l + 1), 0, $r);
182             }
183              
184 5053         20852 return $output;
185             }
186              
187              
188             sub PBKDF2_base64 {
189 1000     1000 1 1639 my $self = shift;
190              
191 1000         2720 return MIME::Base64::encode( $self->PBKDF2(@_), "" );
192             }
193              
194              
195             sub PBKDF2_hex {
196 1009     1009 1 8412 my $self = shift;
197 1009         2768 return unpack "H*", $self->PBKDF2(@_);
198             }
199              
200             sub _PBKDF2_F {
201 5059     5059   11155 my ($self, $hasher, $salt, $password, $iterations, $i) = @_;
202 5059         23859 my $result =
203             my $hash =
204             $hasher->generate( $salt . pack("N", $i), $password );
205              
206 5059         66593 for my $iter (2 .. $iterations) {
207 1407650         1697645 $hash = $hasher->generate( $hash, $password );
208 1407650         10570969 $result ^= $hash;
209             }
210              
211 5059         17838 return $result;
212             }
213              
214              
215             sub encode_string {
216 1022     1022 1 1807 my ($self, $salt, $hash) = @_;
217 1022 100       3473 if ($self->encoding eq 'crypt') {
    50          
218 511         1134 return $self->_encode_string_cryptlike($salt, $hash);
219             } elsif ($self->encoding eq 'ldap') {
220 511         1125 return $self->_encode_string_ldaplike($salt, $hash);
221             } else {
222 0         0 die "Unknown setting '", $self->encoding, "' for encoding";
223             }
224             }
225              
226             sub _encode_string_cryptlike {
227 511     511   720 my ($self, $salt, $hash) = @_;
228 511         5788 my $hasher = $self->hasher;
229 511         2615 my $hasher_class = blessed($hasher);
230 511 50 33     2713 if (!defined $hasher_class || $hasher_class !~ s/^Crypt::PBKDF2::Hash:://) {
231 0         0 croak "Can't ''encode_string'' with a hasher class outside of Crypt::PBKDF2::Hash::*";
232             }
233              
234 511         1094 my $algo_string = $hasher->to_algo_string;
235 511 50       1165 $algo_string = defined($algo_string) ? "{$algo_string}" : "";
236              
237 511         3758 return '$PBKDF2$' . "$hasher_class$algo_string:" . $self->iterations . ':'
238             . MIME::Base64::encode($salt, "") . '$'
239             . MIME::Base64::encode($hash, "");
240             }
241              
242             sub _encode_string_ldaplike {
243 511     511   756 my ($self, $salt, $hash) = @_;
244 511         6240 my $hasher = $self->hasher;
245 511         2913 my $hasher_class = blessed($hasher);
246 511 50 33     2799 if (!defined $hasher_class || $hasher_class !~ s/^Crypt::PBKDF2::Hash:://) {
247 0         0 croak "Can't ''encode_string'' with a hasher class outside of Crypt::PBKDF2::Hash::*";
248             }
249              
250 511         962 my $algo_string = $hasher->to_algo_string;
251 511 50       1121 $algo_string = defined($algo_string) ? "+$algo_string" : "";
252              
253 511         1355 return '{X-PBKDF2}' . "$hasher_class$algo_string:"
254             . $self->_b64_encode_int32($self->iterations) . ':'
255             . MIME::Base64::encode($salt, "") . ':'
256             . MIME::Base64::encode($hash, "");
257             }
258              
259              
260             sub decode_string {
261 1022     1022 1 1531 my ($self, $hashed) = @_;
262 1022 100       3334 if ($hashed =~ /^\$PBKDF2\$/) {
    50          
263 511         966 return $self->_decode_string_cryptlike($hashed);
264             } elsif ($hashed =~ /^\{X-PBKDF2}/i) {
265 511         1078 return $self->_decode_string_ldaplike($hashed);
266             } else {
267 0         0 croak "Unrecognized hash";
268             }
269             }
270              
271             sub _decode_string_cryptlike {
272 511     511   596 my ($self, $hashed) = @_;
273 511 50       1016 if ($hashed !~ /^\$PBKDF2\$/) {
274 0         0 croak "Unrecognized hash";
275             }
276              
277 511 50       3814 if (my ($algorithm, $opts, $iterations, $salt, $hash) = $hashed =~
278             /^\$PBKDF2\$([^:}]+)(?:\{([^}]+)\})?:(\d+):([^\$]+)\$(.*)/) {
279             return {
280 511         3624 algorithm => $algorithm,
281             algorithm_options => $opts,
282             iterations => $iterations,
283             salt => MIME::Base64::decode($salt),
284             hash => MIME::Base64::decode($hash),
285             }
286             } else {
287 0         0 croak "Invalid format";
288             }
289             }
290              
291             sub _decode_string_ldaplike {
292 511     511   642 my ($self, $hashed) = @_;
293 511 50       1299 if ($hashed !~ /^\{X-PBKDF2}/i) {
294 0         0 croak "Unrecognized hash";
295             }
296              
297 511 50       2945 if (my ($algo_str, $iterations, $salt, $hash) = $hashed =~
298             /^\{X-PBKDF2}([^:]+):([^:]{6}):([^\$]+):(.*)/i) {
299 511         1420 my ($algorithm, $opts) = split /\+/, $algo_str;
300             return {
301 511         1092 algorithm => $algorithm,
302             algorithm_options => $opts,
303             iterations => $self->_b64_decode_int32($iterations),
304             salt => MIME::Base64::decode($salt),
305             hash => MIME::Base64::decode($hash),
306             }
307             } else {
308 0         0 croak "Invalid format";
309             }
310             }
311              
312              
313             sub hasher_from_algorithm {
314 1022     1022 1 1990 my ($self, $algorithm, $args) = @_;
315 1022         2769 my $class = Module::Runtime::use_module("Crypt::PBKDF2::Hash::$algorithm");
316              
317 1022 50       28470 if (defined $args) {
318 1022         2680 return $class->from_algo_string($args);
319             } else {
320 0         0 return $class->new;
321             }
322             }
323              
324              
325             sub clone {
326 1034     1034 1 7176 my ($self, %params) = @_;
327 1034         1601 my $class = ref $self;
328              
329             # If the hasher was built from hash_class and hash_args, then omit it from
330             # the clone. But if it was set by the user, then we need to copy it. We're
331             # assuming that the hasher has no state, so it doesn't need a deep clone.
332             # This is true of all of the ones that I'm shipping, but if it's not true for
333             # you, let me know.
334              
335 1034 50       10241 my %new_args = (
    50          
    50          
    100          
336             $self->has_hash_class ? (hash_class => $self->hash_class) : (),
337             $self->has_hash_args ? (hash_args => $self->hash_args) : (),
338             $self->has_output_len ? (output_len => $self->output_len) : (),
339             $self->has_lazy_hasher ? () : (hasher => $self->hasher),
340             iterations => $self->iterations,
341             salt_len => $self->salt_len,
342             %params,
343             );
344            
345 1034         17761 return $class->new(%new_args);
346             }
347              
348             sub _b64_encode_int32 {
349 511     511   846 my ($self, $value) = @_;
350 511         2445 my $b64 = MIME::Base64::encode(pack("N", $value), "");
351 511         1665 $b64 =~ s/==$//;
352 511         3108 return $b64;
353             }
354              
355             sub _b64_decode_int32 {
356 511     511   662 my ($self, $b64) = @_;
357 511         594 $b64 .= "==";
358 511         4631 return unpack "N", MIME::Base64::decode($b64);
359             }
360              
361             __PACKAGE__->meta->make_immutable;
362             1;
363              
364             __END__