Password Validator
<?php
/**
* Squiloople Framework
*
* LICENSE: Feel free to use and redistribute this code.
*
* @author Michael Rushton <michael@squiloople.com>
* @link http://squiloople.com/
* @category Squiloople
* @package Models
* @subpackage Validators
* @version 1.0
* @copyright © 2011 Michael Rushton
*/
// Define the namespace
namespace Models\Validators;
/**
* Password Validator
*
* Hash or validate passwords
*/
final class PasswordValidator
{
/**
* The password
*
* @access private
* @var string $_password
*/
private $_password;
/**
* The salt
*
* @access private
* @var string $_salt
*/
private $_salt;
/**
* The pepper
*
* @access private
* @var string $_pepper
*/
private $_pepper = 'Sz^3X6r[UyvV~2]_0stT}8 uY7RwZx4{q|Q91W5';
/**
* Set the password
*
* @access public
* @param string $password
*/
public function __construct($password = '')
{
$this->_password = $password;
}
/**
* Call the constructor fluently
*
* @access public
* @static
* @param string $password
* @return \Models\Validators\PasswordValidator
*/
public static function setPassword($password)
{
return new self($password);
}
/**
* Return the password
*
* @access public
* @return string
*/
public function getPassword()
{
return $this->_password;
}
/**
* Create a random password
*
* @access public
* @return \Models\Validators\PasswordValidator
*/
public function randomizePassword()
{
// If the password is incorrectly formed then randomize again
if (!$this->isValid($this->_password = substr($this->getSalt(true), 0, 8)))
{
$this->randomizePassword();
}
// Reset the salt
$this->_salt = null;
// Return the object
return $this;
}
/**
* Validate the password
*
* @access public
* @param bool|string $new
* @return bool
*/
public function isValid($new = false)
{
// The password must contain at least one character of each case, one digit, and be between 8 and 39 characters inclusive in length
if (!preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[\x20-\x7E]{8,39}$/D', $new ?: $this->_password))
{
return false;
}
// Otherwise return true
return true;
}
/**
* Set the salt
*
* @access public
* @param string $salt
* @return \Models\Validators\PasswordValidator
*/
public function setSalt($salt)
{
// Set the salt
$this->_salt = $salt;
// Return itself
return $this;
}
/**
* Generate and return a salt
*
* @access public
* @param bool $reset
* @return string
*/
public function getSalt($reset = false)
{
// If a salt has been set and a reset is not required then return the stored salt
if (!$reset && isset($this->_salt))
{
return $this->_salt;
}
// Reset the salt
$salt = '';
// Generate a random salt of 39 printable ASCII characters
for ($i = 1; $i <= 39; ++$i)
{
$salt .= chr(mt_rand(32, 126));
}
// If the salt does not have the correct syntax then regenerate
if (!$this->isValid($salt))
{
$this->getSalt(true);
}
// Return the random salt
return $this->_salt = $salt;
}
/**
* Return the pepper portion
*
* @access private
* @return string
*/
private function _getPepper()
{
return substr($this->_pepper, 0, 39 - strlen($this->_password));
}
/**
* Hash the password
*
* @access private
* @return string
*/
private function _getHash()
{
return hash('sha384', $this->getSalt() . $this->_password . $this->_getPepper());
}
/**
* Hash the password using an HMAC-inspired hash
*
* @access public
* @return string
*/
public function getHash()
{
return hash('sha512', substr(strrev($this->_pepper), 0, 20) . $this->_getHash());
}
}
To instantiate the password validator, either use the new keyword or call the class statically using \Models\Validators\PasswordValidator::setPassword() passing as the only parameter the password.
$passwordValidator = new \Models\Validators\PasswordValidator('password');
To validate the password, which must be between 8 and 39 characters inclusive and may only contain printable ASCII characters — at least one lower-case alphabetic character, one upper-case alphabetic character, and one digit — call the isValid() method. These methods accepts an optional parameter; if provided the parameter will be validated rather than the stored password.
$passwordValidator = \Models\Validators\PasswordValidator::setPassword('password');
$passwordValidator->isValid(); // Returns false
$passwordValidator->isValid('p4Ssw0rD'); // Returns true
To generate and store random valid password call the randomizePassword() method. This method does not accept any parameters.
$passwordValidator->randomizePassword();
To generate and return a salt, which is 39 characters in length and follows the same syntax rules as a valid password, call the getSalt() method. Generated salts are stored and returned if the method is called subsequent times. To generate a new salt pass a true parameter to the method.
$passwordValidator->getSalt(); // Returns h&%G8\SThz7\P"$j>nB[Fpip{_rS{(f{2DEw>4R
$passwordValidator->getSalt(); // Returns h&%G8\SThz7\P"$j>nB[Fpip{_rS{(f{2DEw>4R
$passwordValidator->getSalt(true) // Returns g0ow;+}5HnM*G ;|%!@?px$W4,DK)(3WbE*iK:1
To set an established salt, primarily used when verifying against an already hashed password, use the setSalt() method passing as the only parameter the salt. Calling getSalt() after setting a salt this way will return the set salt unless a true parameter is passed to the former.
$passwordValidator->setSalt('g0ow;+}5HnM*G ;|%!@?px$W4,DK)(3WbE*iK:1'); // Set the salt
To return a hash of the password, which automatically generates a salt if one has not already been set and appends to the password a portion of the pepper to increase its length to 39 characters, call the getHash() method. This uses an HMAC-inspired cryptographic hash function using SHA 384 for the first hash and SHA 512 for the second.
echo \Models\Validators\PasswordValidator::setPassword('p4$$W0rD')>getHash();
// Outputs: cc303db69078854d7d7501bc3df09c45a655daa60412d07747c25da98d612646ea5f8fb650fa8785f6b6ed8d350d6a4ae56343b3dd41e01ba2d93a163852b63c