File Coverage

blib/lib/Crypt/PasswdMD5.pm
Criterion Covered Total %
statement 76 76 100.0
branch 17 18 94.4
condition 4 6 66.6
subroutine 10 10 100.0
pod 3 4 75.0
total 110 114 96.4


line stmt bran cond sub pod time code
1             package Crypt::PasswdMD5;
2              
3 3     3   242491 use strict;
  3         3  
  3         82  
4 3     3   16 use warnings;
  3         4  
  3         159  
5              
6 3     3   1226 use Crypt::URandom qw(urandom);
  3         12783  
  3         212  
7 3     3   20 use Digest::MD5;
  3         5  
  3         95  
8 3     3   871 use Encode;
  3         26686  
  3         273  
9              
10 3     3   15 use Exporter 'import';
  3         6  
  3         2178  
11              
12             our @EXPORT = qw/unix_md5_crypt apache_md5_crypt/;
13             our @EXPORT_OK = (@EXPORT, 'random_md5_salt');
14             our $VERSION = '1.43';
15              
16             # ------------------------------------------------
17              
18             my($itoa64) = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
19             our($Magic) = q/$1$/; # Magic strings. Need 'our' because of local just below.
20             my($max_salt_length) = 8;
21              
22             # ------------------------------------------------
23              
24             sub apache_md5_crypt
25             {
26             # Change the Magic string to match the one used by Apache.
27              
28 2     2 1 21 local $Magic = q/$apr1$/;
29              
30 2         8 return unix_md5_crypt(@_);
31              
32             } # End of apache_md5_crypt.
33              
34             # ------------------------------------------------
35              
36             sub random_md5_salt
37             {
38 12   66 12 1 289691 my($len) = shift || $max_salt_length;
39 12         30 my($salt) = '';
40              
41             # Sanity check.
42              
43 12 100 66     63 $len = $max_salt_length unless ( ($len >= 1) and ($len <= $max_salt_length) );
44 12         66 $salt .= substr($itoa64,unpack("C",urandom(1))&0x3F,1) for (1..$len);
45              
46 12         1530 return $salt;
47              
48             } # End of random_md5_salt.
49              
50             # ------------------------------------------------
51              
52             sub to64
53             {
54 60     60 0 68 my($v, $n) = @_;
55 60         58 my($ret) = '';
56              
57 60         79 while (--$n >= 0)
58             {
59 220         225 $ret .= substr($itoa64, $v & 0x3f, 1);
60              
61 220         240 $v >>= 6;
62             }
63              
64 60         76 return $ret;
65              
66             } # End of to64.
67              
68             # ------------------------------------------------
69              
70             sub unix_md5_crypt
71             {
72 10     10 1 476 my($pw, $salt) = @_;
73 10 100       46 $pw = Encode::encode('utf8', $pw) if Encode::is_utf8($pw);
74              
75 10 100       28 if (defined $salt)
76             {
77 8         77 $salt =~ s/^\Q$Magic//; # Take care of the magic string if present.
78 8         26 $salt =~ s/^(.*)\$.*$/$1/; # Salt can have up to 8 chars...
79 8         14 $salt = substr($salt, 0, 8);
80             }
81             else
82             {
83 2         5 $salt = random_md5_salt(); # In case no salt was proffered.
84             }
85              
86 10         34 my($ctx) = Digest::MD5 -> new; # Here we start the calculation.
87              
88 10         26 $ctx -> add($pw); # Original password...
89 10         20 $ctx -> add($Magic); # ...our magic string...
90 10         21 $ctx -> add($salt); # ...the salt...
91              
92 10         20 my($final) = Digest::MD5 -> new;
93              
94 10         19 $final -> add($pw);
95 10         17 $final -> add($salt);
96 10         20 $final -> add($pw);
97              
98 10         40 $final = $final -> digest;
99              
100 10         39 for (my $pl = length($pw); $pl > 0; $pl -= 16)
101             {
102 8 50       36 $ctx -> add(substr($final, 0, $pl > 16 ? 16 : $pl) );
103             }
104              
105             # Now the 'weird' xform.
106              
107 10         22 for (my $i = length($pw); $i; $i >>= 1)
108             {
109 26 100       39 if ($i & 1)
110             {
111 16         39 $ctx -> add(pack('C', 0) );
112             }
113              
114             # This comes from the original version, where a
115             # memset() is done to $final before this loop.
116              
117             else
118             {
119 10         24 $ctx -> add(substr($pw, 0, 1) );
120             }
121             }
122              
123 10         19 $final = $ctx -> digest;
124              
125             # The following is supposed to make things run slower.
126             # In perl, perhaps it'll be *really* slow!
127              
128 10         18 for (my $i = 0; $i < 1000; $i++)
129             {
130 10000         16570 my($ctx1) = Digest::MD5 -> new;
131              
132 10000 100       14526 if ($i & 1)
133             {
134 5000         5999 $ctx1 -> add($pw);
135             }
136             else
137             {
138 5000         7235 $ctx1 -> add(substr($final, 0, 16) );
139             }
140              
141 10000 100       12421 if ($i % 3)
142             {
143 6660         7698 $ctx1 -> add($salt);
144             }
145              
146 10000 100       11346 if ($i % 7)
147             {
148 8570         9628 $ctx1 -> add($pw);
149             }
150              
151 10000 100       10597 if ($i & 1)
152             {
153 5000         7141 $ctx1 -> add(substr($final, 0, 16) );
154             }
155             else
156             {
157 5000         6044 $ctx1 -> add($pw);
158             }
159              
160 10000         25127 $final = $ctx1 -> digest;
161             }
162              
163             # Final xform
164              
165 10         10 my($passwd);
166              
167 10         15 $passwd = '';
168 10         70 $passwd .= to64(int(unpack('C', (substr($final, 0, 1) ) ) << 16)
169             | int(unpack('C', (substr($final, 6, 1) ) ) << 8)
170             | int(unpack('C', (substr($final, 12, 1) ) ) ), 4);
171 10         44 $passwd .= to64(int(unpack('C', (substr($final, 1, 1) ) ) << 16)
172             | int(unpack('C', (substr($final, 7, 1) ) ) << 8)
173             | int(unpack('C', (substr($final, 13, 1) ) ) ), 4);
174 10         42 $passwd .= to64(int(unpack('C', (substr($final, 2, 1) ) ) << 16)
175             | int(unpack('C', (substr($final, 8, 1) ) ) << 8)
176             | int(unpack('C', (substr($final, 14, 1) ) ) ), 4);
177 10         49 $passwd .= to64(int(unpack('C', (substr($final, 3, 1) ) ) << 16)
178             | int(unpack('C', (substr($final, 9, 1) ) ) << 8)
179             | int(unpack('C', (substr($final, 15, 1) ) ) ), 4);
180 10         43 $passwd .= to64(int(unpack('C', (substr($final, 4, 1) ) ) << 16)
181             | int(unpack('C', (substr($final, 10, 1) ) ) << 8)
182             | int(unpack('C', (substr($final, 5, 1) ) ) ), 4);
183 10         30 $passwd .= to64(int(unpack('C', substr($final, 11, 1) ) ), 2);
184              
185 10         83 return $Magic . $salt . q/$/ . $passwd;
186              
187             } # End of unix_md5_crypt.
188              
189             # ------------------------------------------------
190              
191             1;
192              
193             =pod
194              
195             =encoding utf-8
196              
197             =head1 NAME
198              
199             Crypt::PasswdMD5 - Provide interoperable MD5-based crypt() functions
200              
201             =head1 SYNOPSIS
202              
203             use strict;
204             use warnings;
205              
206             use Crypt::PasswdMD5;
207              
208             my($password) = 'seekrit';
209             my($salt) = 'pepperoni';
210             my($unix_crypted) = unix_md5_crypt($password, $salt);
211             my($apache_crypted) = apache_md5_crypt($password, $salt);
212              
213             Or:
214              
215             use strict;
216             use warnings;
217              
218             use Crypt::PasswdMD5 'random_md5_salt';
219              
220             my($length) = 7;
221             my($salt_1) = random_md5_salt($length);
222             my($salt_2) = random_md5_salt(); # Default to $length == 8.
223              
224              
225             =head1 DESCRIPTION
226              
227             C provides a function compatible with Apache's C<.htpasswd> files.
228             This was contributed by Bryan Hart .
229             This function is exported by default.
230              
231             The C provides a crypt()-compatible interface to the rather new MD5-based crypt() function
232             found in modern operating systems. It's based on the implementation found on FreeBSD 2.2.[56]-RELEASE.
233             This function is also exported by default.
234              
235             For both functions, if a salt value is not supplied, a random salt will be
236             generated, using the function random_md5_salt().
237             This function is not exported by default.
238              
239             =head1 LICENSE AND WARRANTY
240              
241             This code and all accompanying software comes with NO WARRANTY. You
242             use it at your own risk.
243              
244             This code and all accompanying software can be used freely under the
245             same terms as Perl itself.
246              
247             =head1 METHODS
248              
249             =head2 apache_md5_crypt($password, $salt)
250              
251             This sets a magic variable, and then passes all the calling parameters to L.
252              
253             Returns an encrypted version of the given password.
254              
255             Basically, it's a very poor choice for anything other than password authentication.
256              
257             =head2 random_md5_salt([$length])
258              
259             Here, [] indicate an optional parameter.
260              
261             Returns a random salt of the given length.
262              
263             The maximum length is 8.
264              
265             If C<$length> is omitted, it defaults to 8.
266              
267             =head2 unix_md5_crypt($password, $salt)
268              
269             Returns an encrypted version of the given password.
270              
271             Basically, it's a very poor choice for anything other than password authentication.
272              
273             =head1 Repository
274              
275             L
276              
277             =head1 SUPPORT
278              
279             Bugs should be reported via the CPAN bug tracker at
280              
281             L
282              
283             =head1 AUTHOR
284              
285             Luis E. Muñoz .
286              
287             Maintenance by Ron Savage as of V 1.40.
288              
289             =cut