Sponsored Links :
  • When working with secure websites and applications, it’s important to consider any areas where security could be compromised. The main area to focus on is usually the user authentication systems where the site decides whether a specific person should be granted access, usually based on a username-password combination.

    Especially when authenticating users with special administration priveleges, we need to make sure we store these authentication details as securely as possible. This is where hashing comes in useful, a way of scrambling user passwords in such a way that the original password cannot be determined, but which still allows us to verify a user’s identity.

    Secure Password Generation

    The following Class will allow you to generate secure passwords for your PHP applications and generate hash values to store in a database to verify a user’s identity when they are logging in. You can download the files used in this tutorial here.

    General considerations of password management

    • Never store the passwords unencrypted. If your password is “mySecurePass”, then at the very least, apply a function like md5(”mySecurePass”) and then store the resulting value. For example

      $password = "myPass";
      $md5 = md5("myPass");
      //produces 8e87a82e2ef8273a64ec7f2bf770a178
      //now we can check if a password is valid without needing
      //to store the password in a readable format
      if(md5("myPass") == "8e87a82e2ef8273a64ec7f2bf770a178"){
        echo "The passwords match!";
      }

      It is nearly impossible to determine a particular password from a given md5 string, since md5 is a one way hash function

    • Note though, it’s not entirely unfeasible for someone to use a brute-force attack against a table of md5 passwords to try and guess them. One such attack is called a rainbow table attack, the workings of which are outlined at the bottom of this tutorial.
    • A way to prevent rainbow table attacks and further secure your password data is to use a salt when hashing passwords. This means that every hashed password value in the database is computed using a unique salt value For example, instead of just using md5() on a password, the following would occur

      $password = "myPass";
      $salt = "rJ523FskdA"; //salt is a random string of characters, or a random number
      //it is best to randomly generate the salt every time
      //now, we calculate our hash using this salt, combined with our password to encrypt
      $pass = $salt.md5($salt.$password);
      //giving us rJ523FskdAa1df3d2e465a31f38962c32c705800db
      //note how the salt can be stored publicly (see the first 10 characters of the hash)
    • The following class will allow you to use a salting process on your hash values, and also provides you with a handy method to check that a particular salt/hash value matches a given password

    Feel free to use the following script in your own applications, and to modify it as you see fit. Let me know if it’s been of any use to you!

    /**
    * Password Generation
    *
    * @phpver 5.0
    * @author Robin Metcalfe <robin@solarisedesign.co.uk>
    * @version 1.0
    * @package strongPass
    * Date     :  12th Sep 2008
    * Purpose  :  Creating a strong password and hash value with salt
    */ 
     
    /**
     * A password generation class
     * @package strongPass
     */
    class StrongPass {
     
        //to store the salt we use in hash generation
        private $salt;
        //maximum size of the salt value
        private $salt_size = 30;
     
        //the length of the password to generate
        private $len;
        //to store the algorithm used to encrypt
        private $alg;
        //valid algorithms we are allowing
        private $valid_alg = array("md5", "sha1");
     
        //the results of the hash and password will be stored in these
        //publicly accessible variables
        public $hash;
        public $pass;
     
     
     
        function __construct($length = null, $custom_pass = "", $algorithm = "md5"){
            //initialise values
            $this->hash = "";
            //if the user wants to specify a custom password to hash
     
            $this->pass = ($custom_pass==null)?"":$custom_pass;
            $this->salt = "";
            //check that we're using either md5 or sha1
            $this->alg = (in_array($algorithm, $this->valid_alg))?$algorithm:"md5";
            //if we have specified a length, generate a password
            if($length != null){
                $this->len = $length;
                $this->_makeSalt();
                $this->_generate();
            }
            //if a length hasn't been generated, we can just use this instance
            //of StrongPass to call confirm(); to check if a password/hash combo
            //is valid
        }
     
        /**
         * encrypts a string with the chosed encryption type in $this->alg
         * @access private
         * @param string $string
         * @return string $encrypted
         */
        private function _encrypt($string){
            //call the function defined by the chosen algorithm to
            //encrypt the data e.g. md5(), sha1()
            $encrypted = call_user_func($this->alg, $string);
            return $encrypted;
        }
     
     
        /**
         * Perform a string of reversible operations to encode/decode our
         * salt
         */
        private function _decode($string){
            return base64_decode(strrev(str_rot13($string)));
        }
     
        private function _encode($string){
            return str_rot13(strrev(base64_encode($string)));
        }
     
        /**
         * generates a value for $this->salt
         * @access private
         */
        private function _makeSalt(){
            //generate a random salt of 30 characters using mt_rand
            $this->salt = substr($this->_encrypt(mt_rand()), 0, $this->salt_size);
        }
     
        /**
         * generate a random string of characters of length $length
         * @static
         * @access private
         * @params int $length
         * @return string $pass
         */    
        private static function _constructPassword($length){
            $symbols = "#@%&(=;[]+";
            $chars = "abcdfghjkmnpqrstvwxyz23456789ABCDFGHJKLMNPQRSTVWXYZ";
     
            //generate the password string
            $pass = "";
     
            for($i=0; $i<$length; $i++){
                $rand = rand() % strlen($chars.$symbols);
                $pass .= substr($chars.$symbols, $rand, 1);
            }
            return $pass;
        }
     
        /**
         * Public access for the static function _constructPassword
         * @static
         * @access private
         * @param int $length
         * @return string
         */
        public static function make($length){
            return self::_constructPassword($length);
        }
     
        /*
         * Generates the password/hash combination
         * @access private
         */
        private function _generate(){
            //length must be > 6 and <= 64
            if(!filter_var($this->len, FILTER_VALIDATE_INT, array("min_range" => 6, "max_range" => 64))){
                $this->len = self::default_len;   
            }
     
            $this->pass = ($this->pass=="")?self::_constructPassword($this->len):$this->pass;
     
            $salt = $this->_encode($this->salt);
     
            $this->hash = $salt.$this->_encrypt($salt.$this->pass);
        }    
     
        /**
         * detects the algorithm used to encrypt the hash, md5 or sha1
         * @param string $hash
         * @access private
         */    
        private function _detectAlgorithm($hash){
            //based on the length of the hash, we can tell which algorithm
            //was used to produce it
            $len = strlen($hash);
            if($len == 32){
                $this->alg = "md5";   
            } else if($len == 40){
                $this->alg = "sha1";   
            }
        }
     
        /**
         * function used to determine if $hash == $pass
         * @access private
         * @param string $pass
         * @param string $hash
         * @return bool
         */
        private function _confirmPassword($pass, $hash){
            //create a blank temporary string of length $this->salt_size
            //to check what size our salt should be when base64_decode is called
            $tmp = "";
            for($i=0; $i<$this->salt_size; $i++){
                $tmp.="o";
            }
            //figure out how long our salt should be when encoded
            $encoded_salt_length = strlen($this->_encode($tmp));
     
            //find the salt string used by fetching
            //the first n=$salt_length characters of $hash
            //and decoding with $this->_decode
            $salt = $this->_decode(substr($hash, 0, $encoded_salt_length));
     
            //find the old hash, and the new one generated from our user password       
     
            // 1) Stored hash
            // - using the salt_length, we can return the remainder of the string
            //   which corresponds to our original hash
            $stored_hash = substr($hash, $encoded_salt_length);
     
            //find out if the hash is a md5 hash or an sha1 hash
            $this->_detectAlgorithm($stored_hash);
     
            // 2) New hash, built using the provided user password
            // - The hash has been encrypted using the base64_encoded value of $salt
            //   Here, we check that when we decode $salt and prepend it to our
            //   user supplied password $pass, then encrypt it, it matches
            //   the hash we have stored for this password/user
            $check_hash = $this->_encrypt($this->_encode($salt).$pass);
     
            //so if they match, then the password is correct
            if($check_hash === $stored_hash){
                return true;   
            } else {
                return false;
            }
        }
     
        /**
         * public access method for _confirmPassword()
         * @access public
         * @param string $pass
         * @param string $hash
         * @return string
         */
        public function confirm($pass, $hash){
            return $this->_confirmPassword($pass, $hash);
        }
    }

    Using this Class

    To make use of this class, please follow the following examples

    To create a simple, pseudo-random password string of n-characters in length

    $new_pass = StrongPass::make(16);
    echo $new_pass;

    This would generate something like the following (but does not encrypt or hash the password)

    RVcNPvZmLYD&QNYB

    This function is ideal for creating random strings of characters to suggest as new passwords for new users on a site, for example.

    To create a password/hash combination to securely store in a database

    $pw = new StrongPass(16);
    echo $pw->pass;
    echo $pw->hash;

    This will calculate a new password, and generate a hash for it, giving us (e.g.)

    yGZwq2HLr(cWymc2
    3LwAjDwA4NmAmLJZ5xGZwuGBzOwMjLJBvMwAvM2Z1cdb2bb80f56da020dd2f52bd914dc89

    To create a password/hash combo for a custom password

    //the first value is ignored when we specify a custom password
    $pw = new StrongPass(0, "myPass");
    echo $pw->pass;
    echo $pw->hash;

    Giving us

    myPass
    0NGBvIwLuMGZjVJBlNwZ1ZzA4tmL5RTB2V2LjRwM49b5e26e53f5efb711fce725bc6b542c

    Note, if we run the function again, we’ll get a different hash for the same password!

    myPass
    uIQZkDQZ0VGBzuGBjLmZmVGBmNwZ3VmL0DzL5VQMca4bf28cd9fee997e0b0dede80081cf0

    To check if a password matches a given hash

    $pw = new StrongPass();
     
    //try the first hash value
    if($pw->confirm("myPass", "0NGBvIwLuMGZjVJBlNwZ1ZzA4tmL5RTB2V2LjRwM49b5e26e53f5efb711fce725bc6b542c"))
      echo "Password is correct!";
     
    //and try the second..
    if($pw->confirm("myPass", "uIQZkDQZ0VGBzuGBjLmZmVGBmNwZ3VmL0DzL5VQMca4bf28cd9fee997e0b0dede80081cf0"))
      echo "Password is correct!";

    Which produces

    Password is correct!
    Password is correct!

    Using a different hashing algorithm

    To specify a different algorithm to use, simply apply the third argument, like so

    //creates a hash for "myPass" with md5 encryption
    $pw_md5 = new StrongPass(0, "myPass", "md5");
    //creates a custom 16-digit password/hash encrypted with SHA1
    $pw_sha1 = new StrongPass(16, "", "sha1");

    Rainbow Table Attacks and Prevention

    If you’ve chosen the sensible path of encrypting your passwords using an algorithm like md5() then this means that your password data is stored in an unreadable format in your database. Because md5 is a one-way hashing algorithm, it’s not possible to run any counter-function to calculate a password from any given string.

    Because of this, md5 is a major obstacle to any website hacker. To gain access to a password, they would have to try and guess the password to match the md5 hash, and this could potentially be any series of digits, letters and symbols. Be aware though, some people have the unfortunate habit of choosing simple, word-based passwords, like “apple” perhaps, or the name of a relative or friend, “jamessmith” or “peter”

    A good code of practice when setting passwords for yourself is not to use simple, easily guessable words as a password. The first line of attack for a majority of hackers is to use a simple dictionary attack to guess your passwords. This amounts to running through a list of common dictionary words and names until they find one that matches.

    Because of the speed of modern computers though, trying thousands, or hundreds of thousands of common name/word variations in a given space of time is more than feasible.

    However, if you’ve chosen as your password a random list of characters, e.g. “d4gAv23k”, then this task becomes much more difficult for the would-be hacker. They now have a random element involved, and cannot rely on their dictionary attack to find your password. Using a random 8-digit password, chosen from letters (both upper and lower case) and numbers means that a hacker would have to try guessing from 218,340,105,584,896 different combinations.

    (Based on the assumption that a high-speed modern computer could potentially run 5 million md5() calculations per second, it would take around a year and a half to try all the combinations!)

    Using Precomputed Hash Values to hack passwords

    Given this lengthy time-frame to guess even one 8-digit password, you’d think this may indicate that md5 is more than secure enough for use in common security systems. And in many cases you’d still be right. For the average website, storing your password in md5 hash format (or indeed in the newer SHA1 format) is still a major obstacle for anyone trying to gain access.

    Unfortunately, given the recent advances in computing technology, it’s now possible to use a more advanced version of the dictionary attack, where a hacker will use pre-computed tables containing every single potential hash for any combination of characters. Such tables are absolutely massive though, and can easily take up many gigabytes of space in memory, but this is a prime example of a time-memory tradeoff attack, where the availability of abundant, cheap memory allows hackers to store many possible hash values on a computer, and simply lookup a particular hash in the table to find a password.

    However, the method I’ve outlined in this article, salting, is a way to prevent this form of attack. Salting produces hash values which are unique for each password, and cannot be retrieved from a precomputed hash table. Because each of the password/hash combinations calculated above uses a randomly generated salt value in the process, then the resulting hash will never be the same for identical passwords.

    Conclusion

    Hopefully you’ve learned a thing or two about password generation and secure storage here, and I hope the class I’ve provided you with comes in handy, please do let me know if you find any room for improvement, or if you’ve found it useful in your own scripts.

    Download the files used in this tutorial here.

Leave a Comment

Want to ask a question about anything in this tutorial? Have you spotted an inaccuracy, or noticed areas for improvement? Fancy just having a chat? Leave your comments below...

Recommended Reading from Amazon.com

Previous Tutorial
Pagination : Splitting data across multiple pages with MySQL and PHP