File Coverage

blib/lib/Crypt/PasswdMD5.pm
Criterion Covered Total %
statement 73 73 100.0
branch 17 18 94.4
condition 4 6 66.6
subroutine 9 9 100.0
pod 3 4 75.0
total 106 110 96.3


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