File Coverage

blib/lib/PBKDF2/Tiny.pm
Criterion Covered Total %
statement 53 53 100.0
branch 12 18 66.6
condition 4 8 50.0
subroutine 11 11 100.0
pod 6 6 100.0
total 86 96 89.5


line stmt bran cond sub pod time code
1 1     1   17955 use strict;
  1         2  
  1         36  
2 1     1   4 use warnings;
  1         1  
  1         41  
3              
4             package PBKDF2::Tiny;
5             # ABSTRACT: Minimalist PBKDF2 (RFC 2898) with HMAC-SHA1 or HMAC-SHA2
6              
7             our $VERSION = '0.004';
8              
9 1     1   4 use Carp ();
  1         2  
  1         26  
10 1     1   4 use Exporter 5.57 qw/import/;
  1         15  
  1         124  
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   4 no strict 'refs';
  1         2  
  1         603  
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 66     66 1 14932 my ( $type, $passwd, $salt, $iterations, $dk_length ) = @_;
71              
72 66         180 my ( $digester, $block_size, $digest_length ) = digest_fcn($type);
73              
74 66 50       246 $passwd = '' unless defined $passwd;
75 66 50       127 $salt = '' unless defined $salt;
76 66   50     110 $iterations ||= 1000;
77 66   66     155 $dk_length ||= $digest_length;
78              
79             # we insist on octet strings for password and salt
80 66 100       618 Carp::croak("password must be an octet string, not a character string")
81             if utf8::is_utf8($passwd);
82 64 100       531 Carp::croak("salt must be an octet string, not a character string")
83             if utf8::is_utf8($salt);
84              
85 62 50       178 my $key = ( length($passwd) > $block_size ) ? $digester->($passwd) : $passwd;
86 62         151 my $passes = int( $dk_length / $digest_length );
87 62 100       209 $passes++ if $dk_length % $digest_length; # need part of an extra pass
88              
89 62         80 my $dk = "";
90 62         108 for my $i ( 1 .. $passes ) {
91 66   33     227 $INT{$i} ||= pack( "N", $i );
92 66         203 my $digest = my $result =
93             "" . hmac( $salt . $INT{$i}, $key, $digester, $block_size );
94 66         149 for my $iter ( 2 .. $iterations ) {
95 87510         88806 $digest = hmac( $digest, $key, $digester, $block_size );
96 87510         93102 $result ^= $digest;
97             }
98 66         158 $dk .= $result;
99             }
100              
101 62         367 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 14     14 1 8866 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 28     28 1 8241 my ( $dk1, $type, $password, $salt, $iterations, $dk_length ) = @_;
127              
128 28         63 my $dk2 = derive( $type, $password, $salt, $iterations, $dk_length );
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 28 50       100 return unless length($dk1) == length($dk2);
135              
136             # if lengths match, do constant time comparison to avoid timing attacks
137 28         44 my $match = 1;
138 28         61 for my $offset ( 0 .. $dk_length ) {
139 958 50       1318 $match &= ( substr( $dk1, $offset, 1 ) eq substr( $dk2, $offset, 1 ) ) ? 1 : 0;
140             }
141              
142 28         240 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 14     14 1 83 my $dk = pack( "H*", shift );
154 14         34 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 66     66 1 84 my ($type) = @_;
173              
174 66 50       238 Carp::croak("Digest function '$type' not supported")
175             unless exists $DIGEST_TYPES{$type};
176              
177 66         57 return @{ $DIGEST_TYPES{$type} };
  66         196  
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 87576     87576 1 81819 my ( $data, $key, $digest_func, $block_size ) = @_;
205              
206 87576         90274 my $k_ipad = $key ^ ( chr(0x36) x $block_size );
207 87576         78301 my $k_opad = $key ^ ( chr(0x5c) x $block_size );
208              
209 87576         510650 &$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__