File Coverage

blib/lib/PBKDF2/Tiny.pm
Criterion Covered Total %
statement 53 53 100.0
branch 14 18 77.7
condition 4 8 50.0
subroutine 11 11 100.0
pod 6 6 100.0
total 88 96 91.6


line stmt bran cond sub pod time code
1 1     1   24853 use strict;
  1         2  
  1         51  
2 1     1   6 use warnings;
  1         2  
  1         69  
3              
4             package PBKDF2::Tiny;
5             # ABSTRACT: Minimalist PBKDF2 (RFC 2898) with HMAC-SHA1 or HMAC-SHA2
6              
7             our $VERSION = '0.005';
8              
9 1     1   6 use Carp ();
  1         1  
  1         42  
10 1     1   6 use Exporter 5.57 qw/import/;
  1         23  
  1         195  
11              
12             our @EXPORT_OK = qw/derive derive_hex verify verify_hex hmac digest_fcn/;
13              
14             my ( $BACKEND, $LOAD_ERR );
15             for my $mod (qw/Digest::SHA Digest::SHA::PurePerl/) {
16             $BACKEND = $mod, last if eval "require $mod; 1";
17             $LOAD_ERR ||= $@;
18             }
19             die $LOAD_ERR if !$BACKEND;
20              
21             #--------------------------------------------------------------------------#
22             # constants and lookup tables
23             #--------------------------------------------------------------------------#
24              
25             # function coderef placeholder, block size in bytes, digest size in bytes
26             my %DIGEST_TYPES = (
27             'SHA-1' => [ undef, 64, 20 ],
28             'SHA-224' => [ undef, 64, 28 ],
29             'SHA-256' => [ undef, 64, 32 ],
30             'SHA-384' => [ undef, 128, 48 ],
31             'SHA-512' => [ undef, 128, 64 ],
32             );
33              
34             for my $type ( keys %DIGEST_TYPES ) {
35 1     1   7 no strict 'refs';
  1         2  
  1         792  
36             ( my $name = lc $type ) =~ s{-}{};
37             $DIGEST_TYPES{$type}[0] = \&{"$BACKEND\::$name"};
38             }
39              
40             my %INT = map { $_ => pack( "N", $_ ) } 1 .. 16;
41              
42             #--------------------------------------------------------------------------#
43             # public functions
44             #--------------------------------------------------------------------------#
45              
46             #pod =func derive
47             #pod
48             #pod $dk = derive( $type, $password, $salt, $iterations, $dk_length )
49             #pod
50             #pod The C function outputs a binary string with the derived key.
51             #pod The first argument indicates the digest function to use. It must be one
52             #pod of: SHA-1, SHA-224, SHA-256, SHA-384, or SHA-512.
53             #pod
54             #pod If a password or salt are not provided, they default to the empty string, so
55             #pod don't do that! L
56             #pod recommends|https://tools.ietf.org/html/rfc2898#section-4.1> a random salt of at
57             #pod least 8 octets. If you need a cryptographically strong salt, consider
58             #pod L.
59             #pod
60             #pod The password and salt should encoded as octet strings. If not (i.e. if
61             #pod Perl's internal 'UTF8' flag is on), then an exception will be thrown.
62             #pod
63             #pod The number of iterations defaults to 1000 if not provided. If the derived
64             #pod key length is not provided, it defaults to the output size of the digest
65             #pod function.
66             #pod
67             #pod =cut
68              
69             sub derive {
70 102     102 1 13967 my ( $type, $passwd, $salt, $iterations, $dk_length ) = @_;
71              
72 102         262 my ( $digester, $block_size, $digest_length ) = digest_fcn($type);
73              
74 102 50       293 $passwd = '' unless defined $passwd;
75 102 50       211 $salt = '' unless defined $salt;
76 102   50     201 $iterations ||= 1000;
77 102   66     242 $dk_length ||= $digest_length;
78              
79             # we insist on octet strings for password and salt
80 102 100       669 Carp::croak("password must be an octet string, not a character string")
81             if utf8::is_utf8($passwd);
82 100 100       666 Carp::croak("salt must be an octet string, not a character string")
83             if utf8::is_utf8($salt);
84              
85 98 50       483 my $key = ( length($passwd) > $block_size ) ? $digester->($passwd) : $passwd;
86 98         256 my $passes = int( $dk_length / $digest_length );
87 98 100       236 $passes++ if $dk_length % $digest_length; # need part of an extra pass
88              
89 98         111 my $dk = "";
90 98         192 for my $i ( 1 .. $passes ) {
91 106   33     353 $INT{$i} ||= pack( "N", $i );
92 106         301 my $digest = my $result =
93             "" . hmac( $salt . $INT{$i}, $key, $digester, $block_size );
94 106         251 for my $iter ( 2 .. $iterations ) {
95 138258         154776 $digest = hmac( $digest, $key, $digester, $block_size );
96 138258         161484 $result ^= $digest;
97             }
98 106         536 $dk .= $result;
99             }
100              
101 98         568 return substr( $dk, 0, $dk_length );
102             }
103              
104             #pod =func derive_hex
105             #pod
106             #pod Works just like L but outputs a hex string.
107             #pod
108             #pod =cut
109              
110 16     16 1 10914 sub derive_hex { unpack( "H*", &derive ) }
111              
112             #pod =func verify
113             #pod
114             #pod $bool = verify( $dk, $type, $password, $salt, $iterations, $dk_length );
115             #pod
116             #pod The C function checks that a given derived key (in binary form) matches
117             #pod the password and other parameters provided using a constant-time comparison
118             #pod function.
119             #pod
120             #pod The first parameter is the derived key to check. The remaining parameters
121             #pod are the same as for L.
122             #pod
123             #pod =cut
124              
125             sub verify {
126 62     62 1 8736 my ( $dk1, @derive_args ) = @_;
127              
128 62         155 my $dk2 = derive(@derive_args);
129              
130             # shortcut if input dk is the wrong length entirely; this is not
131             # constant time, but this doesn't really give much away as
132             # the keys are of different types anyway
133              
134 62 100       265 return unless length($dk1) == length($dk2);
135              
136             # if lengths match, do constant time comparison to avoid timing attacks
137 60         91 my $match = 1;
138 60         188 for my $i ( 0 .. length($dk1) - 1 ) {
139 1940 100       2881 $match &= ( substr( $dk1, $i, 1 ) eq substr( $dk2, $i, 1 ) ) ? 1 : 0;
140             }
141              
142 60         695 return $match;
143             }
144              
145             #pod =func verify_hex
146             #pod
147             #pod Works just like L but the derived key must be a hex string (without a
148             #pod leading "0x").
149             #pod
150             #pod =cut
151              
152             sub verify_hex {
153 34     34 1 937 my $dk = pack( "H*", shift );
154 34         98 return verify( $dk, @_ );
155             }
156              
157             #pod =func digest_fcn
158             #pod
159             #pod ($fcn, $block_size, $digest_length) = digest_fcn('SHA-1');
160             #pod $digest = $fcn->($data);
161             #pod
162             #pod This function is used internally by PBKDF2::Tiny, but made available in case
163             #pod it's useful to someone.
164             #pod
165             #pod Given one of the valid digest types, it returns a function reference that
166             #pod digests a string of data. It also returns block size and digest length for that
167             #pod digest type.
168             #pod
169             #pod =cut
170              
171             sub digest_fcn {
172 102     102 1 144 my ($type) = @_;
173              
174 102 50       380 Carp::croak("Digest function '$type' not supported")
175             unless exists $DIGEST_TYPES{$type};
176              
177 102         81 return @{ $DIGEST_TYPES{$type} };
  102         302  
178             }
179              
180             #pod =func hmac
181             #pod
182             #pod $key = $digest_fcn->($key) if length($key) > $block_size;
183             #pod $hmac = hmac( $data, $key, $digest_fcn, $block_size );
184             #pod
185             #pod This function is used internally by PBKDF2::Tiny, but made available in case
186             #pod it's useful to someone.
187             #pod
188             #pod The first two arguments are the data and key inputs to the HMAC function. Both
189             #pod should be encoded as octet strings, as underlying HMAC/digest functions may
190             #pod croak or may give unexpected results if Perl's internal UTF-8 flag is on.
191             #pod
192             #pod B: if the key is longer than the digest block size, it must be
193             #pod preprocessed using the digesting function.
194             #pod
195             #pod The third and fourth arguments must be a digesting code reference (from
196             #pod L) and block size.
197             #pod
198             #pod =cut
199              
200             # hmac function adapted from Digest::HMAC by Graham Barr and Gisle Aas.
201             # Compared to that implementation, this *requires* a preprocessed
202             # key and block size, which makes iterative hmac slightly more efficient.
203             sub hmac {
204 138364     138364 1 141623 my ( $data, $key, $digest_func, $block_size ) = @_;
205              
206 138364         162539 my $k_ipad = $key ^ ( chr(0x36) x $block_size );
207 138364         139433 my $k_opad = $key ^ ( chr(0x5c) x $block_size );
208              
209 138364         946995 &$digest_func( $k_opad, &$digest_func( $k_ipad, $data ) );
210             }
211              
212             1;
213              
214              
215             # vim: ts=4 sts=4 sw=4 et:
216              
217             __END__