Page 1 of 1

Password validation

Posted: Sun Feb 20, 2011 3:44 am
by MichaelR

Code: Select all

  /**
   * 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 Copyright © 2011 Michael Rushton
   */

  // Define the namespace
  namespace Models\Validators;

  /**
   * Password Validator
   *
   * Hash or validate passwords
   */
  final class PasswordValidator
  {

    /**
     * The password to hash or validate
     *
     * @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
     * @param string $key
     */
    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);
    }

    /**
     * Check to see if the password is between 8 and 39 characters inclusive in length
     *
     * @access public
     * @return bool
     */
    public function isValidLength()
    {

      // If the password is not between 8 and 39 characters inclusive in length then return false
      if (!isset($this->_password[7]) || isset($this->_password[39]))
      {
        return false;
      }

      // Otherwise return true
      return true;

    }

    /**
     * Check to see if the password (or the salt) is correctly formed
     *
     * @access public
     * @var bool $salt
     * @return bool
     */
    public function isValidSyntax($salt = false)
    {

      // Validate the salt if required else the password
      $password = $salt ?: $this->_password;

      // Return false if the password does not contain at least two characters of each case, two digits, and two other characters
      if (!preg_match('/^(?=(?:.*[a-z]){2})(?=(?:.*[A-Z]){2})(?=(?:.*[0-9]){2})(?=(?:.*[^a-zA-Z0-9]){2})[\x20-\x7E]+$/D', $password))
      {
        return false;
      }

      // Otherwise return true
      return true;

    }

    /**
     * Validate the password's length and syntax
     *
     * @access public
     * @return bool
     */
    public function isValid()
    {

      // If the length validation is successful then return the syntax validation result
      if ($this->isValidLength())
      {
        return $this->isValidSyntax();
      }

      // Otherwise return false
      return false;

    }

    /**
     * Set the salt
     *
     * @access public
     * @var 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
     * @var 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->isValidSyntax($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 using an HMAC
     *
     * @access public
     * @return string
     */
    public function getHMAC()
    {
      return hash_hmac('sha512', $this->_password . $this->_getPepper(), $this->getSalt());
    }

  }
To instantiate the password validator, either use the new keyword or call the class statically using PasswordValidator::setPassword() passing as the only parameter the password.

Code: Select all

$passwordValidator = new PasswordValidator('swordfish');
To validate the password's length, which must be between 8 and 39 characters inclusive, call the isValidLength() method. To validate the password's syntax, which may only contain printable ASCII characters and must contain at least two lower-case alphabetic characters, two upper-case alphabetic character, two digits, and two other characters, call the isValidSyntax() method.

Code: Select all

$passwordValidator = PasswordValidator::setPassword('swordfish');

$passwordValidator->isValidLength(); // Returns true
$passwordValidator->isValidSyntax(); // Returns false
$passwordValidator->isValid()        // Returns false
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.

Code: Select all

$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.

Code: Select all

$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 (the HMAC key) if one has not already been set and appends to the password a portion of the pepper to increase the total length to 39 characters, call the getHash() method. This uses an HMAC SHA 512 cryptographic hash function.

Code: Select all

$passwordValidator = new PasswordValidator('M1ch43L !$ 4w3S0m3');

if ($passwordValidator->isValid() && $passwordValidator->isValid(true))
{
  $hash = $passwordValidator->getHash();
}

// Returns: 645995e153424619fb6f6194f6d60c323a678470388f91c4a84ae713d56b3d00e9feb46040f92df799b00e689c6a9e285eb378ac6475193e13e715e53a3cb854
1. Assuming I use the salt as the HMAC key, and can pad the password with random characters, how many bits should each be, assuming best circumstances (randomness), to achieve optimum results.

Thanks.

Re: Password validation

Posted: Mon Feb 21, 2011 6:19 am
by Mordred
1. No, this is not hmac, and although I don't think it would matter for the purposes of password hashing, it would still be the safe option to use the proper hmac scheme.
2. What matters is not the number of characters in the output, but the number of bits in the input. Your salts are ~ 250 bits which is aplenty.

I think the interface is too clumsy and I don't think validation (and a rigid one at that) should live next to password hashing, it's two different responsibilities.
Also, you appear to think that "key" in the hmac context and "salt" in the password hashing context are two different things. No, you should use the salt from your password scheme as a key in the hmac algorithm.

Re: Password validation

Posted: Mon Feb 21, 2011 12:50 pm
by MichaelR
Thank you for the comments, Mordred. As I want to make the function as secure as possible, how many bits should the key and the password each be (the password can be lengthened as much as necessary using the "pepper", assuming I use hash_hmac('sha512', $password, $key)?

Edit: I wonder whether or not the following would be more efficient:

Code: Select all

//...

    /**
     * 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
     *
     * @access public
     * @return string
     */
    public function getHash()
    {
      return hash('sha512', substr(strrev($this->getSalt()), 0, 20) . $this->_getHash());
    }

//...
My reasoning: The optimum input size for SHA 512/SHA 384 is 512 bits. Assuming complete randomization, this is achieved using 78 of the 95 printable ASCII characters. 39 of these characters are from the unique (and random) salt, and further characters -- from the constant (randomized) "pepper" -- are appended to the password to increase its own length to 39 characters, thereby making the input a total of 78 ASCII characters in length (the minimum needed for randomly generated passwords to achieve an entropy of 512 bits). This is hashed using SHA 384 returning an output of 384 bits. 128 further bits (or 20 characters from the 95 printable ASCII) are then prepended to this hash to provide the 512 bits needed to optimize the subsequent SHA 512 hash (these 20 characters being taken from the salt (which is reversed to increase Hamming Distance (?)).

P.S. I'm curious; does this forum have inline code tags?

Re: Password validation

Posted: Mon Feb 21, 2011 3:14 pm
by Mordred
Both the salt and the pepper should be as strong as the strength requirements of passwords in your login system. 8 chars of upper/lower/digits are about 48 bits which is okay for your everyday use (I think; your paranoia levels may vary). If you're an online banking site, go for the 128 bits. Anything in-between is a reasonable value. Yours are a bit off-the-scale, but in security more is better, and storage is cheap, so go for it if you so wish.

Re: Password validation

Posted: Mon Feb 21, 2011 3:29 pm
by MichaelR
Mordred wrote:Yours are a bit off-the-scale, but in security more is better, and storage is cheap, so go for it if you so wish.
I'm never happy with second best. And it's also my intention to better understand how these things work -- if I just accept what's "good enough" then I'll never really know what makes it good (or bad).

Re: Password validation

Posted: Mon Feb 21, 2011 5:07 pm
by Mordred
MichaelR wrote: And it's also my intention to better understand how these things work -- if I just accept what's "good enough" then I'll never really know what makes it good (or bad).
I wholeheartedly agree and salute you for this kind of thinking. Never take down the thinking hat, that's my motto as well!

I wrote a bit more on the subject here.