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.160010'; # TRIAL VERSION
4             our $AUTHORITY = 'cpan:ARODLAND'; # AUTHORITY
5 6     6   134238 use Moo 2;
  6         87881  
  6         42  
6 6     6   14208 use strictures 2;
  6         9567  
  6         1394  
7 6     6   5785 use namespace::autoclean;
  6         95756  
  6         29  
8 6     6   6059 use MIME::Base64 ();
  6         6714  
  6         177  
9 6     6   33 use Carp qw(croak);
  6         11  
  6         308  
10 6     6   30 use Module::Runtime;
  6         11  
  6         41  
11 6     6   159 use Try::Tiny;
  6         11  
  6         353  
12 6     6   5575 use Type::Tiny;
  6         124587  
  6         257  
13 6     6   5403 use Types::Standard qw(Str Int HashRef ConsumerOf);
  6         289905  
  6         83  
14 6     6   6611 use Scalar::Util qw(blessed);
  6         15  
  6         13797  
15              
16             sub BUILD {
17 1044     1044 0 205268 my ($self) = @_;
18 1044         22893 $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   4250 my ($self) = @_;
56 20         72 my $class = $self->hash_class;
57 20 50       134 if ($class !~ s/^\+//) {
58 20         60 $class = "Crypt::PBKDF2::Hash::$class";
59             }
60 20         66 my $hash_args = $self->hash_args;
61              
62 20         84 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   1520 my ($self) = @_;
88 1023         1416 my $ret = "";
89 1023         3249 for my $n (1 .. $self->salt_len) {
90 4680         8284 $ret .= chr(int rand 256);
91             }
92 1023         2337 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 576888 my ($self, $password, $salt) = @_;
112 1023 50       4268 $salt = $self->_random_salt unless defined $salt;
113              
114 1023 100 100     3819 if ($self->has_length_limit and length($password) > $self->length_limit) {
115 1         162 croak "Password exceeds length limit";
116             }
117              
118 1022         2306 my $hash = $self->PBKDF2($salt, $password);
119 1022         2500 return $self->encode_string($salt, $hash);
120             }
121              
122              
123             sub validate {
124 1023     1023 1 5721 my ($self, $hashed, $password) = @_;
125              
126 1023 100 100     3067 if ($self->has_length_limit and length($password) > $self->length_limit) {
127 1         97 croak "Password exceeds length limit";
128             }
129              
130 1022         2102 my $info = $self->decode_string($hashed);
131              
132             my $hasher = try {
133 1022     1022   35852 $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         7941 };
138              
139             my $checker = $self->clone(
140             hasher => $hasher,
141             iterations => $info->{iterations},
142 1022         25349 output_len => length($info->{hash}),
143             );
144              
145 1022         32033 my $check_hash = $checker->PBKDF2($info->{salt}, $password);
146              
147 1022         9346 return ($check_hash eq $info->{hash});
148             }
149              
150              
151             sub PBKDF2 {
152 5053     5053 1 8674 my ($self, $salt, $password) = @_;
153 5053         9790 my $iterations = $self->iterations;
154 5053         121583 my $hasher = $self->hasher;
155 5053   66     48886 my $output_len = $self->output_len || $hasher->hash_len;
156              
157 5053         13975 my $hLen = $hasher->hash_len;
158 5053         9870 my $l = int($output_len / $hLen);
159 5053         7946 my $r = $output_len % $hLen;
160              
161 5053 50 33     18321 if ($l > 0xffffffff or $l == 0xffffffff && $r > 0) {
      33        
162 0         0 croak "output_len too large for PBKDF2";
163             }
164              
165 5053         5484 my $output;
166              
167 5053         11362 for my $i (1 .. $l) {
168 5050         11174 $output .= $self->_PBKDF2_F($hasher, $salt, $password, $iterations, $i);
169             }
170              
171 5053 100       11198 if ($r) {
172 9         31 $output .= substr( $self->_PBKDF2_F($hasher, $salt, $password, $iterations, $l + 1), 0, $r);
173             }
174              
175 5053         21670 return $output;
176             }
177              
178              
179             sub PBKDF2_base64 {
180 1000     1000 1 1471 my $self = shift;
181              
182 1000         2472 return MIME::Base64::encode( $self->PBKDF2(@_), "" );
183             }
184              
185              
186             sub PBKDF2_hex {
187 1009     1009 1 3751 my $self = shift;
188 1009         2388 return unpack "H*", $self->PBKDF2(@_);
189             }
190              
191             sub _PBKDF2_F {
192 5059     5059   9217 my ($self, $hasher, $salt, $password, $iterations, $i) = @_;
193 5059         22829 my $result =
194             my $hash =
195             $hasher->generate( $salt . pack("N", $i), $password );
196              
197 5059         10848 for my $iter (2 .. $iterations) {
198 209650         492169 $hash = $hasher->generate( $hash, $password );
199 209650         377620 $result ^= $hash;
200             }
201              
202 5059         15839 return $result;
203             }
204              
205              
206             sub encode_string {
207 1022     1022 1 1893 my ($self, $salt, $hash) = @_;
208 1022 100       3375 if ($self->encoding eq 'crypt') {
    50          
209 511         1137 return $self->_encode_string_cryptlike($salt, $hash);
210             } elsif ($self->encoding eq 'ldap') {
211 511         1141 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   770 my ($self, $salt, $hash) = @_;
219 511         11240 my $hasher = $self->hasher;
220 511         4513 my $hasher_class = blessed($hasher);
221 511 50 33     3186 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         1671 my $algo_string = $hasher->to_algo_string;
226 511 100       1092 $algo_string = defined($algo_string) ? "{$algo_string}" : "";
227              
228 511         4293 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   798 my ($self, $salt, $hash) = @_;
235 511         11361 my $hasher = $self->hasher;
236 511         4411 my $hasher_class = blessed($hasher);
237 511 50 33     3264 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         1645 my $algo_string = $hasher->to_algo_string;
242 511 100       1051 $algo_string = defined($algo_string) ? "+$algo_string" : "";
243              
244 511         1878 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 1548 my ($self, $hashed) = @_;
253 1022 100       3584 if ($hashed =~ /^\$PBKDF2\$/) {
    50          
254 511         1165 return $self->_decode_string_cryptlike($hashed);
255             } elsif ($hashed =~ /^\{X-PBKDF2}/i) {
256 511         1109 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   687 my ($self, $hashed) = @_;
264 511 50       1558 if ($hashed !~ /^\$PBKDF2\$/) {
265 0         0 croak "Unrecognized hash";
266             }
267              
268 511 50       3525 if (my ($algorithm, $opts, $iterations, $salt, $hash) = $hashed =~
269             /^\$PBKDF2\$([^:}]+)(?:\{([^}]+)\})?:(\d+):([^\$]+)\$(.*)/) {
270             return {
271 511         3990 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   752 my ($self, $hashed) = @_;
284 511 50       1403 if ($hashed !~ /^\{X-PBKDF2}/i) {
285 0         0 croak "Unrecognized hash";
286             }
287              
288 511 50       3845 if (my ($algo_str, $iterations, $salt, $hash) = $hashed =~
289             /^\{X-PBKDF2}([^:]+):([^:]{6}):([^\$]+):(.*)/i) {
290 511         1222 my ($algorithm, $opts) = split /\+/, $algo_str;
291             return {
292 511         1260 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 1662 my ($self, $algorithm, $args) = @_;
306 1022         4136 my $class = Module::Runtime::use_module("Crypt::PBKDF2::Hash::$algorithm");
307              
308 1022 100       27584 if (defined $args) {
309 21         77 return $class->from_algo_string($args);
310             } else {
311 1001         3431 return $class->new;
312             }
313             }
314              
315              
316             sub clone {
317 1034     1034 1 6564 my ($self, %params) = @_;
318 1034         1775 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       12140 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         26420 return $class->new(%new_args);
337             }
338              
339             sub _b64_encode_int32 {
340 511     511   855 my ($self, $value) = @_;
341 511         1937 my $b64 = MIME::Base64::encode(pack("N", $value), "");
342 511         1759 $b64 =~ s/==$//;
343 511         3712 return $b64;
344             }
345              
346             sub _b64_decode_int32 {
347 511     511   766 my ($self, $b64) = @_;
348 511         682 $b64 .= "==";
349 511         4960 return unpack "N", MIME::Base64::decode($b64);
350             }
351              
352             __PACKAGE__->meta->make_immutable;
353             1;
354              
355             __END__