PHP Developers Network

A community of PHP developers offering assistance, advice, discussion, and friendship.
 
Loading
It is currently Sat Dec 15, 2018 12:28 pm

All times are UTC - 5 hours




Post new topic Reply to topic  [ 6 posts ] 
Author Message
 Post subject: Password validation
PostPosted: Sun Feb 20, 2011 4:44 am 
Offline
Forum Contributor

Joined: Sat Jan 03, 2009 4:27 pm
Posts: 148
Syntax: [ Download ] [ Hide ]
  /**
   * 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.

Syntax: [ Download ] [ Hide ]
$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.

Syntax: [ Download ] [ Hide ]
$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.

Syntax: [ Download ] [ Hide ]
$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.

Syntax: [ Download ] [ Hide ]
$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.

Syntax: [ Download ] [ Hide ]
$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.


Last edited by MichaelR on Mon Feb 21, 2011 4:56 pm, edited 4 times in total.

Top
 Profile  
 
 Post subject: Re: Password validation
PostPosted: Mon Feb 21, 2011 7:19 am 
Offline
DevNet Resident
User avatar

Joined: Sun Sep 03, 2006 5:19 am
Posts: 1579
Location: Sofia, Bulgaria
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.


Top
 Profile  
 
 Post subject: Re: Password validation
PostPosted: Mon Feb 21, 2011 1:50 pm 
Offline
Forum Contributor

Joined: Sat Jan 03, 2009 4:27 pm
Posts: 148
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:

Syntax: [ Download ] [ Hide ]
//...

    /**
     * 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?


Last edited by MichaelR on Mon Feb 21, 2011 4:34 pm, edited 4 times in total.

Top
 Profile  
 
 Post subject: Re: Password validation
PostPosted: Mon Feb 21, 2011 4:14 pm 
Offline
DevNet Resident
User avatar

Joined: Sun Sep 03, 2006 5:19 am
Posts: 1579
Location: Sofia, Bulgaria
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.


Top
 Profile  
 
 Post subject: Re: Password validation
PostPosted: Mon Feb 21, 2011 4:29 pm 
Offline
Forum Contributor

Joined: Sat Jan 03, 2009 4:27 pm
Posts: 148
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).


Top
 Profile  
 
 Post subject: Re: Password validation
PostPosted: Mon Feb 21, 2011 6:07 pm 
Offline
DevNet Resident
User avatar

Joined: Sun Sep 03, 2006 5:19 am
Posts: 1579
Location: Sofia, Bulgaria
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.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 6 posts ] 

All times are UTC - 5 hours


Who is online

Users browsing this forum: Majestic-12 [Bot] and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Jump to:  
Powered by phpBB® Forum Software © phpBB Group