File Coverage

blib/lib/Crypt/SaltedHash.pm
Criterion Covered Total %
statement 82 87 94.2
branch 18 24 75.0
condition 13 15 86.6
subroutine 16 18 88.8
pod 8 8 100.0
total 137 152 90.1


line stmt bran cond sub pod time code
1             package Crypt::SaltedHash;
2            
3 3     3   69443 use strict;
  3         7  
  3         119  
4 3     3   4038 use MIME::Base64 ();
  3         4419  
  3         598  
5 3     3   3724 use Digest ();
  3         3256  
  3         73  
6            
7 3     3   20 use vars qw($VERSION);
  3         6  
  3         5589  
8            
9             $VERSION = '0.09';
10            
11             =encoding latin1
12            
13             =head1 NAME
14            
15             Crypt::SaltedHash - Perl interface to functions that assist in working
16             with salted hashes.
17            
18             =head1 SYNOPSIS
19            
20             use Crypt::SaltedHash;
21            
22             my $csh = Crypt::SaltedHash->new(algorithm => 'SHA-1');
23             $csh->add('secret');
24            
25             my $salted = $csh->generate;
26             my $valid = Crypt::SaltedHash->validate($salted, 'secret');
27            
28            
29             =head1 DESCRIPTION
30            
31             The C module provides an object oriented interface to
32             create salted (or seeded) hashes of clear text data. The original
33             formalization of this concept comes from RFC-3112 and is extended by the use
34             of different digital agorithms.
35            
36             =head1 ABSTRACT
37            
38             =head2 Setting the data
39            
40             The process starts with 2 elements of data:
41            
42             =over
43            
44             =item *
45            
46             a clear text string (this could represent a password for instance).
47            
48             =item *
49            
50             the salt, a random seed of data. This is the value used to augment a hash in order to
51             ensure that 2 hashes of identical data yield different output.
52            
53             =back
54            
55             For the purposes of this abstract we will analyze the steps within code that perform the necessary actions
56             to achieve the endresult hashes. Cryptographers call this hash a digest. We will not however go into an explanation
57             of a one-way encryption scheme. Readers of this abstract are encouraged to get information on that subject by
58             their own.
59            
60             Theoretically, an implementation of a one-way function as an algorithm takes input, and provides output, that are both
61             in binary form; realistically though digests are typically encoded and stored in a database or in a flat text or XML file.
62             Take slappasswd5 for instance, it performs the exact functionality described above. We will use it as a black box compiled
63             piece of code for our analysis.
64            
65             In pseudocode we generate a salted hash as follows:
66            
67             Get the source string and salt as separate binary objects
68             Concatenate the 2 binary values
69             Hash the concatenation into SaltedPasswordHash
70             Base64Encode(concat(SaltedPasswordHash, Salt))
71            
72             We take a clear text string and hash this into a binary object representing the hashed value of the clear text string plus the random salt.
73             Then we have the Salt value, which are typically 4 bytes of purely random binary data represented as hexadecimal notation (Base16 as 8 bytes).
74            
75             Using SHA-1 as the hashing algorithm, SaltedPasswordHash is of length 20 (bytes) in raw binary form
76             (40 bytes if we look at it in hex). Salt is then 4 bytes in raw binary form. The SHA-1 algorithm generates
77             a 160 bit hash string. Consider that 8 bits = 1 byte. So 160 bits = 20 bytes, which is exactly what the
78             algorithm gives us.
79            
80             The Base64 encoding of the binary result looks like:
81            
82             {SSHA}B0O0XSYdsk7g9K229ZEr73Lid7HBD9DX
83            
84             Take note here that the final output is a 32-byte string of data. The Base64 encoding process uses bit shifting, masking, and padding as per RFC-3548.
85            
86             A couple of examples of salted hashes using on the same exact clear-text string:
87            
88             slappasswd -s testing123
89             {SSHA}72uhy5xc1AWOLwmNcXALHBSzp8xt4giL
90            
91             slappasswd -s testing123
92             {SSHA}zmIAVaKMmTngrUi4UlS0dzYwVAbfBTl7
93            
94             slappasswd -s testing123
95             {SSHA}Be3F12VVvBf9Sy6MSqpOgAdEj6JCZ+0f
96            
97             slappasswd -s testing123
98             {SSHA}ncHs4XYmQKJqL+VuyNQzQjwRXfvu6noa
99            
100             4 runs of slappasswd against the same clear text string each yielded unique endresult hashes.
101             The random salt is generated silently and never made visible.
102            
103             =head2 Extracting the data
104            
105             One of the keys to note is that the salt is dealt with twice in the process. It is used once for the actual application of randomness to the
106             given clear text string, and then it is stored within the final output as purely Base64 encoded data. In order to perform an authentication
107             query for instance, we must break apart the concatenation that was created for storage of the data. We accomplish this by splitting
108             up the binary data we get after Base64 decoding the stored hash.
109            
110             In pseudocode we would perform the extraction and verification operations as such:
111            
112             Strip the hash identifier from the Digest
113             Base64Decode(Digest, 20)
114             Split Digest into 2 byte arrays, one for bytes 0 – 20(pwhash), one for bytes 21 – 32 (salt)
115             Get the target string and salt as separate binary object
116             Concatenate the 2 binary values
117             SHA hash the concatenation into targetPasswordHash
118             Compare targetPasswordHash with pwhash
119             Return corresponding Boolean value
120            
121             Our job is to split the original digest up into 2 distinct byte arrays, one of the left 20 (0 - 20 including the null terminator) bytes and
122             the other for the rest of the data. The left 0 – 20 bytes will represent the salted binary value we will use for a byte-by-byte data
123             match against the new clear text presented for verification. The string presented for verification will have to be salted as well. The rest
124             of the bytes (21 – 32) represent the random salt which when decoded will show the exact hex characters that make up the once randomly
125             generated seed.
126            
127             We are now ready to verify some data. Let's start with the 4 hashes presented earlier. We will run them through our code to extract the
128             random salt and then using that verify the clear text string hashed by slappasswd. First, let's do a verification test with an erroneous
129             password; this should fail the matching test:
130            
131             {SSHA}72uhy5xc1AWOLwmNcXALHBSzp8xt4giL Test123
132             Hash extracted (in hex): ef6ba1cb9c5cd4058e2f098d71700b1c14b3a7cc
133             Salt extracted (in hex): 6de2088b
134             Hash length is: 20 Salt length is: 4
135             Hash presented in hex: 256bc48def0ce04b0af90dfd2808c42588bf9542
136             Hashes DON'T match: Test123
137            
138             The match failure test was successful as expected. Now let's use known valid data through the same exact code:
139            
140             {SSHA}72uhy5xc1AWOLwmNcXALHBSzp8xt4giL testing123
141             Hash extracted (in hex): ef6ba1cb9c5cd4058e2f098d71700b1c14b3a7cc
142             Salt extracted (in hex): 6de2088b
143             Hash length is: 20 Salt length is: 4
144             Hash presented in hex: ef6ba1cb9c5cd4058e2f098d71700b1c14b3a7cc
145             Hashes match: testing123
146            
147             The process used for salted passwords should now be clear. We see that salting hashed data does indeed add another layer of security to the
148             clear text one-way hashing process. But we also see that salted hashes should also be protected just as if the data was in clear text form.
149             Now that we have seen salted hashes actually work you should also realize that in code it is possible to extract salt values and use them
150             for various purposes. Obviously the usage can be on either side of the colored hat line, but the data is there.
151            
152             =head1 METHODS
153            
154             =over 4
155            
156             =item B
157            
158             Returns a new Crypt::SaltedHash object.
159             Possible keys for I<%options> are:
160            
161             =over
162            
163             =item *
164            
165             I: It's also possible to use common string representations of the
166             algorithm (e.g. "sha256", "SHA-384"). If the argument is missing, SHA-1 will
167             be used by default.
168            
169             =item *
170            
171             I: You can specify your on salt. You can either specify it as a sequence
172             of charactres or as a hex encoded string of the form "HEX{...}". If the argument is missing,
173             a random seed is provided for you (recommended).
174            
175             =item *
176            
177             I: By default, the module assumes a salt length of 4 bytes (or 8, if it is encoded in hex).
178             If you choose a different length, you have to tell the I function how long your seed was.
179            
180             =back
181            
182             =cut
183            
184             sub new {
185 9     9 1 1544 my ( $class, %options ) = @_;
186            
187 9   100     30 $options{algorithm} ||= 'SHA-1';
188 9   100     37 $options{salt_len} ||= 4;
189 9   66     37 $options{salt} ||= &__generate_hex_salt( $options{salt_len} * 2 );
190            
191 9         23 $options{algorithm} = uc( $options{algorithm} );
192 9 50       27 $options{algorithm} .= '-1'
193             if $options{algorithm} =~ m!SHA$!; # SHA => SHA-1, HMAC-SHA => HMAC-SHA-1
194            
195 9         42 my $digest = Digest->new( $options{algorithm} );
196 9         28222 my $self = {
197             salt => $options{salt},
198             algorithm => $options{algorithm},
199             digest => $digest,
200             scheme => &__make_scheme( $options{algorithm} ),
201             };
202            
203 9         40 return bless $self, $class;
204             }
205            
206             =item B
207            
208             Logically joins the arguments into a single string, and uses it to
209             update the current digest state. For more details see L.
210            
211             =cut
212            
213             sub add {
214 10     10 1 46 my $self = shift;
215 10         22 $self->obj->add(@_);
216 10         19 return $self;
217             }
218            
219             =item B
220            
221             Resets the digest.
222            
223             =cut
224            
225             sub clear {
226 0     0 1 0 my $self = shift;
227 0         0 $self->{digest} = Digest->new( $self->{algorithm} );
228 0         0 return $self;
229             }
230            
231             =item B
232            
233             Returns the salt in binary form.
234            
235             =cut
236            
237             sub salt_bin {
238 10     10 1 12 my $self = shift;
239            
240 10 100       81 return $self->{salt} =~ m!^HEX\{(.*)\}$!i ? pack( "H*", $1 ) : $self->{salt};
241             }
242            
243             =item B
244            
245             Returns the salt in hexadecimal form ('HEX{...}')
246            
247             =cut
248            
249             sub salt_hex {
250 0     0 1 0 my $self = shift;
251            
252 0 0       0 return $self->{salt} =~ m!^HEX\{(.*)\}$!i
253             ? $self->{salt}
254             : 'HEX{' . join( '', unpack( 'H*', $self->{salt} ) ) . '}';
255             }
256            
257             =item B
258            
259             Generates the seeded hash. Uses the I-method of L before actually performing
260             the digest calculation, so adding more cleardata after a call of I to an instance of
261             I has the same effect as adding the data before the call of I.
262            
263             =cut
264            
265             sub generate {
266 10     10 1 26 my $self = shift;
267            
268 10         20 my $clone = $self->obj->clone;
269 10         42 my $salt = $self->salt_bin;
270            
271 10         28 $clone->add($salt);
272            
273 10         77 my $gen = &MIME::Base64::encode_base64( $clone->digest . $salt, '' );
274 10         20 my $scheme = $self->{scheme};
275            
276 10         64 return "{$scheme}$gen";
277             }
278            
279             =item B
280            
281             Validates a hasheddata previously generated against cleardata. I<$salt_len> defaults to 4 if not set.
282             Returns 1 if the validation is successful, 0 otherwise.
283            
284             =cut
285            
286             sub validate {
287 4     4 1 1209 my ( undef, $hasheddata, $cleardata, $salt_len ) = @_;
288            
289             # trim white-spaces
290 4         12 $hasheddata =~ s!^\s+!!;
291 4         9 $hasheddata =~ s!\s+$!!;
292            
293 4         15 my $scheme = &__get_pass_scheme($hasheddata);
294 4 100       12 $scheme = uc( $scheme ) if $scheme;
295 4         573 my $algorithm = &__make_algorithm($scheme);
296 4   100     13 my $hash = &__get_pass_hash($hasheddata) || '';
297 4         10 my $salt = &__extract_salt( $hash, $salt_len );
298            
299 4         22 my $obj = __PACKAGE__->new(
300             algorithm => $algorithm,
301             salt => $salt,
302             salt_len => $salt_len
303             );
304 4         32 $obj->add($cleardata);
305            
306 4         9 my $gen_hasheddata = $obj->generate;
307 4         22 my $gen_hash = &__get_pass_hash($gen_hasheddata);
308            
309 4         43 return $gen_hash eq $hash;
310             }
311            
312             =item B
313            
314             Returns a handle to L object.
315            
316             =cut
317            
318             sub obj {
319 20     20 1 93 return shift->{digest};
320             }
321            
322             =back
323            
324             =head1 FUNCTIONS
325            
326             I
327            
328             =cut
329            
330             sub __make_scheme {
331            
332 9     9   17 my $scheme = shift;
333            
334 9         28 my @parts = split /-/, $scheme;
335 9 100       28 pop @parts if $parts[-1] eq '1'; # SHA-1 => SHA
336            
337 9         21 $scheme = join '', @parts;
338            
339 9         52 return uc("S$scheme");
340             }
341            
342             sub __make_algorithm {
343 4     4   21 my ( $algorithm ) = @_;
344            
345 4   100     18 $algorithm ||= '';
346 4         8 local $1;
347            
348 4 100       16 if ( $algorithm =~ m!^S(.*)$! ) {
349 3         5 $algorithm = $1;
350            
351             # print STDERR "algorithm: $algorithm\n";
352 3 50       14 if ( $algorithm =~ m!([a-zA-Z]+)([0-9]+)! ) {
353            
354 3         5 my $name = uc($1);
355 3         7 my $digits = $2;
356            
357             # print STDERR "name: $name\n";
358             # print STDERR "digits: $digits\n";
359            
360 3 50       6 $name = "HMAC-$2" if $name =~ m!^HMAC(.*)$!; # HMAC-SHA-1
361 3 50       13 $digits = "-$digits" unless $name =~ m!MD$!; # MD2, MD4, MD5
362            
363 3         5 $algorithm = "$name$digits";
364             }
365            
366             }
367            
368 4         12 return $algorithm;
369             }
370            
371             sub __get_pass_scheme {
372 4     4   10 local $1;
373 4 100       24 return unless $_[0] =~ m/{([^}]*)/;
374 3         11 return $1;
375             }
376            
377             sub __get_pass_hash {
378 8     8   17 local $1;
379 8 100       41 return unless $_[0] =~ m/}(.*)/;
380 7         25 return $1;
381             }
382            
383             sub __generate_hex_salt {
384            
385 4     4   35 my @keychars = (
386             "0", "1", "2", "3", "4", "5", "6", "7",
387             "8", "9", "a", "b", "c", "d", "e", "f"
388             );
389 4   50     12 my $length = shift || 8;
390            
391 4         7 my $salt = '';
392 4         4 my $max = scalar @keychars;
393 4         15 for my $i ( 0 .. $length - 1 ) {
394 88 100       110 my $skip = $i == 0 ? 1 : 0; # don't let the first be 0
395 88         245 $salt .= $keychars[ $skip + int( rand( $max - $skip ) ) ];
396             }
397            
398 4         24 return "HEX{$salt}";
399             }
400            
401             sub __extract_salt {
402            
403 4     4   8 my ( $hash, $salt_len ) = @_;
404            
405 4         24 my $binhash = &MIME::Base64::decode_base64($hash);
406 4   100     780 my $binsalt = substr( $binhash, length($binhash) - ( $salt_len || 4 ) );
407            
408 4         10 return $binsalt;
409             }
410            
411             =head1 SEE ALSO
412            
413             L, L
414            
415             =head1 AUTHOR
416            
417             Sascha Kiefer, L
418            
419             =head1 ACKNOWLEDGMENTS
420            
421             The author is particularly grateful to Andres Andreu for his article: Salted
422             hashes demystified - A Primer (L)
423            
424             =head1 COPYRIGHT AND LICENSE
425            
426             Copyright (C) 2010 Sascha Kiefer
427            
428             This library is free software; you can redistribute it and/or modify
429             it under the same terms as Perl itself.
430            
431             =cut
432            
433             1;