File Coverage

blib/lib/Authen/Passphrase/MD5Crypt.pm
Criterion Covered Total %
statement 67 69 97.1
branch 21 34 61.7
condition 4 12 33.3
subroutine 15 15 100.0
pod 6 6 100.0
total 113 136 83.0


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Authen::Passphrase::MD5Crypt - passphrases using the MD5-based Unix
4             crypt()
5              
6             =head1 SYNOPSIS
7              
8             use Authen::Passphrase::MD5Crypt;
9              
10             $ppr = Authen::Passphrase::MD5Crypt->new(
11             salt => "Vd3f8aG6",
12             hash_base64 => "GcsdF4YCXb0PM2UmXjIoI1");
13              
14             $ppr = Authen::Passphrase::MD5Crypt->new(
15             salt_random => 1,
16             passphrase => "passphrase");
17              
18             $ppr = Authen::Passphrase::MD5Crypt->from_crypt(
19             '$1$Vd3f8aG6$GcsdF4YCXb0PM2UmXjIoI1');
20              
21             $ppr = Authen::Passphrase::MD5Crypt->from_rfc2307(
22             '{CRYPT}$1$Vd3f8aG6$GcsdF4YCXb0PM2UmXjIoI1');
23              
24             $salt = $ppr->salt;
25             $hash_base64 = $ppr->hash_base64;
26              
27             if($ppr->match($passphrase)) { ...
28              
29             $passwd = $ppr->as_crypt;
30             $userPassword = $ppr->as_rfc2307;
31              
32             =head1 DESCRIPTION
33              
34             An object of this class encapsulates a passphrase hashed using
35             the MD5-based Unix crypt() hash function. This is a subclass of
36             L, and this document assumes that the reader is
37             familiar with the documentation for that class.
38              
39             The crypt() function in a modern Unix actually supports several
40             different passphrase schemes. This class is concerned only with one
41             particular scheme, an MD5-based algorithm designed by Poul-Henning Kamp
42             and originally implemented in FreeBSD. To handle the whole range of
43             passphrase schemes supported by the modern crypt(), see the
44             L constructor and the
45             L method in L.
46              
47             The MD5-based crypt() scheme uses the whole passphrase, a salt which
48             can in principle be an arbitrary byte string, and the MD5 message
49             digest algorithm. First the passphrase and salt are hashed together,
50             yielding an MD5 message digest. Then a new digest is constructed,
51             hashing together the passphrase, the salt, and the first digest, all in
52             a rather complex form. Then this digest is passed through a thousand
53             iterations of a function which rehashes it together with the passphrase
54             and salt in a manner that varies between rounds. The output of the last
55             of these rounds is the resulting passphrase hash.
56              
57             In the crypt() function the raw hash output is then represented in ASCII
58             as a 22-character string using a base 64 encoding. The base 64 digits
59             are "B<.>", "B", "B<0>" to "B<9>", "B" to "B", "B" to "B"
60             (in ASCII order). Because the base 64 encoding can represent 132 bits
61             in 22 digits, more than the 128 required, the last digit can only take
62             four of the base 64 digit values. An additional complication is that
63             the bytes of the raw algorithm output are permuted in a bizarre order
64             before being represented in base 64.
65              
66             There is no tradition of handling these passphrase hashes in raw
67             binary form. The textual encoding described above, including the final
68             permutation, is used universally, so this class does not support any
69             binary format.
70              
71             The complex algorithm was designed to be slow to compute, in order
72             to resist brute force attacks. However, the complexity is fixed,
73             and the operation of Moore's Law has rendered it far less expensive
74             than intended. If efficiency of a brute force attack is a concern,
75             see L.
76              
77             =cut
78              
79             package Authen::Passphrase::MD5Crypt;
80              
81 1     1   25753 { use 5.006; }
  1         4  
  1         48  
82 1     1   7 use warnings;
  1         2  
  1         48  
83 1     1   6 use strict;
  1         3  
  1         47  
84              
85 1     1   1123 use Authen::Passphrase 0.003;
  1         26  
  1         35  
86 1     1   12 use Carp qw(croak);
  1         1  
  1         70  
87 1     1   1772 use Crypt::PasswdMD5 1.0 qw(unix_md5_crypt);
  1         1362  
  1         79  
88 1     1   1115 use Data::Entropy::Algorithms 0.000 qw(rand_int);
  1         32477  
  1         286  
89              
90             our $VERSION = "0.008";
91              
92 1     1   12 use parent "Authen::Passphrase";
  1         2  
  1         5  
93              
94             =head1 CONSTRUCTORS
95              
96             =over
97              
98             =item Authen::Passphrase::MD5Crypt->new(ATTR => VALUE, ...)
99              
100             Generates a new passphrase recogniser object using the MD5-based crypt()
101             algorithm. The following attributes may be given:
102              
103             =over
104              
105             =item B
106              
107             The salt, as a raw string. It may be any byte string, but in crypt()
108             usage it is conventionally limited to zero to eight base 64 digits.
109              
110             =item B
111              
112             Causes salt to be generated randomly. The value given for this
113             attribute is ignored. The salt will be a string of eight base 64 digits.
114             The source of randomness may be controlled by the facility described
115             in L.
116              
117             =item B
118              
119             The hash, as a string of 22 base 64 digits. This is the final part of
120             what crypt() outputs.
121              
122             =item B
123              
124             A passphrase that will be accepted.
125              
126             =back
127              
128             The salt must be given, and either the hash or the passphrase.
129              
130             =cut
131              
132             sub new {
133 9     9 1 29 my $class = shift;
134 9         29 my $self = bless({}, $class);
135 9         16 my $passphrase;
136 9         25 while(@_) {
137 18         27 my $attr = shift;
138 18         30 my $value = shift;
139 18 100       69 if($attr eq "salt") {
    100          
    100          
    50          
140 8 50       27 croak "salt specified redundantly"
141             if exists $self->{salt};
142 8 50       68 $value =~ m#\A[\x00-\xff]*\z#
143             or croak "not a valid salt";
144 8         31 $self->{salt} = "$value";
145             } elsif($attr eq "salt_random") {
146 1 50       6 croak "salt specified redundantly"
147             if exists $self->{salt};
148 1         3 $self->{salt} = "";
149 1         6 for(my $i = 8; $i--; ) {
150 8         9154 $self->{salt} .= chr(rand_int(64));
151             }
152 1         40 $self->{salt} =~ tr#\x00-\x3f#./0-9A-Za-z#;
153             } elsif($attr eq "hash_base64") {
154 7 50 33     37 croak "hash specified redundantly"
155             if exists($self->{hash_base64}) ||
156             defined($passphrase);
157 7 50       26 $value =~ m#\A[./0-9A-Za-z]{21}[./01]\z#
158             or croak "\"$value\" is not a valid ".
159             "MD5-based crypt() hash";
160 7         28 $self->{hash_base64} = "$value";
161             } elsif($attr eq "passphrase") {
162 2 50 33     16 croak "passphrase specified redundantly"
163             if exists($self->{hash_base64}) ||
164             defined($passphrase);
165 2         7 $passphrase = $value;
166             } else {
167 0         0 croak "unrecognised attribute `$attr'";
168             }
169             }
170 9 50       24 croak "salt not specified" unless exists $self->{salt};
171 9 100       27 $self->{hash_base64} = $self->_hash_base64_of($passphrase)
172             if defined $passphrase;
173 9 50       32 croak "hash not specified" unless exists $self->{hash_base64};
174 9         34 return $self;
175             }
176              
177             =item Authen::Passphrase::MD5Crypt->from_crypt(PASSWD)
178              
179             Generates a new passphrase recogniser object using the MD5-based crypt()
180             algorithm, from a crypt string. The crypt string must consist of
181             "B<$1$>", the salt, "B<$>", then 22 base 64 digits giving the hash.
182             The salt may be up to 8 characters long, and cannot contain "B<$>"
183             or any character that cannot appear in a crypt string.
184              
185             =cut
186              
187             sub from_crypt {
188 2     2 1 5 my($class, $passwd) = @_;
189 2 50       12 if($passwd =~ /\A\$1\$/) {
190 2 50       12 $passwd =~ m:\A\$1\$([!-#%-9;-~]{0,8})\$([./0-9A-Za-z]{22})\z:
191             or croak "malformed \$1\$ data";
192 2         10 my($salt, $hash) = ($1, $2);
193 2         10 return $class->new(salt => $salt, hash_base64 => $hash);
194             }
195 0         0 return $class->SUPER::from_crypt($passwd);
196             }
197              
198             =item Authen::Passphrase::MD5Crypt->from_rfc2307(USERPASSWORD)
199              
200             Generates a new passphrase recogniser object using the MD5-based
201             crypt() algorithm, from an RFC 2307 string. The string must consist of
202             "B<{CRYPT}>" (case insensitive) followed by an acceptable crypt string.
203              
204             =back
205              
206             =head1 METHODS
207              
208             =over
209              
210             =item $ppr->salt
211              
212             Returns the salt, in raw form.
213              
214             =cut
215              
216             sub salt {
217 9     9 1 2650 my($self) = @_;
218 9         48 return $self->{salt};
219             }
220              
221             =item $ppr->hash_base64
222              
223             Returns the hash value, as a string of 22 base 64 digits.
224              
225             =cut
226              
227             sub hash_base64 {
228 9     9 1 14 my($self) = @_;
229 9         42 return $self->{hash_base64};
230             }
231              
232             =item $ppr->match(PASSPHRASE)
233              
234             =item $ppr->as_crypt
235              
236             =item $ppr->as_rfc2307
237              
238             These methods are part of the standard L interface.
239             Not every passphrase recogniser of this type can be represented as a
240             crypt string: the crypt format only allows the salt to be up to eight
241             bytes, and it cannot contain any NUL or "B<$>" characters.
242              
243             =cut
244              
245             sub _hash_base64_of {
246 28     28   52 my($self, $passphrase) = @_;
247 28 50 33     1129 die "can't use a crypt-incompatible salt yet ".
248             "(need generalised Crypt::MD5Passwd)"
249             if $self->{salt} =~ /[^\!-\#\%-9\;-\~]/ ||
250             length($self->{salt}) > 8;
251 28         152 my $hash = unix_md5_crypt($passphrase, $self->{salt});
252 28         480264 $hash =~ s/\A.*\$//;
253 28         339 return $hash;
254             }
255              
256             sub match {
257 26     26 1 42386 my($self, $passphrase) = @_;
258 26         117 return $self->_hash_base64_of($passphrase) eq $self->{hash_base64};
259             }
260              
261             sub as_crypt {
262 10     10 1 13 my($self) = @_;
263 10 50 33     67 croak "can't put this salt into a crypt string"
264             if $self->{salt} =~ /[^\!-\#\%-9\;-\~]/ ||
265             length($self->{salt}) > 8;
266 10         61 return "\$1\$".$self->{salt}."\$".$self->{hash_base64};
267             }
268              
269             =back
270              
271             =head1 SEE ALSO
272              
273             L,
274             L
275              
276             =head1 AUTHOR
277              
278             Andrew Main (Zefram)
279              
280             =head1 COPYRIGHT
281              
282             Copyright (C) 2006, 2007, 2009, 2010, 2012
283             Andrew Main (Zefram)
284              
285             =head1 LICENSE
286              
287             This module is free software; you can redistribute it and/or modify it
288             under the same terms as Perl itself.
289              
290             =cut
291              
292             1;