Page 1 of 2

Very Secure Password Hashing using unknown salts.

Posted: Wed Jan 19, 2011 2:20 pm
by Benjamin
This code creates a variable length salt to the left and right of a fixed length password hash using the password length to determine the characters allocated for the salt. This makes it impossible to determine what part of the hash is the the salt vs what part of the hash is the actual hash.

Just posting this in case anyone finds it useful. Modify to suit.

Code: Select all

<?php
/**
 * Class asPasswordHash
 *
 * A very secure password hashing scheme using the whirlpool algorythm
 * and creating an impossible to find salt without knowing the actual
 * length of the password.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @package
 * @author     Benjamin Lewis
 * @version    1.0
 */
class asPasswordHash
{
    /**
     * Contains the algorythm used for hashing passwords
     *
     * @var string
     * @access private
     */
    private $_algorythm = 'whirlpool';

    /**
     *
     * @param string $algo The hashing algorythm to use
     * @access public
     */
    public function __construct($algo = null) {
        $this->_algorythm = $algo === null ? $this->_algorythm : $algo;
    }

    /**
     * Sets the algorythm to use when creating password hashes
     *
     * @var string The algorythm to use
     * @return object An instance of this class
     * @access public
     */
    public function setAlgo($algo) {
        $this->_algorythm = $algo;
        return $this;
    }

    /**
     * Gets the algorythm currently being used to create hashes
     *
     * @return string The current alogrythm in use
     */
    public function getAlgo() {
        return $this->_algorythm;
    }

    /**
     * Creates a salted password hash with the salt embedded in the actual
     * hash.
     *
     * @param  string $password The actual password
     * @param  string $hash The hash to check a password against
     * @return string The hashed password
     */
    public function get($password, $hash = null) {
        if (!in_array($this->_algorythm, hash_algos())) {
            trigger_error(sprintf("%s is not an available hashing algorythm", $this->_algorythm), E_USER_ERROR);
        }

        /*
         * If a hash is not specified, one is created by hashing a random
         * 256 byte string.
         */
        if ($hash === null) {
            $salt = '';
            for ($i = 0; $i <= 256; $i++) {
                $salt .= chr(mt_rand(0, 255));
            }

            $hashedSalt = hash($this->_algorythm, $salt);
        } else {
            $hashedSalt = $hash;
        }

        /*
         * The length of the salt to use is set by dividing the length
         * of the generated hash for this algorythm by 6 and rounding
         * down.
         */
        $saltLen = floor(strlen($hashedSalt) / 6);

        /*
         * Save the length of the password for later use.
         */
        $passwdLen = strlen($password);

        /*
         * While the length of the password is greater than the salt length,
         * multiply the length of the password by the value of the last digit
         * of the length divided by 10.
         *
         * Example:
         *     Password Length = 1009
         *     (9 / 10) = 0.9
         *     (1009 * 0.9) = 908.1
         *     New Password Length = 908.1
         */
        while ($passwdLen > $saltLen) {
            $passwdLen = $passwdLen * (substr($passwdLen, -1) / 10);
        }

        /*
         * We need $passwdLen to be an absolute integer value
         */
        $passwdLen  = (integer) abs($passwdLen);

        /*
         * Subtracting the length of the password from the length of the
         * salt that we will be using gives us the number of salt characters
         * to use in the final hash
         */
        $saltLen -= $passwdLen;

        /*
         * Populate the left and right salt values from the hashed salt using
         * the values we calculated earlier
         */
        $leftSalt  = substr($hashedSalt, 0, $passwdLen);
        $rightSalt = substr($hashedSalt, -$saltLen, $saltLen);

        /*
         * Generate the password hash
         */
        $passwdHash = hash($this->_algorythm, ($leftSalt . $password . $rightSalt));

        /*
         * Cut off left and right portions of the hashed password.  These will
         * be replaced by the left and right salts.
         */
        if ($saltLen == 0) {
            $passwdHash = substr($passwdHash, $passwdLen);
        } else {
            $passwdHash = substr($passwdHash, $passwdLen, -$saltLen);
        }

        /*
         * Return the left salt, password hash and right salt
         */
        return $leftSalt . $passwdHash . $rightSalt;
    }
}
Usage:

Code: Select all

    public static function isUserPassword($password, $passwordHash) {
        $pHash = new asPasswordHash();
        return $passwordHash == $pHash->get($password, $passwordHash);
    }

    public function setPassword($password) {
        /*
         * Every time this method is called a new password hash will be
         * generated, even if the password has not been changed.  This
         * check will stop the password update if it has not been changed
         * by comparing the password hash with the password posted.
         */
        $pHash = new asPasswordHash();
        if ($this->password == $pHash->get($password, $this->password)) {
            return $this;
        } else {
            return parent::setPassword($pHash->get($password));
        }
    }

Code: Select all

CREATE TABLE IF NOT EXISTS `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(25) NOT NULL,
  `email` varchar(64) NOT NULL,
  `password` varchar(128) NOT NULL,
  `reg_ip_lan` int(11) DEFAULT NULL,
  `reg_ip_wan` int(11) DEFAULT NULL,
  `last_login` datetime DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_U_1` (`user_name`),
  UNIQUE KEY `user_U_2` (`email`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

Re: Very Secure Password Hashing using unknown salts.

Posted: Wed Jan 19, 2011 2:49 pm
by Jonah Bron
Looks good, a clever way to eliminate the extra table column.

Re: Very Secure Password Hashing using unknown salts.

Posted: Wed Jan 19, 2011 3:19 pm
by Benjamin
Well, in reality a salt is useless if it is known.

Re: Very Secure Password Hashing using unknown salts.

Posted: Wed Jan 19, 2011 3:45 pm
by Jonah Bron
What do you mean? Isn't it unknown stored in a database table?

Re: Very Secure Password Hashing using unknown salts.

Posted: Wed Jan 19, 2011 3:50 pm
by Benjamin
Sure. But so is the hash. So why use the salt in the first place;)

Re: Very Secure Password Hashing using unknown salts.

Posted: Wed Jan 19, 2011 4:00 pm
by Peter Kelly
Its very good but surely doing

Code: Select all

$password = md5($password . $password . $password);
Is just as secure as it basically has a salt as you cant work out the salt or the password without knowing the password.

Re: Very Secure Password Hashing using unknown salts.

Posted: Wed Jan 19, 2011 4:09 pm
by Jonah Bron
Wait, what hash?

Re: Very Secure Password Hashing using unknown salts.

Posted: Wed Jan 19, 2011 4:22 pm
by VladSun
Benjamin wrote:Well, in reality a salt is useless if it is known.
Benjamin wrote:Sure. But so is the hash. So why use the salt in the first place;)
I think rainbow attacks or dictionary attacks are useless (or at least very hard to apply) for salted hashes even if the salt is known. Yes, a "secret" salt will increase the security, but if the salt is long enough it really doesn't matter.

Re: Very Secure Password Hashing using unknown salts.

Posted: Thu Jan 20, 2011 11:31 am
by Benjamin
VladSun wrote:I think rainbow attacks or dictionary attacks are useless (or at least very hard to apply) for salted hashes even if the salt is known.
What makes you say this?
Jonah Bron wrote:Wait, what hash?
The one stored in the database.
Peter Kelly wrote:Its very good but surely doing

Code: Select all

$password = md5($password . $password . $password);
Is just as secure as it basically has a salt as you cant work out the salt or the password without knowing the password.
No. An attacker only needs to know how the hashes are generated in order to break them.

Re: Very Secure Password Hashing using unknown salts.

Posted: Thu Jan 20, 2011 12:38 pm
by Jonah Bron
Benjamin wrote:The one stored in the database.
You mean the hash of the password?

Re: Very Secure Password Hashing using unknown salts.

Posted: Thu Jan 20, 2011 1:25 pm
by Benjamin
Yeah

Re: Very Secure Password Hashing using unknown salts.

Posted: Thu Jan 20, 2011 1:27 pm
by Jonah Bron
I see. So if they have the database and the code, the salt won't help?

Re: Very Secure Password Hashing using unknown salts.

Posted: Thu Jan 20, 2011 1:39 pm
by Benjamin
This wikipedia entry covers the topic pretty well: http://en.wikipedia.org/wiki/Salt_%28cryptography%29

Re: Very Secure Password Hashing using unknown salts.

Posted: Thu Jan 20, 2011 2:04 pm
by Jonah Bron
Okay, I read it. Does one store a hash of the salt or the salt itself? I'm still not entirely clear on how it secures things if a malicious person has access to the database.

Re: Very Secure Password Hashing using unknown salts.

Posted: Thu Jan 20, 2011 2:07 pm
by Benjamin
Well most of the code I've seen that actually *does* use a salt generally prefixes the password with it, so it's easy to find.

My class above creates a random, variable length salt that is mixed in with the actual password hash, so it's extremely difficult to determine what the salt actually is.