Skip to content

Instantly share code, notes, and snippets.

@mz0
Last active March 12, 2018 09:37
Show Gist options
  • Save mz0/274ac003028899ca3dfe3cc8c779447d to your computer and use it in GitHub Desktop.
Save mz0/274ac003028899ca3dfe3cc8c779447d to your computer and use it in GitHub Desktop.
a PHP function to check user credentials against .htpasswd file with apr1 (MD5) hashes
<?php
/* loosely based on
https://elonen.iki.fi/code/misc-notes/htpasswd-php
Use
===
require('htpasswd.inc');
if ( passOK($user,$pass) ) print "Access granted.";
*/
/**
* @param $plaintext
* @param $salt
* @return string - as found in .htpasswd for given $plaintext password and $salt
Copied (+fix initialization warning) from
www.virendrachandak.com/techtalk/using-php-create-passwords-for-htpasswd-file
originally from
stackoverflow.com/questions/2994637/how-to-edit-htpasswd-using-php/8786956#8786956
*/
function apr1hash($plaintext,$salt) {
$len = strlen($plaintext);
$txt = $plaintext.'$apr1$'.$salt;
$bin = pack("H32", md5($plaintext.$salt.$plaintext));
for($i = $len; $i > 0; $i -= 16) { $txt .= substr($bin, 0, min(16, $i)); }
for($i = $len; $i > 0; $i >>= 1) { $txt .= ($i & 1) ? chr(0) : $plaintext{0}; }
$bin = pack("H32", md5($txt));
for($i = 0; $i < 1000; $i++) {
$new = ($i & 1) ? $plaintext : $bin;
if ($i % 3) $new .= $salt;
if ($i % 7) $new .= $plaintext;
$new .= ($i & 1) ? $bin : $plaintext;
$bin = pack("H32", md5($new));
}
$tmp='';
for ($i = 0; $i < 5; $i++) {
$k = $i + 6;
$j = $i + 12;
if ($j == 16) $j = 5;
$tmp = $bin[$i].$bin[$k].$bin[$j].$tmp;
}
$tmp = chr(0).chr(0).$bin[11].$tmp;
$tmp = strtr(strrev(substr(base64_encode($tmp), 2)),
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
return '$apr1$'.$salt.'$'.$tmp;
}
// Loads htpasswd file into an array of form
// Array( username => crypted_pass, ... )
function load_htpasswd($fileloc='.htpasswd') {
$res = Array();
if ( file_exists($fileloc) ) {
foreach(file($fileloc) as $l) {
$array = explode(':',$l);
$user = $array[0];
$pass = chop($array[1]);
$res[$user] = $pass;
}
}
return $res;
}
/* Where's salt?
apr_md5_encode(const char *pw, const char *salt, char *result, apr_size_t nbytes)
http://svn.apache.org/viewvc/apr/apr/trunk/crypto/apr_md5.c?revision=1460244&view=markup#l535
// It stops at the first '$' or 8 chars, whichever comes first
for (ep = sp; (*ep != '\0') && (*ep != '$') && (ep < (sp + 8)); ep++) {
continue;
}
// Get the length of the true salt
sl = ep - sp;
see .htpasswd, salt is always 8 chars long, ends with '$':
3oVLW6C6$m2gqFRWaYmZURzERixkKd1
bdlItA56$sYBF3T/7vzNbkC/3mPse01
gHB5qipH$9xQSr6o6BOoAUO34MM.8y1
0uvNx1Oj$wobMgp7H6Wxbi5M0fjsYK/
eI7jaDcY$kjSqQ7DId/Gh04WvWMkO/0
*/
/**
* @param string $user
* @param string $pass
* @param string $fileloc
* @return bool true if $user exists in $fileloc (.htpasswd is the default) and $pass matches
*
*/
function passOK( $user, $pass, $fileloc='.htpasswd' ) {
$pass_array = load_htpasswd($fileloc);
if ( !isset($pass_array[$user])) {
print "No record found for $user";
return False;
}
$rec = $pass_array[$user];
/* See .htpasswd record parsing reference implementation in
apr_password_validate(const char *passwd, const char *hash)
definition in
[apr_password.c](http://svn.apache.org/viewvc/apr/apr/trunk/crypto/apr_passwd.c?view=markup#l108)
*/
if (substr($rec, 0, 6) == '$apr1$' ) {
$salt = substr($rec,6,8);
return $rec == apr1hash($pass,$salt);
} else {
// we do not deal with y2 (blowfish) and others
print "No idea what's going on here!";
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment