* @license BSD */ /** 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); } // }}} }