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   217513 use strict;
  3         6  
  3         86  
4 3     3   38 use warnings;
  3         12  
  3         150  
5              
6 3     3   1158 use Crypt::URandom qw(urandom);
  3         13592  
  3         236  
7 3     3   20 use Digest::MD5;
  3         3  
  3         92  
8 3     3   931 use Encode;
  3         27770  
  3         203  
9              
10 3     3   15 use Exporter 'import';
  3         4  
  3         2262  
11              
12             our @EXPORT = qw/unix_md5_crypt apache_md5_crypt/;
13             our @EXPORT_OK = (@EXPORT, 'random_md5_salt');
14             our $VERSION = '1.44';
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 28 local $Magic = q/$apr1$/;
29              
30 2         7 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 196789 my($len) = shift || $max_salt_length;
39 12         21 my($salt) = '';
40              
41             # Sanity check.
42              
43 12 100 66     61 $len = $max_salt_length unless ( ($len >= 1) and ($len <= $max_salt_length) );
44 12         46 $salt .= substr($itoa64,unpack("C",urandom(1))&0x3F,1) for (1..$len);
45              
46 12         1040 return $salt;
47              
48             } # End of random_md5_salt.
49              
50             # ------------------------------------------------
51              
52             sub to64
53             {
54 60     60 0 72 my($v, $n) = @_;
55 60         58 my($ret) = '';
56              
57 60         79 while (--$n >= 0)
58             {
59 220         213 $ret .= substr($itoa64, $v & 0x3f, 1);
60              
61 220         234 $v >>= 6;
62             }
63              
64 60         77 return $ret;
65              
66             } # End of to64.
67              
68             # ------------------------------------------------
69              
70             sub unix_md5_crypt
71             {
72 10     10 1 488 my($pw, $salt) = @_;
73 10 100       90 $pw = Encode::encode('utf8', $pw) if Encode::is_utf8($pw);
74              
75 10 100       35 if (defined $salt)
76             {
77 8         126 $salt =~ s/^\Q$Magic//; # Take care of the magic string if present.
78 8         38 $salt =~ s/^(.*)\$.*$/$1/; # Salt can have up to 8 chars...
79 8         21 $salt = substr($salt, 0, 8);
80             }
81             else
82             {
83 2         7 $salt = random_md5_salt(); # In case no salt was proffered.
84             }
85              
86 10         52 my($ctx) = Digest::MD5 -> new; # Here we start the calculation.
87              
88 10         34 $ctx -> add($pw); # Original password...
89 10         43 $ctx -> add($Magic); # ...our magic string...
90 10         23 $ctx -> add($salt); # ...the salt...
91              
92 10         30 my($final) = Digest::MD5 -> new;
93              
94 10         26 $final -> add($pw);
95 10         21 $final -> add($salt);
96 10         25 $final -> add($pw);
97              
98 10         27 $final = $final -> digest;
99              
100 10         47 for (my $pl = length($pw); $pl > 0; $pl -= 16)
101             {
102 8 50       63 $ctx -> add(substr($final, 0, $pl > 16 ? 16 : $pl) );
103             }
104              
105             # Now the 'weird' xform.
106              
107 10         51 for (my $i = length($pw); $i; $i >>= 1)
108             {
109 26 100       48 if ($i & 1)
110             {
111 16         48 $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         39 $ctx -> add(substr($pw, 0, 1) );
120             }
121             }
122              
123 10         22 $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         28 for (my $i = 0; $i < 1000; $i++)
129             {
130 10000         13677 my($ctx1) = Digest::MD5 -> new;
131              
132 10000 100       11020 if ($i & 1)
133             {
134 5000         5653 $ctx1 -> add($pw);
135             }
136             else
137             {
138 5000         7080 $ctx1 -> add(substr($final, 0, 16) );
139             }
140              
141 10000 100       12321 if ($i % 3)
142             {
143 6660         7298 $ctx1 -> add($salt);
144             }
145              
146 10000 100       11161 if ($i % 7)
147             {
148 8570         9262 $ctx1 -> add($pw);
149             }
150              
151 10000 100       10507 if ($i & 1)
152             {
153 5000         7097 $ctx1 -> add(substr($final, 0, 16) );
154             }
155             else
156             {
157 5000         5532 $ctx1 -> add($pw);
158             }
159              
160 10000         22824 $final = $ctx1 -> digest;
161             }
162              
163             # Final xform
164              
165 10         12 my($passwd);
166              
167 10         20 $passwd = '';
168 10         86 $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         41 $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         34 $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         33 $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         35 $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         19 $passwd .= to64(int(unpack('C', substr($final, 11, 1) ) ), 2);
184              
185 10         96 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 METHODS
240              
241             =head2 apache_md5_crypt($password, $salt)
242              
243             This sets a magic variable, and then passes all the calling parameters to L.
244              
245             Returns an encrypted version of the given password.
246              
247             Basically, it's a very poor choice for anything other than password authentication.
248              
249             =head2 random_md5_salt([$length])
250              
251             Here, [] indicate an optional parameter.
252              
253             Returns a random salt of the given length.
254              
255             The maximum length is 8.
256              
257             If C<$length> is omitted, it defaults to 8.
258              
259             =head2 unix_md5_crypt($password, $salt)
260              
261             Returns an encrypted version of the given password.
262              
263             Basically, it's a very poor choice for anything other than password authentication.
264              
265             =head1 Repository
266              
267             L
268              
269             =head1 SUPPORT
270              
271             Bugs should be reported via the CPAN bug tracker at
272              
273             L
274              
275             =head1 LICENSE, AND DISCLAIMER
276              
277             See the accompanying LICENSE file.
278              
279             This program is distributed in the hope that it will be useful, but
280             without any warranty; without even the implied warranty of
281             merchantability or fitness for a particular purpose.
282              
283             =head1 AUTHOR
284              
285             Luis E. Muñoz .
286              
287             Maintenance by Ron Savage as of V 1.40.
288              
289             =cut