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 16 16 100.0
pod 6 6 100.0
total 114 137 83.2


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 2     2   89162 { use 5.006; }
  2         12  
82 2     2   14 use warnings;
  2         5  
  2         149  
83 2     2   23 use strict;
  2         3  
  2         68  
84              
85 2     2   493 use Authen::Passphrase 0.003;
  2         38  
  2         81  
86 2     2   11 use Carp qw(croak);
  2         3  
  2         149  
87 2     2   1047 use Crypt::PasswdMD5 1.0 qw(unix_md5_crypt);
  2         18313  
  2         153  
88 2     2   329 use Crypt::SysRandom 'random_bytes';
  2         2915  
  2         106  
89 2     2   17 use MIME::Base64 'encode_base64';
  2         4  
  2         107  
90              
91             our $VERSION = "0.009";
92              
93 2     2   8 use parent "Authen::Passphrase";
  2         4  
  2         14  
94              
95             =head1 CONSTRUCTORS
96              
97             =over
98              
99             =item Authen::Passphrase::MD5Crypt->new(ATTR => VALUE, ...)
100              
101             Generates a new passphrase recogniser object using the MD5-based crypt()
102             algorithm. The following attributes may be given:
103              
104             =over
105              
106             =item B
107              
108             The salt, as a raw string. It may be any byte string, but in crypt()
109             usage it is conventionally limited to zero to eight base 64 digits.
110              
111             =item B
112              
113             Causes salt to be generated randomly. The value given for this
114             attribute is ignored. The salt will be a string of eight base 64 digits.
115              
116             =item B
117              
118             The hash, as a string of 22 base 64 digits. This is the final part of
119             what crypt() outputs.
120              
121             =item B
122              
123             A passphrase that will be accepted.
124              
125             =back
126              
127             The salt must be given, and either the hash or the passphrase.
128              
129             =cut
130              
131             sub new {
132 9     9 1 155191 my $class = shift;
133 9         30 my $self = bless({}, $class);
134 9         19 my $passphrase;
135 9         35 while(@_) {
136 18         36 my $attr = shift;
137 18         37 my $value = shift;
138 18 100       81 if($attr eq "salt") {
    100          
    100          
    50          
139             croak "salt specified redundantly"
140 8 50       31 if exists $self->{salt};
141 8 50       50 $value =~ m#\A[\x00-\xff]*\z#
142             or croak "not a valid salt";
143 8         78 $self->{salt} = "$value";
144             } elsif($attr eq "salt_random") {
145             croak "salt specified redundantly"
146 1 50       6 if exists $self->{salt};
147 1         18 $self->{salt} = encode_base64(random_bytes(6), '');
148 1         6 $self->{salt} =~ tr{A-Za-z0-9+/=}{./0-9A-Za-z}d;
149             } elsif($attr eq "hash_base64") {
150             croak "hash specified redundantly"
151 7 50 33     44 if exists($self->{hash_base64}) ||
152             defined($passphrase);
153 7 50       31 $value =~ m#\A[./0-9A-Za-z]{21}[./01]\z#
154             or croak "\"$value\" is not a valid ".
155             "MD5-based crypt() hash";
156 7         27 $self->{hash_base64} = "$value";
157             } elsif($attr eq "passphrase") {
158             croak "passphrase specified redundantly"
159 2 50 33     16 if exists($self->{hash_base64}) ||
160             defined($passphrase);
161 2         9 $passphrase = $value;
162             } else {
163 0         0 croak "unrecognised attribute `$attr'";
164             }
165             }
166 9 50       27 croak "salt not specified" unless exists $self->{salt};
167 9 100       35 $self->{hash_base64} = $self->_hash_base64_of($passphrase)
168             if defined $passphrase;
169 9 50       32 croak "hash not specified" unless exists $self->{hash_base64};
170 9         58 return $self;
171             }
172              
173             =item Authen::Passphrase::MD5Crypt->from_crypt(PASSWD)
174              
175             Generates a new passphrase recogniser object using the MD5-based crypt()
176             algorithm, from a crypt string. The crypt string must consist of
177             "B<$1$>", the salt, "B<$>", then 22 base 64 digits giving the hash.
178             The salt may be up to 8 characters long, and cannot contain "B<$>"
179             or any character that cannot appear in a crypt string.
180              
181             =cut
182              
183             sub from_crypt {
184 2     2 1 8 my($class, $passwd) = @_;
185 2 50       16 if($passwd =~ /\A\$1\$/) {
186 2 50       13 $passwd =~ m:\A\$1\$([!-#%-9;-~]{0,8})\$([./0-9A-Za-z]{22})\z:
187             or croak "malformed \$1\$ data";
188 2         11 my($salt, $hash) = ($1, $2);
189 2         9 return $class->new(salt => $salt, hash_base64 => $hash);
190             }
191 0         0 return $class->SUPER::from_crypt($passwd);
192             }
193              
194             =item Authen::Passphrase::MD5Crypt->from_rfc2307(USERPASSWORD)
195              
196             Generates a new passphrase recogniser object using the MD5-based
197             crypt() algorithm, from an RFC 2307 string. The string must consist of
198             "B<{CRYPT}>" (case insensitive) followed by an acceptable crypt string.
199              
200             =back
201              
202             =head1 METHODS
203              
204             =over
205              
206             =item $ppr->salt
207              
208             Returns the salt, in raw form.
209              
210             =cut
211              
212             sub salt {
213 9     9 1 4114 my($self) = @_;
214 9         61 return $self->{salt};
215             }
216              
217             =item $ppr->hash_base64
218              
219             Returns the hash value, as a string of 22 base 64 digits.
220              
221             =cut
222              
223             sub hash_base64 {
224 9     9 1 24 my($self) = @_;
225 9         49 return $self->{hash_base64};
226             }
227              
228             =item $ppr->match(PASSPHRASE)
229              
230             =item $ppr->as_crypt
231              
232             =item $ppr->as_rfc2307
233              
234             These methods are part of the standard L interface.
235             Not every passphrase recogniser of this type can be represented as a
236             crypt string: the crypt format only allows the salt to be up to eight
237             bytes, and it cannot contain any NUL or "B<$>" characters.
238              
239             =cut
240              
241             sub _hash_base64_of {
242 28     28   55 my($self, $passphrase) = @_;
243             die "can't use a crypt-incompatible salt yet ".
244             "(need generalised Crypt::MD5Passwd)"
245             if $self->{salt} =~ /[^\!-\#\%-9\;-\~]/ ||
246 28 50 33     239 length($self->{salt}) > 8;
247 28         142 my $hash = unix_md5_crypt($passphrase, $self->{salt});
248 28         310481 $hash =~ s/\A.*\$//;
249 28         188 return $hash;
250             }
251              
252             sub match {
253 26     26 1 8676 my($self, $passphrase) = @_;
254 26         88 return $self->_hash_base64_of($passphrase) eq $self->{hash_base64};
255             }
256              
257             sub as_crypt {
258 10     10 1 25 my($self) = @_;
259             croak "can't put this salt into a crypt string"
260             if $self->{salt} =~ /[^\!-\#\%-9\;-\~]/ ||
261 10 50 33     86 length($self->{salt}) > 8;
262 10         65 return "\$1\$".$self->{salt}."\$".$self->{hash_base64};
263             }
264              
265             =back
266              
267             =head1 SEE ALSO
268              
269             L,
270             L
271              
272             =head1 AUTHOR
273              
274             Andrew Main (Zefram)
275              
276             =head1 COPYRIGHT
277              
278             Copyright (C) 2006, 2007, 2009, 2010, 2012
279             Andrew Main (Zefram)
280              
281             =head1 LICENSE
282              
283             This module is free software; you can redistribute it and/or modify it
284             under the same terms as Perl itself.
285              
286             =cut
287              
288             1;