Files
DomFramework/src/password.php

244 lines
8.3 KiB
PHP

<?php
/** DomFramework
* @package domframework
* @author Dominique Fournier <dominique@fournier38.fr>
* @license BSD
*/
namespace Domframework;
/** A class to manage the password hashing, password generation
*/
class password
{
////////////////////
// PROPERTIES //
////////////////////
/** List all the allowed hashing methods, sort from weak to strong encryption
*/
private $methods = array (
// {{{
"MYSQL" => array (
"hash" => "", "size" => "", "pre" => "", "post" => ""),
"CRYPT_STD_DES" => array (
"hash" => CRYPT_STD_DES, "size" => 2, "pre" => "", "post" => "" ),
"CRYPT_EXT_DES" => array (
"hash" => CRYPT_EXT_DES, "size" => 9, "pre" => "", "post" => "" ),
"CRYPT_MD5" => array (
"hash" => CRYPT_MD5, "size" => 12, "pre" => "$1$", "post" => "$" ),
"CRYPT_BLOWFISH" => array (
"hash" => CRYPT_BLOWFISH, "size" => 16, "pre" => "$2y$11$",
"post" => "$" ),
"CRYPT_SHA256" => array (
"hash" => CRYPT_SHA256, "size" => 16, "pre" => "$5\$rounds=5000$",
"post" => "$" ),
"CRYPT_SHA512" => array (
"hash" => CRYPT_SHA512, "size" => 16, "pre" => "$6\$rounds=5000$",
"post" => "$" ),
"PASSWORD_BCRYPT" => array (
"hash" => PASSWORD_BCRYPT, "size" => "", "pre" => "", "post" => ""),
"PASSWORD_ARGON2I" => array (
"hash" => PASSWORD_ARGON2I, "size" => "", "pre" => "", "post" => ""),
"PASSWORD_ARGON2ID" => array (
"hash" => PASSWORD_ARGON2ID, "size" => "", "pre" => "", "post" => ""),
);
// }}}
/////////////////
// METHODS //
/////////////////
/** List all the allowed password encrytion methods, sorted from weak to
* strong encryption
* @return array ("PASSWORD_ARGON2ID" => PASSWORD_ARGON2ID);
*/
public function listMethods ()
// {{{
{
$res = array ();
foreach ($this->methods as $key => $params)
{
if ($key !== "MYSQL" && (! defined ($key) || $params["hash"] === 0))
continue;
if (substr ($key, 0, 9) === "PASSWORD_" &&
! function_exists ("password_hash"))
continue;
$res[$key] = $params["hash"];
}
return $res;
}
// }}}
/** Create a salt, based on openssl_random_pseudo_bytes function
* @return a string salt
*/
public function salt ()
// {{{
{
if (function_exists ("openssl_random_pseudo_bytes"))
$salt = substr (base64_encode (openssl_random_pseudo_bytes (17)), 0, 22);
elseif (function_exists ("random_bytes"))
$salt = substr (base64_encode(random_bytes (22)), 0, 22);
else
throw new \Exception (dgettext ("domframework",
"Password : no PHP support for random numbers (OpenSSL...)"), 500);
$salt = str_replace ("+",".",$salt);
return $salt;
}
// }}}
/** Crypt the provided password with the wanted crypt method
* @param string $password The password to crypt
* @param string|null $method The method to use. If null, use the strongest
* one available
* @return string The hashed password
*/
public function cryptPassword ($password, $method = null)
// {{{
{
if (! is_string ($password) && ! is_integer ($password))
throw new \Exception (dgettext ("domframework",
"Invalid clear password provided to be crypted : not a string"), 403);
$methods = $this->listMethods ();
if ($method === null)
{
end ($methods);
$method = key ($methods);
}
if (! key_exists ($method, $methods))
throw new \Exception (sprintf (dgettext ("domframework",
"Password : cryptPassword method not allowed : %s"), $method), 500);
$params = $this->methods[$method];
if (substr ($method, 0, 9) === "PASSWORD_")
return password_hash ($password, $params["hash"]);
if ($method === "MYSQL")
return "*" . strtoupper (sha1 (sha1 ($password, true)));
if (substr ($method, 0, 6) === "CRYPT_")
{
$salt = $this->salt ();
$tmpSalt = $params["pre"].substr ($salt, 0, $params["size"]).
$params["post"];
return crypt ($password, $tmpSalt);
}
// Will never match here
throw new \Exception (sprintf (dgettext ("domframework",
"Password : Unknown method to crypt requested : %s"), $method), 500);
}
// }}}
/** Crypt the password with the best algorithm available
* @param string $password The password to crypt
* @return string The hashed password
*/
static public function cryptPasswd ($password)
// {{{
{
$passwd = new password ();
return $passwd->cryptPassword ($password);
}
// }}}
/** Check if the clear password is valid against the hashed one
* @param string $clear The clear password
* @param string $hashed The hashed password
* @return boolean true if the password correspond to the hash
*/
static public function checkPassword ($clear, $hashed)
// {{{
{
if (! is_string ($clear))
throw new \Exception (dgettext ("domframework",
"Invalid clear password provided to be checked : not a string"), 403);
if (! is_string ($hashed))
throw new \Exception (dgettext ("domframework",
"Invalid hashed password provided to be checked : not a string"), 403);
if (function_exists ("password_verify"))
return password_verify ($clear, $hashed);
// Crypt do not work with Argon2. But If Argon2 exists, password_verify
// should exists...
if (crypt ($clear, $hashed) === $hashed)
return true;
return false;
}
// }}}
/** Create a random password with $nbChars chars, ASCII chars (with special
* chars).
* A maximum of 20% for special chars in the size
* @param integer|null $nbChars The number of chars (12 by default)
* @return The random password
*/
static public function generateASCII ($nbChars = 12)
// {{{
{
if (! is_int ($nbChars) || $nbChars < 1)
throw new \Exception (dgettext ("domframework",
"Password : generateASCII : invalid nbChars provided : not an int or ".
"negative or null"));
if ($nbChars > 72)
throw new \Exception (dgettext ("domframework",
"Password : generateASCII : Can't generate more than 72 chars"));
$password = array ();
$chars = array_merge (range (97, 122), range (65, 90), range (48, 57));
$special = range (33, 47);
$rand1 = array_rand ($chars, floor ($nbChars * 0.80));
foreach ($rand1 as $chr)
$password[] = chr($chars[$chr]);
$rand2 = array_rand ($special, ceil ($nbChars * 0.2));
foreach ($rand2 as $chr)
$password[] = chr($special[$chr]);
shuffle ($password);
return implode ($password);
}
// }}}
/** Create a random password with $nbChars chars, Alphanumericals chars
* (without special chars)
* @param integer|null $nbChars The number of chars (12 by default)
* @return The random password
*/
static public function generateAlphanum ($nbChars = 12)
// {{{
{
if (! is_int ($nbChars) || $nbChars < 1)
throw new \Exception (dgettext ("domframework",
"Password : generateAlphanum : invalid nbChars provided : not an int or ".
"negative or null"));
if ($nbChars > 72)
throw new \Exception (dgettext ("domframework",
"Password : generateASCII : Can't generate more than 72 chars"));
$password = array ();
$chars = array_merge (range (97, 122), range (65, 90), range (48, 57));
$rand1 = array_rand ($chars, $nbChars);
foreach ($rand1 as $chr)
$password[] = chr($chars[$chr]);
shuffle ($password);
return implode ($password);
}
// }}}
/** Create a random password with $nbChars chars, Alphabeticals chars
* (without special chars, neither numbers)
* @param integer|null $nbChars The number of chars (12 by default)
* @return The random password
*/
static public function generateAlphabetical ($nbChars = 12)
// {{{
{
if (! is_int ($nbChars) || $nbChars < 1)
throw new \Exception (dgettext ("domframework",
"Password : generateAlphanum : invalid nbChars provided : not an int or ".
"negative or null"));
if ($nbChars > 72)
throw new \Exception (dgettext ("domframework",
"Password : generateASCII : Can't generate more than 72 chars"));
$password = array ();
$chars = array_merge (range (97, 122), range (65, 90));
$rand1 = array_rand ($chars, $nbChars);
foreach ($rand1 as $chr)
$password[] = chr($chars[$chr]);
shuffle ($password);
return implode ($password);
}
// }}}
}