I am currently building an online system, it has come to the point to think about securing peoples passwords. How ever, for admin reasons I was wondering if it was possible to decode the encoded password, I believe this is not possible with md5 but hoping there is another method?

Any help would be geat, also any other information regarding safety, thanks.

Recommended Answers

All 18 Replies

Here is a good PHP5 class that uses the mcrypt library for two way encryption.

<?php

class Encryption
{
    static $cypher = 'blowfish';
    static $mode   = 'cfb';
    static $key    = '1a2s3d4f5g6h';

    public function encrypt($plaintext)
    {
        $td = mcrypt_module_open(self::$cypher, '', self::$mode, '');
        $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
        mcrypt_generic_init($td, self::$key, $iv);
        $crypttext = mcrypt_generic($td, $plaintext);
        mcrypt_generic_deinit($td);
        return $iv.$crypttext;
    }

    public function decrypt($crypttext)
    {
        $plaintext = "";
        $td        = mcrypt_module_open(self::$cypher, '', self::$mode, '');
        $ivsize    = mcrypt_enc_get_iv_size($td);
        $iv        = substr($crypttext, 0, $ivsize);
        $crypttext = substr($crypttext, $ivsize);
        if ($iv)
        {
            mcrypt_generic_init($td, self::$key, $iv);
            $plaintext = mdecrypt_generic($td, $crypttext);
        }
        return $plaintext;
    }
}

// Encrypt text
$encrypted_text = Encryption::encrypt('this text is unencrypted');

// Decrypt text
$decrypted_text = Encryption::decrypt($encrypted_text);


?>

Thanks alot for your help, however, I am using 4.3.9, sorry I should have mentioned this to begin with, the code you gave strictly php5?

It can be changed to work with PHP 4. You just need to change the PHP 5 features to 4:

<?php

class Encryption
{
    var $cypher = 'blowfish';
    var $mode   = 'cfb';
    var $key    = '1a2s3d4f5g6h';

    function Encryption()
    {
        // do nothing
    }

    function encrypt($plaintext)
    {
        $td = mcrypt_module_open($this->cypher, '', $this->mode, '');
        $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
        mcrypt_generic_init($td, $this->key, $iv);
        $crypttext = mcrypt_generic($td, $plaintext);
        mcrypt_generic_deinit($td);
        return $iv.$crypttext;
    }

    function decrypt($crypttext)
    {
        $plaintext = "";

        $td        = mcrypt_module_open($this->cypher, '', $this->mode, '');
        $ivsize    = mcrypt_enc_get_iv_size($td);
        $iv        = substr($crypttext, 0, $ivsize);
        $crypttext = substr($crypttext, $ivsize);
        if ($iv)
        {
            mcrypt_generic_init($td, $this->key, $iv);
            $plaintext = mdecrypt_generic($td, $crypttext);
        }
        return $plaintext;
    }
}

?>

can this store, say for instance, a PayPal token that I am supposed to keep hidden?

You can do it at the database level as well if you want.

For INSERT

$aes_key = "EF77FHH7-E6G1-31y4-w2D7-G4gH8HWF20H1";
$sql = "INSERT INTO user(username, pass) VALUES ('bob', AES_ENCRYPT('password', '$aes_key' ))";

And for SELECT

$aes_key = "EF77FHH7-E6G1-31y4-w2D7-G4gH8HWF20H1";
$sql = "SELECT *, AES_DECRYPT(password, '$aes_key ') AS password FROM  user";

You'll have to keep the AES key as a config value or something. If you lose it you can't decrypt the data :(

More references here.

Hey.

Be careful if you do this in a SQL query tho. Some MySQL servers use plain-text query logs, so while your passwords might be encrypted in the database itself, they would be stored in their original form in the logs.

See these two pages in the manual for details on that.

@Atli
Good point. I didn't know this. This can be a problem if your MySQL server is not controlled by you alone.

I am currently building an online system, it has come to the point to think about securing peoples passwords. How ever, for admin reasons I was wondering if it was possible to decode the encoded password, I believe this is not possible with md5 but hoping there is another method?

Any help would be geat, also any other information regarding safety, thanks.

There really is no reason to use 2 way encryption on passwords. Retrieving the password is not the concern, gaining access to their account is. So if the user forgets their password, send them a token through email to set a new password.

Use secure hashes to store the passwords. Add a long salt before hashing, and hash that password and salt together 100,000 times or so. Make sure you use quite a bit of memory in the process.

@digital-ether
I agree with you 100%, although 100.000 iterations seem a bit excessive to me. (But that's just me :-P)
However, I got to ask why you specifically mention high memory usage?

I think she recommended high memory usage so that the effort needed to try to recreate/hack any of the passwords would be excessive and not worth it. But if you use up that much processing/memory, wouldn't you make it untenable to be used within a login/registering system? For even a reasonable amount of requests even.

@digital-ether
I agree with you 100%, although 100.000 iterations seem a bit excessive to me. (But that's just me :-P)
However, I got to ask why you specifically mention high memory usage?

You can save the number of iterations used on the hash. That way you can start with just a few, say 1000, then add more if needed. Computation speed is always increasing so to keep the password safe in a few years time, you may need more iterations.

100.000 iterations of SHA1 for instance only takes 300ms on a Core2 Duo 2.83Ghz.
On an production (under heavy use) 8 core Xeon 2.27 Ghz it took 500 ms.

So it looks like 100.000 is too high. (I just threw it in there). Maybe around 10.000 iterations. (around 50 ms to do).

Usually, the logins are not a bottleneck in your application.

See: http://chargen.matasano.com/chargen/2007/9/7/enough-with-the-rainbow-tables-what-you-need-to-know-about-s.html
under "adaptive hashing"

I can't find any implementations of this in PHP. Though what is described in that article (BCrypt) uses Blowfish which PHP 5.3 has a native implementation. http://php.net/manual/en/function.crypt.php
So you could do something equivalent.

I think she recommended high memory usage so that the effort needed to try to recreate/hack any of the passwords would be excessive and not worth it. But if you use up that much processing/memory, wouldn't you make it untenable to be used within a login/registering system? For even a reasonable amount of requests even.

Yes, exactly. You can tune the memory usage also. This is to make distributed attacks less effective since there is a limited amount of memory, even for 1000 parallel machines.

Thought I'd write an example:

note: for PHP4 just remove the "public static" in front of each function declaration.

The class:

/**
 * Generate cryptographic Hashes for passwords 
 * 
 * Features: 
 * 	Harderned against precomputation attacks like rainbow tables (using salt)
 * 	Harderned against brute force and dictionary attacks (using key stretching, large memory usage and an optional secret key)
 * 
 *  http://en.wikipedia.org/wiki/Password_cracking
 *  
 *  Note: for PHP4 and lower, just remove the "public static" before function declaration
 * 
 * @author gabe@fijiwebdesign.com
 * @link http://www.fijiwebdesign.com/
 * @version $Id$
 */
class Password_Hash 
{

	/**
	 * Generate the Hash
	 * @return String
	 * @param $password String
	 * @param $salt String[optional]
	 * @param $iterations Int[optional]
	 * @param $secret String[optional]
	 */
	public static function generate($password, $salt = null, $iterations = 10000, $hash_function = 'sha1', $secret = '') 
	{
		$salt or $salt = self::generateToken();
		$hashes = array();
		$hash = $password;
		// stores a sequence of 10000 unique hashes in memory
		$i = $iterations;
		while(--$i) 
		{ 
			$hash = $hashes[] = $hash_function($hash.$salt.$secret);
		}
		// hash the 10000 unique hashes into a single hash
		$hash = $hash_function(implode('', $hashes).$salt.$secret);
		return implode(':', array($hash, $iterations, $hash_function, $salt));
	}
	
	/**
	 * Verify a password meets a hash
	 * @return Bool
	 * @param $password String
	 * @param $hash String
	 * @param $secret String[optional]
	 */
	public static function verify($password, $hash, $secret = '')
	{
		list($_hash, $iterations, $hash_function, $salt) = explode(':', $hash);
		return ($hash == self::generate($password, $salt, $iterations, $hash_function, $secret));
	}
	
	/**
	 * Generate a random hex based token
	 * @return String
	 * @param $length Int[optional]
	 */
	public static function generateToken($length = 40) 
	{
		$token = array();
		for( $i = 0; $i < $length; ++$i ) 
		{
			$token[] =	dechex( mt_rand(0, 15) );
		}
		return implode('', $token);
	}
	
}

Example using SHA1 with default of 10000 iterations.

// generating the hash
$password = 'test';
$hash = Password_Hash::generate($password);

// verifying a password
$result = Password_Hash::verify($password, $hash);

// dump results
var_dump($hash, $result);

Example using whirlpool as the hash function, a 128 length salt as well as a secret.

// define our custom hash function
function whirlpool($str) {
	return hash('whirlpool', $str);
}

$password = 'test';
$salt = password_Hash::generateToken(128);
$secret = password_Hash::generateToken(128);
$iterations = 10000;

// generate hash
$hash = Password_Hash::generate($password, $salt, $iterations, 'whirlpool', $secret);

// verify
$result = Password_Hash::verify($password, $hash, $secret);

// dump results
var_dump($result);

Thanks for that.
I see what you mean now, about the memory.

Interesting article to. I had never really considered using bcrypt before, although after doing some testing, and considering your memory point, I'm inclined to keep using the hashing algorithms.

Your code looks pretty awesome to. Very flexible :-)
Looks like it could easily starve you for memory if you aren't careful. (Which is what you were aiming for, I assume.)

I'm trying out a system where I store details on the hash inside the database, everything from which algorithm to use to how much RAM it should be using. I use a similar method you used, to make sure the RAM suffers during the process.

Been working fine so far:

/**
 * Handles member security.
 *
 * Assumes a DB table named `member` structured like so:
 *   CREATE TABLE `member` (
 *     `id` int Unsigned NOT NULL Auto_Increment Primary Key,
 *     `name` VarChar(64) Not Null Unique,
 *     `pw_salt` char(32) NOT NULL,
 *     `pw_memory` int(10) unsigned DEFAULT 10485760,
 *     `pw_algo` varchar(32) DEFAULT 'sha512',
 *     `password` char(128) NOT NULL
 *   ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 *
 * Static Methods:
 *  generate_hash - Generates a has based on the input and the user's pw_ details.
 *  validate_password - Validates the given password against the member password.
 *
 */
class MemberSecurity
{
    /**
     * Generates a hash based on the user-specific salt and the passed data.
     * @param string $input The keyword (password) to hash.
     * @param uint $userID The ID of the user, who's salt will be used.
     * @return string The hash. Variable length, based on the algorithm used.
     * @throws Exception
     */
    public static function generate_hash($input, $userID)
    {
        try
        {
            // Fetch and initialize data.
            $sql = "SELECT `pw_salt`, `pw_memory`, `pw_algo` FROM `member` WHERE `id` = {$userID}";
            $resultSet = Database::Get()->query($sql);
            $resultRow = $resultSet->fetch_assoc();

            $salt = $resultRow['pw_salt'];
            $algo = $resultRow['pw_algo'];
            $memorySize = $resultRow['pw_memory'];
            $hash = $input . $salt;

            // In case of a normal hash algorithm.
            if(in_array($algo, hash_algos()))
            {
                // Set up a memory buffer and fill it with hashes created using
                // the input and the salt, then hash the whole thing as the output.
                // Should eat up loads of memory and CPU time.
                $buffer = "";
                while(strlen($buffer) < $memorySize)
                {
                    $buffer .= hash($algo, $input . $salt);
                }
                $hash = hash($algo, $buffer);
                $buffer = null;
            }

            // Fall back to bcrypt, if possible.
            // No memory limit. (The memory field will be used as the key length)
            // This eats up much more CPU time than memory.
            else if($algo == 'bcrypt' && CRYPT_BLOWFISH == 1)
            {
                $keyLength = ($memorySize < 10 ? "0".$memorySize : $memorySize);
                $hash = substr(crypt($input, '$2a$'. $keyLength .'$'. $salt . '$'), 28);
            }

            // Algorithm not available. Bail out!
            else
            {
                throw new Exception("The hashing algorithm '$algo' is not available.", 501);
            }
            
            return $hash;
        }
        catch(DatabaseException $ex)
        {
            throw new Exception("Failed to fetch user data.", 304, $ex);
        }
    }

    /**
     * Validates the password against the one stored in the user database.
     * @param string $password The password to validate.
     * @param string $userName The name of the member.
     * @return bool
     * @throws Exception
     */
    public static function validate_password($password, $userName)
    {
        try
        {
            $sql = "SELECT `id`, `password` FROM `member` WHERE `name` = '{$userName}'";
            $resultSet = Database::Get()->query($sql);
            if($resultSet && $resultSet->num_rows == 1)
            {
                $resultRow = $resultSet->fetch_assoc();
                $generatedHash = self::generate_hash($password, $resultRow['id']);
                
                return ($resultRow['password'] == $generatedHash);
            }
            else
            {
                throw new DatabaseException("Failed to fetch user data.", 500, $sql, Database::Get()->error);
                return false;
            }

        }
        catch(DatabaseException $ex)
        {
            throw new Exception("Failed to fetch user data.", 500, $ex);
        }
    }
}

I use my own database wrapper classes in there, but I trust you can read around them ;-)

Using the following, validating a single password uses more than 10Mb memory and takes just under a second.
(Using Core Duo 3.16GHz CPU and 1333Mhz DDR3 RAM)

mysql> SELECT * FROM member\G
*************************** 1. row ***************************
       id: 1
     name: User 1
  pw_salt: 0e20d1dc3c6e60225597be
pw_memory: 10485760
  pw_algo: sha512
 password: 09254dc706eb1acb5420adcbc97d67a0992fb3530e8c844c4c3a930614d447f371f2f0a2af369dd1c73452819d3aac30bdeea7f889b11f4cf038435c26d5036e

Looks promising... I think :-)

Looks great. I modified the class I wrote a bit after looking at your code.

Just a few suggestions:

You could optimize the string concatenation.

$buffer .= hash($algo, $input . $salt);

A lot of the execution time is the concatenation of the $buffer string.

If you use an array however, it becomes negligible. I believe the reason is the whole string has to be copied/destroyed in memory each time you append to it. Arrays are mutable so appending would not create a copy.

You could calculate the number of iterations you'll need by dividing the memory usage (in bytes) by the length of the hash. That way you also don't have to re-check the length of the string.

Thanks.

You could optimize the string concatenation.

I did use an array at first, adding each hash as an element and them imploding it before creating the final hash. (Like you do in your code.)

However, after testing that I found that this method uses double the amount of memory the string concatenation method uses.
Makes sense when you think about it. Both the array and the string will have to exist in memory at the same time when the implode function is called.

After testing this a bit more, I've decided to just use half the array size. The array takes up half the memory, and the string the implode creates uses the rest. Seems to be most efficient.

$buffer = array();
$hash = hash($algo, $input . $salt);
for($y = floor($memorySize / strlen($hash) / 2); $y > 0; --$y)
{
        $buffer[] = hash($algo, $y . $hash . $salt);
}
$hash = hash($algo, implode($buffer));

This takes only half the time my previous code did :-)

This also fixed another problem in my previous code.
The loop that created the memory buffer in my previous code just hashed and added the same hash over and over. It could have been optimized to practically nothing by hashing it once outside the loop and adding that each loop, rather than recreating the hash each time.
By adding the loop index to the string that gets hashed, I now get a unique hash added to the buffer each loop.

You could calculate the number of iterations you'll need by dividing the memory usage (in bytes) by the length of the hash. That way you also don't have to re-check the length of the string.

Good point, thanks.

Thanks.


I did use an array at first, adding each hash as an element and them imploding it before creating the final hash. (Like you do in your code.)

However, after testing that I found that this method uses double the amount of memory the string concatenation method uses.
Makes sense when you think about it. Both the array and the string will have to exist in memory at the same time when the implode function is called.

After testing this a bit more, I've decided to just use half the array size. The array takes up half the memory, and the string the implode creates uses the rest. Seems to be most efficient.

$buffer = array();
$hash = hash($algo, $input . $salt);
for($y = floor($memorySize / strlen($hash) / 2); $y > 0; --$y)
{
        $buffer[] = hash($algo, $y . $hash . $salt);
}
$hash = hash($algo, implode($buffer));

This takes only half the time my previous code did :-)

This also fixed another problem in my previous code.
The loop that created the memory buffer in my previous code just hashed and added the same hash over and over. It could have been optimized to practically nothing by hashing it once outside the loop and adding that each loop, rather than recreating the hash each time.
By adding the loop index to the string that gets hashed, I now get a unique hash added to the buffer each loop.


Good point, thanks.

After thinking about this again, I realized I made an assumption that is very incorrect. I assumed hash functions require the whole input in order to produce the hash.

But it appears the hash is generated by reading the input in sequence. That way it does not require storing the whole input in memory.
A bit of searching online seemed to prove it: http://www.partow.net/programming/hashfunctions/#HashingMethodologies

And a test case:

// generate a hash for 2MB file
$hash = sha1_file('hash.txt');
echo 'memory peak use: '.floor(memory_get_peak_usage(true)/1000).' Kb';

The result was only 524 Kb memory allocated for the PHP process even though the file was 2MB which shows that the file was read sequentially.

For example with PHP, sha1_file() supports the stream wrappers. So you could just write each hash generated to a stream/pipe that sha1_file() would read as the input. A unidirectional stream is not kept in memory as a whole, only each packet is read into memory and then dropped.

So I guess the memory consumption bit has to be rewritten some other way.

But neither of our codes need the hash functions themselves to use a lot of memory. They both use arrays and strings we created to fill the memory.
I mean, using my last code, setting the $memorySize variable to 10Mb, the peak usage of the script goes to around 10.5Mb.

Or am I not getting your point?

P.S. floor(memory_get_peak_usage(true)/1000) 1Kb = 1024b ;-)

But neither of our codes need the hash functions themselves to use a lot of memory. They both use arrays and strings we created to fill the memory.
I mean, using my last code, setting the $memorySize variable to 10Mb, the peak usage of the script goes to around 10.5Mb.

Or am I not getting your point?

P.S. floor(memory_get_peak_usage(true)/1000) 1Kb = 1024b ;-)

The whole array or string does not need to be kept in memory, in order to generate the hash. The hash can be generated by writing the input in 1 byte at a time.

Example:

// sha1_stream.php
echo sha1_file('php://stdin');

Saved as file sha1_stream.php the above would read the input from STDIN and write the hash to STDOUT.

Then I used proc_open() to write the input to it bit by bit. So it never stays in memory.

$salt = 'e5cbe45c71a0b805278f2b5f94eb108dba532af3';
$hash = 'test';

$descriptorspec = array(
   0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
   1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
   2 => array("file", "error-output.txt", "a") // stderr is a file to write to
);

$cwd = './';
$env = array();
$options = array('bypass_shell');
$php_path = 'c:\xampp\php\php.exe'; // change to php path
$file_path = dirname(__FILE__).'/sha1_stream.php';
$process = proc_open("$php_path $file_path", $descriptorspec, $pipes, $cwd, $env, $options);
if (is_resource($process)) {
    $i = 1000;
	while(--$i) 
	{ 
		// instead of saving the hashes in memory, we write each hash to the other PHP process which computes the overall hash for the whole string. 
		fwrite($pipes[0], sha1($hash.$salt));
	}
    fclose($pipes[0]);
	
    $hash = stream_get_contents($pipes[1]);

    fclose($pipes[1]);
    $return_value = proc_close($process);
} else {
	throw new Exception('Could not create process. see: error-output.txt');
}
echo $hash;
echo '<br />';

With 100.000 iterations the full process still takes only about 1.12secs and memory usage of both processes was around 700Kb.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.