File Coverage

blib/lib/Authen/Passphrase/PHPass.pm
Criterion Covered Total %
statement 105 107 98.1
branch 33 52 63.4
condition 11 21 52.3
subroutine 20 20 100.0
pod 9 9 100.0
total 178 209 85.1


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Authen::Passphrase::PHPass - passphrases using the phpass algorithm
4              
5             =head1 SYNOPSIS
6              
7             use Authen::Passphrase::PHPass;
8              
9             $ppr = Authen::Passphrase::PHPass->new(
10             cost => 10, salt => "NaClNaCl",
11             hash_base64 => "ObRxTm/.EiiYN02xUeAQs/");
12              
13             $ppr = Authen::Passphrase::PHPass->new(
14             cost => 10, salt_random => 1,
15             passphrase => "passphrase");
16              
17             $ppr = Authen::Passphrase::PHPass->from_crypt(
18             '$P$8NaClNaClObRxTm/.EiiYN02xUeAQs/');
19              
20             $ppr = Authen::Passphrase::PHPass->from_rfc2307(
21             '{CRYPT}$P$8NaClNaClObRxTm/.EiiYN02xUeAQs/');
22              
23             $cost = $ppr->cost;
24             $cost_base64 = $ppr->cost_base64;
25             $cost = $ppr->nrounds_log2;
26             $cost_base64 = $ppr->nrounds_log2_base64;
27             $salt = $ppr->salt;
28             $hash = $ppr->hash;
29             $hash_base64 = $ppr->hash_base64;
30              
31             if($ppr->match($passphrase)) { ...
32              
33             $passwd = $ppr->as_crypt;
34             $userPassword = $ppr->as_rfc2307;
35              
36             =head1 DESCRIPTION
37              
38             An object of this class encapsulates a passphrase hashed using
39             the phpass algorithm invented by Solar Designer and described
40             at L. This is a subclass of
41             L, and this document assumes that the reader is
42             familiar with the documentation for that class.
43              
44             The phpass algorithm is based on the MD5 message digest algorithm.
45             There is an eight-byte salt, which is conventionally restricted to
46             consist of base 64 digits. There is also a cost parameter that controls
47             the expense of hashing. First the salt and passphrase are concatenated
48             and hashed by MD5. Then, 2^cost times, the hash from the previous stage
49             is concatenated with the passphrase and hashed by MD5. The passphrase
50             hash is the output from the final iteration.
51              
52             The passphrase hash is represented in ASCII using the crypt format with
53             prefix "B<$P$>". The first character after the format prefix is a base 64
54             digit giving the cost parameter. The next eight characters are the salt.
55             The salt is followed by 22 base 64 digits giving the hash. The base 64
56             digits are "B<.>", "B", "B<0>" to "B<9>", "B" to "B", "B"
57             to "B" (in ASCII order).
58              
59             =cut
60              
61             package Authen::Passphrase::PHPass;
62              
63 2     2   112021 { use 5.006; }
  2         10  
64 2     2   16 use warnings;
  2         4  
  2         148  
65 2     2   11 use strict;
  2         4  
  2         79  
66              
67 2     2   521 use Authen::Passphrase 0.003;
  2         42  
  2         128  
68 2     2   15 use Carp qw(croak);
  2         4  
  2         157  
69 2     2   572 use Crypt::SysRandom 'random_bytes';
  2         5010  
  2         215  
70 2     2   16 use Digest::MD5 1.99_53 ();
  2         41  
  2         121  
71              
72             our $VERSION = "0.009";
73              
74 2     2   583 use parent "Authen::Passphrase";
  2         471  
  2         18  
75              
76             my $base64_digits = "./0123456789ABCDEFGHIJKLMNOPQRST".
77             "UVWXYZabcdefghijklmnopqrstuvwxyz";
78              
79             sub _en_base64($) {
80 21     21   55 my($bytes) = @_;
81 21         56 my $nbytes = length($bytes);
82 21         76 my $npadbytes = 2 - ($nbytes + 2) % 3;
83 21         84 $bytes .= "\0" x $npadbytes;
84 21         49 my $digits = "";
85 21         88 for(my $i = 0; $i < $nbytes; $i += 3) {
86 122         395 my $v = ord(substr($bytes, $i, 1)) |
87             (ord(substr($bytes, $i+1, 1)) << 8) |
88             (ord(substr($bytes, $i+2, 1)) << 16);
89 122         491 $digits .= substr($base64_digits, $v & 0x3f, 1) .
90             substr($base64_digits, ($v >> 6) & 0x3f, 1) .
91             substr($base64_digits, ($v >> 12) & 0x3f, 1) .
92             substr($base64_digits, ($v >> 18) & 0x3f, 1);
93             }
94 21         58 substr $digits, -$npadbytes, $npadbytes, "";
95 21         204 return $digits;
96             }
97              
98             sub _de_base64($) {
99 8     8   24 my($digits) = @_;
100 8         26 my $ndigits = length($digits);
101 8         28 my $npadbytes = 3 - ($ndigits + 3) % 4;
102 8         29 $digits .= "." x $npadbytes;
103 8         24 my $bytes = "";
104 8         35 for(my $i = 0; $i < $ndigits; $i += 4) {
105 48         194 my $v = index($base64_digits, substr($digits,$i,1)) |
106             (index($base64_digits, substr($digits,$i+1,1)) << 6) |
107             (index($base64_digits, substr($digits,$i+2,1)) << 12) |
108             (index($base64_digits, substr($digits,$i+3,1)) << 18);
109 48         173 $bytes .= chr($v & 0xff) .
110             chr(($v >> 8) & 0xff) .
111             chr(($v >> 16) & 0xff);
112             }
113 8         23 substr $bytes, -$npadbytes, $npadbytes, "";
114 8         50 return $bytes;
115             }
116              
117             =head1 CONSTRUCTORS
118              
119             =over
120              
121             =item Authen::Passphrase::PHPass->new(ATTR => VALUE, ...)
122              
123             Generates a new passphrase recogniser object using the phpass algorithm.
124             The following attributes may be given:
125              
126             =over
127              
128             =item B
129              
130             Base-two logarithm of the number of hashing rounds to perform.
131              
132             =item B
133              
134             Base-two logarithm of the number of hashing rounds to perform, expressed
135             as a single base 64 digit.
136              
137             =item B
138              
139             Synonym for B.
140              
141             =item B
142              
143             Synonym for B.
144              
145             =item B
146              
147             The salt, as an eight-byte string.
148              
149             =item B
150              
151             Causes salt to be generated randomly. The value given for this
152             attribute is ignored. The salt will be a string of eight base 64 digits.
153              
154             =item B
155              
156             The hash, as a 16-byte string.
157              
158             =item B
159              
160             The hash, as a string of 22 base 64 digits.
161              
162             =item B
163              
164             A passphrase that will be accepted.
165              
166             =back
167              
168             The cost and salt must be given, and either the hash or the passphrase.
169              
170             =cut
171              
172             sub new {
173 11     11 1 232383 my $class = shift;
174 11         37 my $self = bless({}, $class);
175 11         32 my $passphrase;
176 11         55 while(@_) {
177 33         70 my $attr = shift;
178 33         84 my $value = shift;
179 33 100 100     484 if($attr eq "cost" || $attr eq "nrounds_log2") {
    100 100        
    100          
    100          
    100          
    100          
    50          
180             croak "cost specified redundantly"
181 2 50       14 if exists $self->{cost};
182 2 50 33     24 croak "\"$value\" is not a valid cost parameter"
      33        
183             unless $value == int($value) && $value >= 0 &&
184             $value <= 30;
185 2         10 $self->{cost} = 0+$value;
186             } elsif($attr eq "cost_base64" ||
187             $attr eq "nrounds_log2_base64") {
188             croak "cost specified redundantly"
189 9 50       52 if exists $self->{cost};
190 9 50       67 croak "\"$value\" is not a valid cost parameter"
191             unless $value =~ m#\A[./0-9A-S]\z#;
192 9         59 $self->{cost} = index($base64_digits, $value);
193             } elsif($attr eq "salt") {
194             croak "salt specified redundantly"
195 10 50       42 if exists $self->{salt};
196 10 50       61 $value =~ m#\A[\x00-\xff]{8}\z#
197             or croak "\"$value\" is not a valid salt";
198 10         50 $self->{salt} = "$value";
199             } elsif($attr eq "salt_random") {
200             croak "salt specified redundantly"
201 1 50       6 if exists $self->{salt};
202 1         15 $self->{salt} = _en_base64(random_bytes(6));
203             } elsif($attr eq "hash") {
204             croak "hash specified redundantly"
205 1 50 33     6 if exists($self->{hash}) ||
206             defined($passphrase);
207 1 50       6 $value =~ m#\A[\x00-\xff]{16}\z#
208             or croak "not a valid raw hash";
209 1         5 $self->{hash} = "$value";
210             } elsif($attr eq "hash_base64") {
211             croak "hash specified redundantly"
212 8 50 33     52 if exists($self->{hash}) ||
213             defined($passphrase);
214 8 50       43 $value =~ m#\A[./0-9A-Za-z]{21}[./01]\z#
215             or croak "\"$value\" is not a valid hash";
216 8         35 $self->{hash} = _de_base64($value);
217             } elsif($attr eq "passphrase") {
218             croak "passphrase specified redundantly"
219 2 50 33     16 if exists($self->{hash}) ||
220             defined($passphrase);
221 2         7 $passphrase = $value;
222             } else {
223 0         0 croak "unrecognised attribute `$attr'";
224             }
225             }
226 11 50       45 croak "cost not specified" unless exists $self->{cost};
227 11 50       41 croak "salt not specified" unless exists $self->{salt};
228 11 100       42 $self->{hash} = $self->_hash_of($passphrase) if defined $passphrase;
229 11 50       42 croak "hash not specified" unless exists $self->{hash};
230 11         80 return $self;
231             }
232              
233             =item Authen::Passphrase::PHPass->from_crypt(PASSWD)
234              
235             Generates a new phpass passphrase recogniser object from a crypt string.
236             The crypt string must consist of "B<$P$>", one base 64 character encoding
237             the cost, the salt, then 22 base 64 digits giving the hash. The salt
238             must be exactly 8 characters long, and cannot contain any character that
239             cannot appear in a crypt string.
240              
241             =cut
242              
243             sub from_crypt {
244 2     2 1 9 my($class, $passwd) = @_;
245 2 50       14 if($passwd =~ /\A\$P\$/) {
246 2 50       15 $passwd =~ m#\A\$P\$([./0-9A-Za-z])([!-9;-~]{8})
247             ([./0-9A-Za-z]{22})\z#x
248             or croak "malformed \$P\$ data";
249 2         13 my($cost, $salt, $hash) = ($1, $2, $3);
250 2         15 return $class->new(cost_base64 => $cost, salt => $salt,
251             hash_base64 => $hash);
252             }
253 0         0 return $class->SUPER::from_crypt($passwd);
254             }
255              
256             =item Authen::Passphrase::PHPass->from_rfc2307(USERPASSWORD)
257              
258             Generates a new phpass passphrase recogniser object from an RFC 2307
259             string. The string must consist of "B<{CRYPT}>" (case insensitive)
260             followed by an acceptable crypt string.
261              
262             =back
263              
264             =head1 METHODS
265              
266             =over
267              
268             =item $ppr->cost
269              
270             Returns the base-two logarithm of the number of hashing rounds that will
271             be performed.
272              
273             =cut
274              
275             sub cost {
276 10     10 1 3242 my($self) = @_;
277 10         68 return $self->{cost};
278             }
279              
280             =item $ppr->cost_base64
281              
282             Returns the base-two logarithm of the number of hashing rounds that will
283             be performed, expressed as a single base 64 digit.
284              
285             =cut
286              
287             sub cost_base64 {
288 23     23 1 4103 my($self) = @_;
289 23         157 return substr($base64_digits, $self->{cost}, 1);
290             }
291              
292             =item $ppr->nrounds_log2
293              
294             Synonym for L.
295              
296             =cut
297              
298             *nrounds_log2 = \&cost;
299              
300             =item $ppr->nrounds_log2_base64
301              
302             Synonym for L.
303              
304             =cut
305              
306             *nrounds_log2_base64 = \&cost_base64;
307              
308             =item $ppr->salt
309              
310             Returns the salt, as a string of eight bytes.
311              
312             =cut
313              
314             sub salt {
315 11     11 1 40 my($self) = @_;
316 11         73 return $self->{salt};
317             }
318              
319             =item $ppr->hash
320              
321             Returns the hash value, as a string of 16 bytes.
322              
323             =cut
324              
325             sub hash {
326 3     3 1 13 my($self) = @_;
327 3         21 return $self->{hash};
328             }
329              
330             =item $ppr->hash_base64
331              
332             Returns the hash value, as a string of 22 base 64 digits.
333              
334             =cut
335              
336             sub hash_base64 {
337 20     20 1 81 my($self) = @_;
338 20         93 return _en_base64($self->{hash});
339             }
340              
341             =item $ppr->match(PASSPHRASE)
342              
343             =item $ppr->as_crypt
344              
345             =item $ppr->as_rfc2307
346              
347             These methods are part of the standard L interface.
348              
349             =cut
350              
351             sub _hash_of {
352 28     28   71 my($self, $passphrase) = @_;
353 28         224 my $ctx = Digest::MD5->new;
354 28         143 $ctx->add($self->{salt});
355 28         111 $ctx->add($passphrase);
356 28         91 my $hash = $ctx->digest;
357 28         124 for(my $i = 1 << $self->{cost}; $i--; ) {
358 20128         52485 $ctx = Digest::MD5->new;
359 20128         42693 $ctx->add($hash);
360 20128         57575 $ctx->add($passphrase);
361 20128         54666 $hash = $ctx->digest;
362             }
363 28         338 return $hash;
364             }
365              
366             sub match {
367 26     26 1 12660 my($self, $passphrase) = @_;
368 26         125 return $self->_hash_of($passphrase) eq $self->{hash};
369             }
370              
371             sub as_crypt {
372 10     10 1 27 my($self) = @_;
373             croak "can't put this salt into a crypt string"
374 10 50       69 if $self->{salt} =~ /[^!-9;-~]/;
375 10         34 return "\$P\$".$self->cost_base64.$self->{salt}.$self->hash_base64;
376             }
377              
378             =back
379              
380             =head1 SEE ALSO
381              
382             L,
383             L
384              
385             =head1 AUTHOR
386              
387             Andrew Main (Zefram)
388              
389             =head1 COPYRIGHT
390              
391             Copyright (C) 2006, 2007, 2009, 2010, 2012
392             Andrew Main (Zefram)
393              
394             =head1 LICENSE
395              
396             This module is free software; you can redistribute it and/or modify it
397             under the same terms as Perl itself.
398              
399             =cut
400              
401             1;