1# Functions for MD5 and SHA1 password encryption 2 3use strict; 4use warnings; 5our %config; 6 7# check_md5() 8# Returns a perl module name if the needed perl module(s) for MD5 encryption 9# are not installed, or undef if they are 10sub check_md5 11{ 12# On some systems, the crypt function just works! 13return undef if (&unix_crypt_supports_md5()); 14 15# Try Perl modules 16eval "use MD5"; 17if (!$@) { 18 eval "use Digest::MD5"; 19 if ($@) { 20 return "Digest::MD5"; 21 } 22 } 23return undef; 24} 25 26# encrypt_md5(string, [salt]) 27# Returns a string encrypted in MD5 format 28sub encrypt_md5 29{ 30my ($passwd, $salt) = @_; 31my $magic = '$1$'; 32if ($salt && $salt =~ /^\$1\$([^\$]+)/) { 33 # Extract actual salt from already encrypted password 34 $salt = $1; 35 } 36if ($salt && $salt !~ /^[a-z0-9\/\.]{8}$/i) { 37 # Non-MD5 salt 38 $salt = undef; 39 } 40$salt ||= substr(time(), -8); 41 42# Use built-in crypt support for MD5, if we can 43if (&unix_crypt_supports_md5()) { 44 return crypt($passwd, $magic.$salt.'$xxxxxxxxxxxxxxxxxxxxxx'); 45 } 46 47# Add the password, magic and salt 48my $cls = "MD5"; 49eval "use MD5"; 50if ($@) { 51 $cls = "Digest::MD5"; 52 eval "use Digest::MD5"; 53 if ($@) { 54 &error("Missing MD5 or Digest::MD5 perl modules"); 55 } 56 } 57my $ctx = eval "new $cls"; 58$ctx->add($passwd); 59$ctx->add($magic); 60$ctx->add($salt); 61 62# Add some more stuff from the hash of the password and salt 63my $ctx1 = eval "new $cls"; 64$ctx1->add($passwd); 65$ctx1->add($salt); 66$ctx1->add($passwd); 67my $final = $ctx1->digest(); 68for(my $pl=length($passwd); $pl>0; $pl-=16) { 69 $ctx->add($pl > 16 ? $final : substr($final, 0, $pl)); 70 } 71 72# This piece of code seems rather pointless, but it's in the C code that 73# does MD5 in PAM so it has to go in! 74my $j = 0; 75for(my $i=length($passwd); $i; $i >>= 1) { 76 if ($i & 1) { 77 $ctx->add("\0"); 78 } 79 else { 80 $ctx->add(substr($passwd, $j, 1)); 81 } 82 } 83$final = $ctx->digest(); 84 85# This loop exists only to waste time 86for(my $i=0; $i<1000; $i++) { 87 my $ctx1 = eval "new $cls"; 88 $ctx1->add($i & 1 ? $passwd : $final); 89 $ctx1->add($salt) if ($i % 3); 90 $ctx1->add($passwd) if ($i % 7); 91 $ctx1->add($i & 1 ? $final : $passwd); 92 $final = $ctx1->digest(); 93 } 94 95# Convert the 16-byte final string into a readable form 96my $rv = $magic.$salt.'$'; 97my @final = map { ord($_) } split(//, $final); 98my $l = ($final[ 0]<<16) + ($final[ 6]<<8) + $final[12]; 99$rv .= &to64($l, 4); 100$l = ($final[ 1]<<16) + ($final[ 7]<<8) + $final[13]; 101$rv .= &to64($l, 4); 102$l = ($final[ 2]<<16) + ($final[ 8]<<8) + $final[14]; 103$rv .= &to64($l, 4); 104$l = ($final[ 3]<<16) + ($final[ 9]<<8) + $final[15]; 105$rv .= &to64($l, 4); 106$l = ($final[ 4]<<16) + ($final[10]<<8) + $final[ 5]; 107$rv .= &to64($l, 4); 108$l = $final[11]; 109$rv .= &to64($l, 2); 110 111return $rv; 112} 113 114# unix_crypt_supports_md5() 115# Returns 1 if the built-in crypt() function can already do MD5 116sub unix_crypt_supports_md5 117{ 118my $hash = '$1$A9wB3O18$zaZgqrEmb9VNltWTL454R/'; 119my $newhash = eval { crypt('test', $hash) }; 120return $newhash eq $hash; 121} 122 123our @itoa64 = split(//, "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); 124 125sub to64 126{ 127my ($v, $n) = @_; 128my $r = ""; 129while(--$n >= 0) { 130 $r .= $itoa64[$v & 0x3f]; 131 $v >>= 6; 132 } 133return $r; 134} 135 136sub check_sha1 137{ 138eval "use Digest::SHA1"; 139return $@ ? "Digest::SHA1" : undef; 140} 141 142# encrypt_sha1(password) 143# Encrypts a password in SHA1 format 144sub encrypt_sha1 145{ 146my ($pass) = @_; 147my $sh = eval "use Digest::SHA1 qw(sha1_base64);return sha1_base64(\$pass);"; 148return "{SHA}$sh="; 149} 150 151# encrypt_sha1_hash(password, salt) 152# Hashes a combined salt+password with SHA1, and returns it in hex. Used on OSX 153sub encrypt_sha1_hash 154{ 155my ($pass, $salt) = @_; 156# XXX not done yet?? 157} 158 159# check_blowfish() 160# Returns an missing Perl module if blowfish is not available, undef if OK 161sub check_blowfish 162{ 163eval "use Crypt::Eksblowfish::Bcrypt"; 164return $@ ? "Crypt::Eksblowfish::Bcrypt" : undef; 165} 166 167# encrypt_blowfish(password, [salt]) 168# Returns a string encrypted in blowfish format, suitable for /etc/shadow 169sub encrypt_blowfish 170{ 171my ($passwd, $salt) = @_; 172my ($plain, $base64) = ("", ""); 173eval "use Crypt::Eksblowfish::Bcrypt"; 174if ($salt && $salt !~ /^\$2a\$/) { 175 # Invalid salt for Blowfish 176 $salt = undef; 177 } 178if (!$salt) { 179 # Generate a 22-character base-64 format salt 180 &seed_random(); 181 while(length($base64) < 22) { 182 $plain .= chr(int(rand()*96)+32); 183 $base64 = Crypt::Eksblowfish::Bcrypt::en_base64($plain); 184 } 185 $base64 = substr($base64, 0, 22); 186 $salt = '$2a$'.'08'.'$'.$base64; 187 } 188return Crypt::Eksblowfish::Bcrypt::bcrypt($passwd, $salt); 189} 190 191# unix_crypt_supports_sha512() 192# Returns 1 if the built-in crypt() function can already do SHA512 193sub unix_crypt_supports_sha512 194{ 195my $hash = '$6$Tk5o/GEE$zjvXhYf/dr5M7/jan3pgunkNrAsKmQO9r5O8sr/Cr1hFOLkWmsH4iE9hhqdmHwXd5Pzm4ubBWTEjtMeC.h5qv1'; 196my $newhash = eval { crypt('test', $hash) }; 197return $newhash eq $hash; 198} 199 200# check_sha512() 201# Returns undef if SHA512 hashing is supported, or an error message if not 202sub check_sha512 203{ 204return &unix_crypt_supports_sha512() ? undef : 'Crypt::SHA'; 205} 206 207# encrypt_sha512(password, [salt]) 208# Hashes a password, possibly with the give salt, with SHA512 209sub encrypt_sha512 210{ 211my ($passwd, $salt) = @_; 212$salt ||= '$6$'.substr(time(), -8).'$'; 213return crypt($passwd, $salt); 214} 215 216# validate_password(password, hash) 217# Compares a password with a hash to see if they match, returns 1 if so, 218# 0 otherwise. Tries all supported hashing schemes. 219sub validate_password 220{ 221my ($passwd, $hash) = @_; 222 223# Classic Unix crypt 224my $chash = eval { 225 local $main::error_must_die = 1; 226 &unix_crypt($passwd, $hash); 227 }; 228return 1 if ($chash eq $hash); 229 230# MD5 231if (!&check_md5()) { 232 my $mhash = &encrypt_md5($passwd, $hash); 233 return 1 if ($mhash eq $hash); 234 } 235 236# Blowfish 237if (!&check_blowfish()) { 238 my $mhash = &encrypt_blowfish($passwd, $hash); 239 return 1 if ($mhash eq $hash); 240 } 241 242# SHA1 243if (!&check_sha512()) { 244 my $shash = &encrypt_sha512($passwd, $hash); 245 return 1 if ($shash && $shash eq $hash); 246 } 247 248# Some other hashing, maybe supported by crypt 249my $ohash = eval { crypt($passwd, $hash) }; 250return 1 if ($ohash && $ohash eq $hash); 251 252return 0; 253} 254 255=head2 is_dictionary_word(word) 256 257Returns 1 if some file can be found in a dictionary words file 258 259=cut 260sub is_dictionary_word 261{ 262my ($word) = @_; 263$word = lc($word); 264my @files; 265if ($config{'dict_file'}) { 266 @files = split(/\s+/, $config{'dict_file'}); 267 } 268else { 269 @files = ( "/usr/share/dict/words", 270 "/usr/share/dict/linux.words", 271 "/usr/dict/words" ); 272 } 273foreach my $f (@files) { 274 my $found = 0; 275 my $fh = "WORDS"; 276 &open_readfile($fh, $f); 277 while(<$fh>) { 278 s/#.*//; 279 s/\s//; 280 if (lc($_) eq $word) { 281 $found = 1; 282 last; 283 } 284 } 285 close($fh); 286 return 1 if ($found); 287 } 288return 0; 289} 290 2911; 292 293