Skip to content

Instantly share code, notes, and snippets.

@KrzysztofPrzygoda
Last active September 19, 2022 07:37
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save KrzysztofPrzygoda/81963c1634e82329e3344bf36fd9364e to your computer and use it in GitHub Desktop.
Save KrzysztofPrzygoda/81963c1634e82329e3344bf36fd9364e to your computer and use it in GitHub Desktop.
A simple PHP .htpasswd and .htaccess generator
<pre>
<?php
/**
* A simple PHP .htpasswd and .htaccess generator
* If already present, .htpasswd is overwrittern but .htaccess is prepended with Access clousure if not found
*
* @author Krzysztof Przygoda, 2018
* @version 1.0
*/
// Choose encryption method
// See: http://httpd.apache.org/docs/2.2/misc/password_encryptions.html
$encryption = 'md5';
//$encryption = 'crypt';
//$encryption = 'sha';
// Setup login and passwords
$passwords = array(
'login1' => 'password1',
'login2' => 'password2',
'login3' => 'password3',
);
// Default .htaccess file
$htpasswd_path = dirname(__FILE__).'/.htpasswd';
$htaccess_block = <<<EOT
# BEGIN Access
AuthType Basic
AuthName "Password Protected Area"
AuthUserFile {$htpasswd_path}
Require valid-user
# END Access
\n
EOT;
/**
* Create random string
*
* @param integer $lenght String lenght
* @param string|null $aphabet Optional. Default alphabet "./0-9A-Za-z"
* @return string A random string
*/
function random_string($lenght = 8, $alphabet = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')
{
return substr(str_shuffle((string)$alphabet), 0, (integer)$lenght);
}
/**
* Encrypt password with APR1-MD5 Apache method
*
* @param string $password
* @param string|null $salt
* @return string Encrypted string
* @ref https://stackoverflow.com/a/8786956
*/
function crypt_apr1_md5($password, $salt = NULL)
{
if (!$salt) $salt = str_shuffle('./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz');
$salt = substr($salt, 0, 8);
$len = strlen($password);
$text = $password.'$apr1$'.$salt;
$bin = pack("H32", md5($password.$salt.$password));
for ($i = $len; $i > 0; $i -= 16) $text .= substr($bin, 0, min(16, $i));
for ($i = $len; $i > 0; $i >>= 1) $text .= ($i & 1) ? chr(0) : $password{0};
$bin = pack("H32", md5($text));
for ($i = 0; $i < 1000; $i++)
{
$new = ($i & 1) ? $password : $bin;
if ($i % 3) $new .= $salt;
if ($i % 7) $new .= $password;
$new .= ($i & 1) ? $bin : $password;
$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;
}
/**
* Encrypt password
*
* @param string $password
* @return string Encrypted password
*
* @see http://httpd.apache.org/docs/2.2/misc/password_encryptions.html
*/
function encrypt_password($password, $encryption = 'md5')
{
$result = FALSE;
$password = (string)$password;
switch ($encryption)
{
case 'crypt':
// Unix only. Uses the traditional Unix crypt(3) function with a randomly-generated 32-bit salt (only 12 bits used) and the first 8 characters of the password.
$salt = random_string(2); // Salt must be 2 char range ./0-9A-Za-z
$result = crypt($password, $salt);
break;
case 'sha':
// "{SHA}" + Base64-encoded SHA-1 digest of the password.
$result = '{SHA}'.base64_encode(sha1($password, true));
break;
case 'md5':
// "$apr1$" + the result of an Apache-specific algorithm using an iterated (1,000 times) MD5 digest of various combinations of a random 32-bit salt and the password.
// See the APR source file apr_md5.c for the details of the algorithm.
default:
$salt = random_string(8); // Salt must be 8 char range ./0-9A-Za-z
$result = crypt_apr1_md5($password, $salt);
break;
}
return $result;
}
/**
* Get .htpasswd file contents
* Looks for .htpasswd file path in .htaccess if provided
*
* @param string $path Full path to file: '/path/to/.htpasswd' or '/path/to/.htaccess'
* @return array Array with keys 'path' and 'contents', where values can be:
* string with .htpasswd contents (empty string means empty file)
* NULL when file does not exists
* FALSE on failure
*/
function file_get_htpasswd($path = '.htaccess')
{
$file = [
'path' => $path,
'contents' => NULL,
];
// If .htpasswd path file provided
if (strpos($file['path'], '.htpasswd') !== FALSE)
{
if (file_exists($file['path'])) $file['contents'] = file_get_contents($path);
return $file;
}
if (strpos($file['path'], '.htaccess') !== FALSE)
{
if (file_exists($file['path'])) $file['contents'] = file_get_contents($path);
if ($file['contents'] === FALSE) return FALSE;
}
else return FALSE;
// Then .htaccess path provided and we're looking for .htpasswd path
$file['path'] = NULL;
$htaccess_lines = explode("\n", $file['contents']);
foreach ($htaccess_lines as $line)
{
if ($line)
{
$line_parts = preg_split('/\s+/', $line); // Split spaces and tabs
if (strcmp($line_parts[0], 'AuthUserFile') == 0) $file['path'] = preg_replace('/"/', '', $line_parts[1]); // Clean quotes and double quotes
}
if ($file['path'] !== NULL) break;
}
$file['contents'] = NULL;
if ($file['path'] && file_exists($file['path'])) $file['contents'] = file_get_contents($file['path']);
return $file;
}
/**
* Save .htpasswd file contents
*
* @param array $passords An array of ('login' => 'password') pairs.
* @param string $path Optional. Full path to file: '/path/to/.htpasswd'. Default is current directory.
* @param string $encription Optional. Encryption method: crypt, sha, md5. Default is md5.
* @return integer|bool Number of bytes that were written to the file, or FALSE on failure.
*/
function file_put_htpasswd($passwords = array(), $path = '.htpasswd', $encryption = 'md5')
{
$contents = '';
foreach ($passwords as $login => $password)
{
$password = encrypt_password($password, $encryption);
$contents .= "{$login}:{$password}\n";
}
return file_put_contents($path, $contents);
}
// Encrypt passwords
var_dump($passwords);
$htaccess_file = [
'path' => '.htaccess',
'contents' => NULL,
];
$htpasswd_file = [
'path' => NULL,
'contents' => NULL,
];
// Create .htaccess if does not exists
if (file_exists($htaccess_file['path'])) $htaccess_file['contents'] = file_get_contents($htaccess_file['path']);
else file_put_contents($htaccess_file['path'], $htaccess_file['contents']);
var_dump($htaccess_file);
// Find .htpasswd path in .htaccess
$htpasswd_file = file_get_htpasswd($htaccess_file['path']);
var_dump($htpasswd_file);
// Add Auth block in .htaccess if does not exists
if (!$htpasswd_file['path'])
{
$htaccess_file['contents'] = $htaccess_block . $htaccess_file['contents'];
file_put_contents($htaccess_file['path'], $htaccess_file['contents']);
$htpasswd_file['path'] = $htpasswd_path;
}
// Save passwords to .htpasswd
file_put_htpasswd($passwords, $htpasswd_path);
?>
</pre>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment