File Coverage

blib/lib/Authen/Passphrase/SaltedDigest.pm
Criterion Covered Total %
statement 114 119 95.8
branch 49 82 59.7
condition 5 15 33.3
subroutine 21 21 100.0
pod 9 9 100.0
total 198 246 80.4


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Authen::Passphrase::SaltedDigest - passphrases using the generic salted
4             digest algorithm
5              
6             =head1 SYNOPSIS
7              
8             use Authen::Passphrase::SaltedDigest;
9              
10             $ppr = Authen::Passphrase::SaltedDigest->new(
11             algorithm => "SHA-1",
12             salt_hex => "a9f524b1e819e96d8cc7".
13             "a04d5471e8b10c84e596",
14             hash_hex => "8270d9d1a345d3806ab2".
15             "3b0385702e10f1acc943");
16              
17             $ppr = Authen::Passphrase::SaltedDigest->new(
18             algorithm => "SHA-1", salt_random => 20,
19             passphrase => "passphrase");
20              
21             $ppr = Authen::Passphrase::SaltedDigest->from_rfc2307(
22             "{SSHA}gnDZ0aNF04BqsjsDhXAuEPGsy".
23             "UOp9SSx6BnpbYzHoE1UceixDITllg==");
24              
25             $algorithm = $ppr->algorithm;
26             $salt = $ppr->salt;
27             $salt_hex = $ppr->salt_hex;
28             $hash = $ppr->hash;
29             $hash_hex = $ppr->hash_hex;
30              
31             if($ppr->match($passphrase)) { ...
32              
33             $userPassword = $ppr->as_rfc2307;
34              
35             =head1 DESCRIPTION
36              
37             An object of this class encapsulates a passphrase hashed using
38             a generic digest-algorithm-based scheme. This is a subclass of
39             L, and this document assumes that the reader is
40             familiar with the documentation for that class.
41              
42             The salt is an arbitrary string of bytes. It is appended to passphrase,
43             and the combined string is passed through a specified message digest
44             algorithm. The output of the message digest algorithm is the passphrase
45             hash.
46              
47             The strength depends entirely on the choice of digest algorithm, so
48             choose according to the level of security required. SHA-1 is suitable for
49             most applications, but recent work has revealed weaknesses in the basic
50             structure of MD5, SHA-1, SHA-256, and all similar digest algorithms.
51             A new generation of digest algorithms emerged in 2008, centred around
52             NIST's competition to design SHA-3. Once these algorithms have been
53             subjected to sufficient cryptanalysis, the survivors will be preferred
54             over SHA-1 and its generation.
55              
56             Digest algorithms are generally designed to be as efficient to compute
57             as possible for their level of cryptographic strength. An unbroken
58             digest algorithm makes brute force the most efficient way to attack it,
59             but makes no effort to resist a brute force attack. This is a concern
60             in some passphrase-using applications.
61              
62             The use of this kind of passphrase scheme is generally recommended for
63             new systems. Choice of digest algorithm is important: SHA-1 is suitable
64             for most applications. If efficiency of brute force attack is a concern,
65             see L for an algorithm designed to
66             be expensive to compute.
67              
68             =cut
69              
70             package Authen::Passphrase::SaltedDigest;
71              
72 3     3   218370 { use 5.006; }
  3         14  
73 3     3   29 use warnings;
  3         35  
  3         194  
74 3     3   16 use strict;
  3         6  
  3         83  
75              
76 3     3   1040 use Authen::Passphrase 0.003;
  3         124  
  3         130  
77 3     3   21 use Carp qw(croak);
  3         7  
  3         238  
78 3     3   906 use Crypt::SysRandom 'random_bytes';
  3         7238  
  3         253  
79 3     3   1868 use Digest 1.00;
  3         2522  
  3         137  
80 3     3   22 use MIME::Base64 2.21 qw(encode_base64 decode_base64);
  3         48  
  3         205  
81 3     3   20 use Module::Runtime 0.011 qw(is_valid_module_name use_module);
  3         40  
  3         20  
82 3     3   2101 use Params::Classify 0.000 qw(is_string is_blessed);
  3         9456  
  3         300  
83              
84             our $VERSION = "0.009";
85              
86 3     3   21 use parent "Authen::Passphrase";
  3         5  
  3         15  
87              
88             =head1 CONSTRUCTORS
89              
90             =over
91              
92             =item Authen::Passphrase::SaltedDigest->new(ATTR => VALUE, ...)
93              
94             Generates a new passphrase recogniser object using the generic salted
95             digest algorithm. The following attributes may be given:
96              
97             =over
98              
99             =item B
100              
101             Specifies the algorithm to use. If it is a reference to a blessed object,
102             it must be possible to call the L method on that object to generate
103             a digest context object.
104              
105             If it is a string containing the subsequence "::" then it specifies
106             a module to use. A plain package name in bareword syntax, optionally
107             preceded by "::" (so that top-level packages can be recognised as such),
108             is taken as a class name, on which the L method will be called to
109             generate a digest context object. The package name may optionally be
110             followed by "-" to cause automatic loading of the module, and the "-"
111             (if present) may optionally be followed by a version number that will
112             be checked against. For example, "Digest::MD5-1.99_53" would load the
113             L module and check that it is at least version 1.99_53
114             (which is the first version that can be used by this module).
115              
116             A string not containing "::" and which is understood by
117             L<< Digest->new|Digest/"OO INTERFACE" >> will be passed to that function
118             to generate a digest context object.
119              
120             Any other type of algorithm specifier has undefined behaviour.
121              
122             The digest context objects must support at least the standard C
123             and C methods.
124              
125             =item B
126              
127             The salt, as a raw string of bytes. Defaults to the empty string,
128             yielding an unsalted scheme.
129              
130             =item B
131              
132             The salt, as a string of hexadecimal digits. Defaults to the empty
133             string, yielding an unsalted scheme.
134              
135             =item B
136              
137             Causes salt to be generated randomly. The value given for this
138             attribute must be a non-negative integer, giving the number of bytes
139             of salt to generate. (The same length as the hash is recommended.)
140              
141             =item B
142              
143             The hash, as a string of bytes.
144              
145             =item B
146              
147             The hash, as a string of hexadecimal digits.
148              
149             =item B
150              
151             A passphrase that will be accepted.
152              
153             =back
154              
155             The digest algorithm must be given, and either the hash or the passphrase.
156              
157             =cut
158              
159             sub new {
160 14     14 1 33 my $class = shift;
161 14         35 my $self = bless({}, $class);
162 14         24 my $passphrase;
163 14         43 while(@_) {
164 42         62 my $attr = shift;
165 42         66 my $value = shift;
166 42 100       139 if($attr eq "algorithm") {
    100          
    100          
    100          
    100          
    100          
    50          
167             croak "algorithm specified redundantly"
168 14 50       46 if exists $self->{algorithm};
169 14         49 $self->{algorithm} = $value;
170             } elsif($attr eq "salt") {
171             croak "salt specified redundantly"
172 7 50       21 if exists $self->{salt};
173 7 50       33 $value =~ m#\A[\x00-\xff]*\z#
174             or croak "\"$value\" is not a valid salt";
175 7         26 $self->{salt} = "$value";
176             } elsif($attr eq "salt_hex") {
177             croak "salt specified redundantly"
178 6 50       17 if exists $self->{salt};
179 6 50       44 $value =~ m#\A(?:[0-9A-Fa-f]{2})+\z#
180             or croak "\"$value\" is not a valid salt";
181 6         25 $self->{salt} = pack("H*", $value);
182             } elsif($attr eq "salt_random") {
183             croak "salt specified redundantly"
184 1 50       3 if exists $self->{salt};
185 1 50 33     7 croak "\"$value\" is not a valid salt length"
186             unless $value == int($value) && $value >= 0;
187 1         10 $self->{salt} = random_bytes($value);
188             } elsif($attr eq "hash") {
189             croak "hash specified redundantly"
190 6 50 33     46 if exists($self->{hash}) ||
191             defined($passphrase);
192 6 50       27 $value =~ m#\A[\x00-\xff]*\z#
193             or croak "\"$value\" is not a valid hash";
194 6         34 $self->{hash} = "$value";
195             } elsif($attr eq "hash_hex") {
196             croak "hash specified redundantly"
197 6 50 33     37 if exists($self->{hash}) ||
198             defined($passphrase);
199 6 50       36 $value =~ m#\A(?:[0-9A-Fa-f]{2})+\z#
200             or croak "\"$value\" is not a valid hash";
201 6         27 $self->{hash} = pack("H*", $value);
202             } elsif($attr eq "passphrase") {
203             croak "passphrase specified redundantly"
204 2 50 33     8 if exists($self->{hash}) ||
205             defined($passphrase);
206 2         4 $passphrase = $value;
207             } else {
208 0         0 croak "unrecognised attribute `$attr'";
209             }
210             }
211 14 50       35 croak "algorithm not specified" unless exists $self->{algorithm};
212 14 50       32 $self->{salt} = "" unless exists $self->{salt};
213 14 100       39 if(defined $passphrase) {
    50          
214 2         5 $self->{hash} = $self->_hash_of($passphrase);
215             } elsif(exists $self->{hash}) {
216             croak "not a valid ".$self->{algorithm}." hash"
217 12 50       39 unless length($self->{hash}) ==
218             length($self->_hash_of(""));
219             } else {
220 0         0 croak "hash not specified";
221             }
222 14         51 return $self;
223             }
224              
225             =item Authen::Passphrase::SaltedDigest->from_rfc2307(USERPASSWORD)
226              
227             Generates a salted-digest passphrase recogniser from the supplied
228             RFC2307 encoding. The scheme identifier gives the digest algorithm and
229             controls whether salt is permitted. It is followed by a base 64 string,
230             using standard MIME base 64, which encodes the concatenation of the hash
231             and salt.
232              
233             The scheme identifiers accepted are "B<{MD4}>" (unsalted MD4), "B<{MD5}>"
234             (unsalted MD5), "B<{RMD160}>" (unsalted RIPEMD-160), "B<{SHA}>" (unsalted
235             SHA-1), "B<{SMD5}>" (salted MD5), and "B<{SSHA}>" (salted SHA-1).
236             All scheme identifiers are recognised case-insensitively.
237              
238             =cut
239              
240             my %rfc2307_scheme_meaning = (
241             "MD4" => ["MD4", 16, 0],
242             "MD5" => ["MD5", 16, 0],
243             "RMD160" => ["Crypt::RIPEMD160-", 20, 0],
244             "SHA" => ["SHA-1", 20, 0],
245             "SMD5" => ["MD5", 16, 1],
246             "SSHA" => ["SHA-1", 20, 1],
247             );
248              
249             sub from_rfc2307 {
250 2     2 1 404219 my($class, $userpassword) = @_;
251 2 50       16 return $class->SUPER::from_rfc2307($userpassword)
252             unless $userpassword =~ /\A\{([-0-9A-Za-z]+)\}/;
253 2         10 my $scheme = uc($1);
254 2         6 my $meaning = $rfc2307_scheme_meaning{$scheme};
255 2 50       11 return $class->SUPER::from_rfc2307($userpassword)
256             unless defined $meaning;
257 2 50       18 croak "malformed {$scheme} data"
258             unless $userpassword =~
259             m#\A\{.*?\}
260             ((?>(?:[A-Za-z0-9+/]{4})*)
261             (?:|[A-Za-z0-9+/]{2}[AEIMQUYcgkosw048]=|
262             [A-Za-z0-9+/][AQgw]==))\z#x;
263 2         6 my $b64 = $1;
264 2         13 my $hash_and_salt = decode_base64($b64);
265 2         7 my($algorithm, $hash_len, $salt_allowed) = @$meaning;
266 2 50       8 croak "insufficient hash data for {$scheme}"
267             if length($hash_and_salt) < $hash_len;
268 2 50 33     7 croak "too much hash data for {$scheme}"
269             if !$salt_allowed && length($hash_and_salt) > $hash_len;
270 2         56 return $class->new(algorithm => $algorithm,
271             salt => substr($hash_and_salt, $hash_len),
272             hash => substr($hash_and_salt, 0, $hash_len));
273             }
274              
275             =back
276              
277             =head1 METHODS
278              
279             =over
280              
281             =item $ppr->algorithm
282              
283             Returns the digest algorithm, in the same form as supplied to the
284             constructor.
285              
286             =cut
287              
288             sub algorithm {
289 2     2 1 1038 my($self) = @_;
290 2         13 return $self->{algorithm};
291             }
292              
293             =item $ppr->salt
294              
295             Returns the salt, in raw form.
296              
297             =cut
298              
299             sub salt {
300 12     12 1 476 my($self) = @_;
301 12         40 return $self->{salt};
302             }
303              
304             =item $ppr->salt_hex
305              
306             Returns the salt, as a string of hexadecimal digits.
307              
308             =cut
309              
310             sub salt_hex {
311 13     13 1 4602 my($self) = @_;
312 13         75 return unpack("H*", $self->{salt});
313             }
314              
315             =item $ppr->hash
316              
317             Returns the hash value, in raw form.
318              
319             =cut
320              
321             sub hash {
322 12     12 1 31 my($self) = @_;
323 12         66 return $self->{hash};
324             }
325              
326             =item $ppr->hash_hex
327              
328             Returns the hash value, as a string of hexadecimal digits.
329              
330             =cut
331              
332             sub hash_hex {
333 13     13 1 34 my($self) = @_;
334 13         71 return unpack("H*", $self->{hash});
335             }
336              
337             =item $ppr->match(PASSPHRASE)
338              
339             =item $ppr->as_rfc2307
340              
341             These methods are part of the standard L interface.
342             Only passphrase recognisers using certain well-known digest algorithms
343             can be represented in RFC 2307 form.
344              
345             =cut
346              
347             sub _hash_of {
348 65     65   132 my($self, $passphrase) = @_;
349 65         122 my $alg = $self->{algorithm};
350 65         102 my $ctx;
351 65 50       159 if(is_string($alg)) {
    0          
352 65 100       177 if($alg =~ /::/) {
353 1 50       7 $alg =~ /\A(?:::)?([0-9a-zA-Z_:]+)
354             (-([0-9][0-9_]*(?:\._*[0-9][0-9_]*)?)?)?\z/x
355             or croak "module spec `$alg' not understood";
356 1         4 my($pkgname, $load_p, $modver) = ($1, $2, $3);
357 1 50       4 croak "bad package name `$pkgname'"
358             unless is_valid_module_name($pkgname);
359 1 50       62 if($load_p) {
360 1 50       2 if(defined $modver) {
361 1         3 $modver =~ tr/_//d;
362 1         4 use_module($pkgname, $modver);
363             } else {
364 0         0 use_module($pkgname);
365             }
366             }
367 1         59 $ctx = $pkgname->new;
368             } else {
369 64         247 $ctx = Digest->new($alg);
370             }
371             } elsif(is_blessed($alg)) {
372 0         0 $ctx = $alg->new;
373             } else {
374 0         0 croak "algorithm specifier `$alg' is of an unrecognised type";
375             }
376 65         7167 $ctx->add($passphrase);
377 65         210 $ctx->add($self->{salt});
378 65         569 return $ctx->digest;
379             }
380              
381             sub match {
382 51     51 1 16159 my($self, $passphrase) = @_;
383 51         126 return $self->_hash_of($passphrase) eq $self->{hash};
384             }
385              
386             my %rfc2307_scheme_for_digest_name = (
387             "MD4" => "MD4",
388             "MD5" => "MD5",
389             "SHA-1" => "SHA",
390             "SHA1" => "SHA",
391             );
392              
393             my %rfc2307_scheme_for_package_name = (
394             "Crypt::RIPEMD160" => "RMD160",
395             "Digest::MD4" => "MD4",
396             "Digest::MD5" => "MD5",
397             "Digest::MD5::Perl" => "MD5",
398             "Digest::Perl::MD4" => "MD4",
399             "Digest::SHA" => "SHA",
400             "Digest::SHA::PurePerl" => "SHA",
401             "Digest::SHA1" => "SHA",
402             "MD5" => "MD5",
403             "RIPEMD160" => "RMD160",
404             );
405              
406             sub as_rfc2307 {
407 12     12 1 28 my($self) = @_;
408 12         26 my $alg = $self->{algorithm};
409 12         17 my $scheme;
410 12 50       52 if(is_string($alg)) {
411 12 100       58 if($alg =~ /::/) {
412 1 50       8 $scheme = $rfc2307_scheme_for_package_name{$1}
413             if $alg =~ /\A(?:::)?
414             ([0-9a-zA-Z_:]+)(?:-[0-9._]*)?\z/x;
415             } else {
416 11         24 $scheme = $rfc2307_scheme_for_digest_name{$alg};
417             }
418             }
419 12 50       47 croak "don't know RFC 2307 scheme identifier for digest algorithm $alg"
420             unless defined $scheme;
421             return "{".($self->{salt} eq "" ? "" : "S").$scheme."}".
422 12 50       145 encode_base64($self->{hash}.$self->{salt}, "");
423             }
424              
425             =back
426              
427             =head1 SEE ALSO
428              
429             L,
430             L
431              
432             =head1 AUTHOR
433              
434             Andrew Main (Zefram)
435              
436             =head1 COPYRIGHT
437              
438             Copyright (C) 2006, 2007, 2009, 2010, 2012
439             Andrew Main (Zefram)
440              
441             =head1 LICENSE
442              
443             This module is free software; you can redistribute it and/or modify it
444             under the same terms as Perl itself.
445              
446             =cut
447              
448             1;