DIY Hashing (if you must)

Every so often, I come across more and more clever schemes implemented by programmers wanting to one-up on what’s already available in their favorite software libraries. My response is DON’T DO IT! For one very simple reason: Crypto is hard. Good crypto is harder. PHP developers in particular are notorious for this, but I’ve seen C# devs do the same.

Even if you come up with a clever hashing scheme of your own, you don’t really want to play around next to something that’s tried and proven, like say bcrypt. Plus there are innumerable instances where very, very smart people have gone out and created their own schemes, only to be defeated by an attack a short while later. It happens. That’s reality.

Consider password hashing

You shouldn’t use anything other than bcrypt. There I said it. There are many examples on the web on how to use bcrypt, but just in case you’re wondering, here are couple of functions :

/**
 * Hash password with blowfish + salt
 *
 * @param string $pass Raw password
 * @param string $salt Stored salt stored in the db or generated
 */
public function password( $pass, $salt ) {
	if ( empty( $pass ) || empty( $salt ) ) {
		return false;
	}
	return crypt( $pass, '$2y$09$'. $salt . '$' );
}

/**
 * Verify password hash against the stored value
 *
 * @param string $pass Raw password
 * @param string $salt Password salt as stored in the database
 * @param string $storedPass Hashed password to match against
 */
public function verifyPassword( $pass, $salt, $storedPass ) {
	if ( empty( $pass ) || empty( $salt ) || empty( $storedPass ) ) {
		return false;
	}
	$pass = $this->password( $pass, $salt );

	if ( $storedPass === $pass ) {
		return true;
	}
	return false;
}

These are very simple and are fairly self-explanatory. You’re seeing the PHP “crypt” function being used here to create a blowfish hash with a cost of 9 (this is the work factor used to calculate the hash; the higher it is, the longer it takes to hash, thus more expensive to brute-force). The verifyPassword function does the hash again with the salt (even though bcrypt has built in salting, I prefer adding a randomly generated one of my own for more entropy) to see if it matches the stored hash.

Now the DIY version

Simply re-hashing a bunch of times isn’t usually good enough, especially if it’s just md5. I’ve lost count of how many times I’ve seen that. There are well known software packages out there that still insist on putting md5 in a loop 1000x or more, but realistically, these are fairly easy to crack these days with the proliferation of fast GPUs.

If for some reason, you still want to do your own hashing (as mentioned above, I don’t recommend it), I’ve written an alternative you can use :

/**
 * DIY hash, best to use this for something non-critical
 */
public function saltedHash( $str, $salt = NULL ) {
	if ( NULL === $salt ) {
		$salt = bin2hex( $this->IV( 30 ) );
	}
	
	for ( $i = 0; $i < 5000; $i++ ) {
		$str	= hash( 'ripemd320', $str );
		$arr	= str_split( $str, 8 );
		$j	= $i;
		
		foreach( $arr as &$a ) {
			$salt	= strrev( $salt ) . $j;
			$str	= hash( 'ripemd160', strrev( $a ) . $salt );
			$j++;
		}	
	}

	return $salt . 'x' . $str ;
}

/**
 * Verify DIY hash
 */
public function matchSaltedHash( $stored, $sent ) {
	if ( empty( $sent ) || empty( $stored ) ) {
		return false;
	}
	
	$pkg = explode( 'x', $stored );
	if ( 1 <= count( $pkg ) ) {
		return false;
	}
	
	if( $pkg[1] === $this->saltedHash( $sent, $pkg[0] ) ) {
		return true;
	}
	
	return false;
}

/**
 * @returns mixed Binary IV
 */
public function IV( $size ) {
	return mcrypt_create_iv( $size, MCRYPT_DEV_URANDOM );
}

This function is expensive enough to be not a simple re-iteration that can create hashes which are easily cracked on a modern GPU in short order. Because the hash is also salted, it should be a bit more difficult to simply run reiterations. It won’t make it uncrackable, but certainly more secure than simply running md5 a thousand times.

If you’re interested in a class for cryptographic purposes (encryption/decryption, password hashing/generation etc…) I’ve created a complete example. Note: I’m using a defined constant called SALT_KEY, which is basically an application specific random string. You can create your one of your own via the WordPress salt generator (do NOT share the same salt among multiple applications).

I’ve already published much of this code previously, but this version includes improvements.

<?php

/**
 * Encryption/Decryption, Encoding/Decoding and password related functions.
 * 
 * The license below was inherited from the project this originally went into.
 * If you need a different license for some reason, just email me.
 * 
 * @author Eksith Rodrigo <reksith at gmail.com>
 * @license http://opensource.org/licenses/ISC ISC License
 */

class Crypto {
	
	/**#@+
	 * Instance getter, constructor and destructor
	 */
	
	private static $_instance;
	public static function getInstance() {
		if ( !self::$_instance instanceof self ) {
			self::$_instance = new self();
		}
		return self::$_instance;
	}
	
	private function __construct() {}
	
	/**#@-*/
	
	
	
	/**#@+
	 * Password related functions
	 */
	
	
	/**
	 * Hash password with blowfish + salt
	 *
	 * @param string $pass Raw password
	 * @param string $decodedSalt Stored salt after being decoded or freshly generated
	 */
	public function password( $pass, $decodedSalt ) {
		// This will reject NULL, 0, FALSE etc... but these are terrible passwords anyway
		if ( empty( $pass ) || empty( $decodedSalt ) ) {
			return false;
		}
		
		return crypt( $pass, '$2y$09$'. $decodedSalt . '$' );
	}
	
	
	/**
	 * Verify password hash against the stored value
	 * 
	 * @param string $pass Raw password
	 * @param string $encodedSalt Password salt as stored in the database
	 * @param string $storedPass Hashed password to match against 
	 */
	public function verifyPassword( $pass, $encodedSalt, $storedPass ) {
		if ( empty( $pass ) || empty( $encodedSalt ) || empty( $storedPass ) ) {
			return false;
		}
		
		$salt = $this->decode( $encodedSalt, SALT_KEY );
		$pass = $this->password( $pass, $salt );
		
		if ( $storedPass === $pass ) {
			return true;
		}
		return false;
	}
	
	
	/**
	 * Hashes the passed password and creates a matching salt (also encoded)
	 * This function does NOT create a new password.
	 * 
	 * @param string $pass User provided password
	 * @param string $salt Generated salt (encoded using the password)
	 */
	public function pairPasswordSalt( &$pass, &$salt ) {
		if ( empty( $pass ) ) {
			return false;
		}
		
		$salt = bin2hex( $this->IV( 30 ) ); 
		$pass = $this->password( $pass, $salt );
		
		$salt = $this->encode( $salt, SALT_KEY );
		
		return true;
	}
	
	
	/**
	 * Random password generator
	 * 
	 * @param int $len Password length
	 * @return string Random password with commonly confused characters removed
	 */
	public function randomPassword( $len ) {
		// Extra padding to accomodate removed chars
		$r = $this->randomStr( $len + 15 );
		
		// Remove confusing chars
		$r = str_replace( array( '1', 'i', 'l', '0', 'o', '5', 's' ), '', $r );
		
		return substr( $r, 0, $len );
	}
	
	/**#@-*/

	
	
	/**#@+
	 * Random number/string generators
	 */
	
	/**
	 * Generates a random string of a given length (or random between 5 and 10 if length is empty)
	 * 
	 * @param int $len Random string length. If empty, will revert to a number between 10 - 20
	 * @return string Alphanumeric string
	 */
	public function randomStr( $len = 0 ) {
		if ( empty( $len ) ) {
			$len = $this->_rnd( 10, 20 );
		}
		
		$r = base64_encode( $this->IV( 30 ) );
		$r = str_replace( array( '+', '/', '-', '_', '=' ), '', $r );
		if ( strlen( $r ) > $len ) {
			return substr( $r, 0, $len );
		}
		return $r;
	}
	
	
	/**
	 * Generates a random number
	 * 
	 * @param int $min Minimum number size. Defaults to 1
	 * @param int $max Maximum number size. Defaults to maximum allowed in 32 bit PHP
	 */
	public function randomInt( $min = 1, $max = 0x7FFFFFFF ) {
		$d = $max - $min;
		if ( $d < 0 || $d > 0x7FFFFFFF ) {
			$d = 1;
		}
		
		$r = $this->IV( 4 );
		$p = unpack( "Nint", $r );
		$f = ( float ) ( $p['int'] & 0x7FFFFFFF ) / 2147483647.0;
		
		return  round( $f * $d ) + $min;
	}
	
	/**#@-*/
	
	
	
	/**
	 * Encrypt and serialize data with a given key
	 * 
	 * @param mixed $data Data to encrypt (assumes strings are UTF-8)
	 * @param string $key Encryption key
	 * @return string Serialized and encrypted
	 */
	public function cryptSerialize( $data, $key ) {
		if ( empty( $data ) ) {
			return '';
		} else {
			$data = json_encode( $data );
			return $this->encryption( $data, $key, 'encrypt' );
		}
	}
	
	
	/**
	 * Deserialize and decrypt data
	 * This is safer than PHP's 'deserialize' especially with cookie data
	 *
	 * @param string $data Data to decrypt (this should be completely unaltered)
	 * @param string $key Decryption key
	 * @return mixed Orignally serialized object
	 */
	public function cryptDeserialize( $data, $key ) {
		if ( empty( $data ) ) {
			return '';
		} else {
			$data = $this->encryption( $data, $key, 'decrypt' );
			if ( empty( $data ) ) {
				return '';
			} else {
				try {
					return json_decode( $data );
				} catch ( Exception $e ) {
					return '';
				}
			}
		}
	}
	

	/**
	 * Encode ( so called because this is only ECB ) in Rijndael 256 with Base64 encoding
	 *
	 * @param string $data The content to be encoded
	 * @param string $salt Required nonce
	 * @return string Encoded and optionally base64 encoded string
	 */
	public function encode( $data, $salt ) {
		$enc = mcrypt_encrypt( MCRYPT_RIJNDAEL_256, $salt, $data, MCRYPT_MODE_ECB, '' );
		return base64_encode( $enc );
	}
	
	
	/**
	 * Decode as encoded above 
	 *
	 * @param string $data The content to be decoded
	 * @param string $salt Required matching nonce to the encrypted data
	 * @return string Decoded content
	 */
	public function decode( $data, $salt ) {
		$data = base64_decode( $data );
		$dec = mcrypt_decrypt( MCRYPT_RIJNDAEL_256, $salt, $data, MCRYPT_MODE_ECB, '' );
		
		// Padding is automatic, but unpad is not
		return $this->_unpad( $dec, 32 );
	}
	

	/**
	 * Generates a random IV for encryption and password/string generation
	 * 
	 * @returns mixed Binary IV
	 */
	public function IV( $size ) {
		return mcrypt_create_iv( $size, MCRYPT_DEV_URANDOM );
	}
	
	
	/**
	 * Decode as encoded above in Rijndael 256 with optional Base64 decoding 
	 *
	 * @param string $data The content to be decoded.
	 * @return string Decoded content
	 */
	public function encryption( $str, $key, $mode = 'encrypt', $time = null ) {
		// Open Rijndael-256 in CBC mode
		$td = mcrypt_module_open('rijndael-256', '', 'cbc', '');
		
		// Find the IV size for the mcrypt module
		$size = mcrypt_enc_get_iv_size( $td );
		
		// Block sizes are needed to calculate the padding ( further down )
		$bsize = mcrypt_enc_get_block_size( $td );
		
		// Key size
		$ksize = mcrypt_enc_get_key_size( $td );
		
		// Hashed key ( for consistency )
		$hkey = hash( 'ripemd160', $key );
		
		// Hash the key for consistency and use only the key size needed from the front
		$key = substr( $hkey, 0, $ksize );
		
		// Salt for encoding/decoding is extracted from the back of the key hashed again
		$salt = substr(  hash( 'ripemd160', $hkey ), ( -1 * $ksize ) );
		
		if ( $mode == 'encrypt' ) {
			/// We're encrypting. Create a new initialization vector
			$iv = $this->IV( $size );
		} else {
			// Unwrap the combined IV and data packet and extract the IV from the front (using IV size)
			$str = $this->decode( $str, $salt );
			$iv = mb_substr( $str, 0, $size );
			
			// Isolate the data by removing the IV altogether and decode in preparation for decryption
			$str = mb_substr( $str, mb_strlen( $iv ) );
			$str = base64_decode( $str );
		}
		
		// Initialize mcrypt
		mcrypt_generic_init( $td, $key, $iv );
		if ( $mode == 'encrypt' ) {
			// Prepare string by padding to match block size and encrypt
			$str = $this->_pad( $str, $bsize );
			$enc = mcrypt_generic( $td, $str );
			
			// Add the IV to the front and Encode
			$out = $this->encode( $iv . base64_encode( $enc ), $salt );
		} else {
			// Decrypt the data ( we removed the IV and decoded above ) and remove padding
			$str = mdecrypt_generic( $td, $str );
			$out = $this->_unpad( $str, $bsize );
		}
		
		// Clean up
		mcrypt_generic_deinit( $td );
		mcrypt_module_close( $td );
		
		// Return encrypted/decrypted string
		return $out;
	}
	
	
	/**#@+
	 * Helpers
	 */
		
	/**
	 * DIY hash, best to use this for something non-critical
	 */
	public function saltedHash( $str, $salt = NULL ) {
		if ( NULL === $salt ) {
			$salt = bin2hex( $this->IV( 30 ) );
		}
		
		for ( $i = 0; $i < 5000; $i++ ) {
			$str	= hash( 'ripemd320', $str );
			$arr	= str_split( $str, 8 );
			$j	= $i;
			
			foreach( $arr as &$a ) {
				$salt	= strrev( $salt ) . $j;
				$str	= hash( 'ripemd160', strrev( $a ) . $salt );
				$j++;
			}
			
		}
		
		return $salt . 'x' . $str ;
	}
	
	/**
	 * Verify DIY hash
	 */
	public function matchSaltedHash( $stored, $sent ) {
		if ( empty( $sent ) || empty( $stored ) ) {
			return false;
		}
		
		$pkg = explode( 'x', $stored );
		if ( 1 <= count( $pkg ) ) {
			return false;
		}
		
		if( $pkg[1] === $this->saltedHash( $sent, $pkg[0] ) ) {
			return true;
		}
		
		return false;
	}
	
	
	/**
	 * Workaround for mt_rand abnormalities
	 */
	public function _rnd( $min, $max ) {
		$r = 0;
		if ( $min > $max ) {
			$min ^= $max;
			$max ^= $min;
			$min ^= $max;
		}
		while( 0 === $r || $r < $min || $r > $max ) {
			$r = mt_rand( $min, $max );
		}
		
		return $r;
	}
	
	
	/**
	 * Pad data to encryption block size.
	 * Michael Corleone says hello.
	 */
	private function _pad( $str, $bsize ) {
		// Find the pad size for this block size and string length
		$pad = $bsize - ( mb_strlen( $str ) % $bsize );
		
		// Repeat the equivalent character up to the pad size
		$str .= str_repeat( chr( $pad ), $pad );
		
		return $str;
	}
	
	
	/**
	 * Remove extra padding added during encryption.
	 * This is a bit of a hack, so if you have improvements, please add them [ and let me know :) ].
	 * Thanks!
	 */
	private function _unpad( $str, $bsize ) {
		$len = mb_strlen( $str );
		
		// Find the pad character ( last one )
		$pad = ord( $str[$len - 1] );
		
		// If padding would have been applied to the string...
		if ($pad && $pad < $bsize) {
			// ...find the pad
			$pm = preg_match( '/' . chr( $pad ) . '{' . $pad . '}$/', $str );
			
			// Pad found, strip it.
			if( $pm ) {
				return mb_substr($str, 0, $len - $pad);
			}
		}
		return $str;
	}
		
	/**#@-*/
}
Advertisement

Banks still don’t get online security

I was changing my Chase account password online when I came across this mess.

Change password page

And establishment IT strikes again.

You idiots still don’t get it

I can understand this limitation for usernames, but passwords too? Clearly, Chase thinks it knows better than people who want to take a proactive approach to their passwords.

Between bunny123, MonkeyWrench5875 and cNF7k=RsF$M4p which do you think is the more secure password? Hint: The one not likely to show up pre-hashed in a Rainbow Table.

This forced dumbing down is just because whoever wrote it, clearly didn’t bother with or wasn’t able to put it proper filtering to make sure there’s no SQL injection taking place.

I don’t believe there should be any limits up to maybe 600 characters since hashing is universal. I should be able to write a password in Kanji or Sanskrit if I so please and maybe even an entire sentence with numbers. Why in the name of all that is holy would you be this lazy with something simple like a password?

Is it any wonder that financial institutions are being targetted by hackers as ruthlessly as they are? They know the banking world is full of these. If you’re trying to advise customers with security procedures, why would you put in this glaring shorticoming? Whoever came up with this limiation should be fired on the spot. If your banking software is as archane as your security, then what else is hiding in there?

If you want to create passwords that are impossible to guess, yet easy for you to remember, try out my password generator. You can take a phrase like “Scooby Doo Where are you” and turn it into a password like s19D4@w23a1$y25 with all the bells and whistles turned on. Make passwords out of lines from out-of-print books, some secret shared with a loved one or even a phrase in another language transliterated into English (my favorite technique).

Of course, no secure password in the world will help you if the institutions you depend on aren’t willing to accept them for stupid technical reasons.

Ultra-secure passwords part II

This is mostly a followup to my original post on easily generating passwords from a mnemonic. It was prompted by a comment by Francesco Sullo advocating against the method, saying “If the attacker is a cryptoanalyst and he catch two of your generated “super secure” passwords (for example, because you login into his websites) he can easily discover your method in minutes.

The premise is false, and here’s why…

For an attacker to decipher the password, I either have to be using commonly known phrases or sentences from well known books (which I don’t) or the attacker needs to scan every book in print and out of print, which is quite unlikely. Mind you the text can even be a phrase, as demonstrated in my reply to him, which no attacker would ever hope of guessing unless it was made public at some point.

As a side note, I created a quick little utility that does the hard part. There’s ample room for improvement for sure, but it gets the point across that book cipher cryptography is only weak when the source is known.

PassPack on the browser end

So while the fallout from the last time I came across this company clears up, I decided to find out exactly how they do what they do. At the time of this writing, PassPack is at Beta5.39.5.

First and foremost… My old nemesis, multiple stylesheets for multiple browsers.

Whenever I see :

<!--[if gte IE 7]>

That’s usually the sign of a poor UI implementation. That means they are jumping through hoops when they really shouldn’t. That excerpt above means that they have a separate stylesheet for Internet Explorer 7. There’s one more for IE6, and it appears they have been through several versions of each stylesheet. It also appears that they’ve been experimenting with stylesheets specifically for the iPhone as well.

I know there’s no “standard” when it comes to the CSS implementation (especially CSS2), but in the year 2008, can we start “attempting” to use cross-browser CSS please? But hey, it’s a beta, so let’s move on.

You will be reminded to enter in an email, I strongly suggest you do use it. Just in case you forget your password… for the password reminder *cough*.

There’s an option for Auto-Login, and I suggest you not use it. As far as I can see, this defeats the whole purpose of security. Call me paranoid, but I never enable auto-logins in anything I use. Call it an old habit as an admin, but I think it makes sense.

PassPack auto lockout The system will automatically log you out when inactive. You can change this setting by going into Account > Options. I suggest you not increase this setting. It defaults at 5 minutes, and in fact, you may want to reduce that to 2. Quickly finish up any entry you are making as the system cannot lockup while you are making an entry. Do not walk away from your computer while you are entering in anything because of this.

Get into this habit : Login, do your thing, logout.  You might think it’s a pain to re-enter your info, but don’t worry about having to log in again. It’s much worse if you left it unattended for some stranger screw up your passwords.

Passpack Password ListThe Password display shows if you have set any of the additional options for each password entry… That is an actual Password, a UserID, a Link to whatever login page you will need the password for as well as any Tags. I’m not sure if the Tags make any sense if you are descriptive in the Title.

PassPack Disposable Login Do use the disposable logins if you plan to take a trip. It allows you to create a one-time use login for when you are away from your home computer. It’s a good idea if you are using a system in a CyberCafe or hotel.

PassPack makes extensive use of JavaScript to do what it does, which means, you will have to use a JS enabled browser (screen-readers for the visually impaired and text-only browsers like Lynx are out). This is to make sure that bots cannot access the system, since they and other automated hacking tools are unable make use of JavaScript.

The scripts are designed to ensure that you are not being tricked into entering data while browsing another page. I.E. Phishing attempts. Also a lot of password encoding, special character recognition, strength metering, query sanitizing etc… takes place client-side. Some of the scripts appear to be written by Francesco Sullo, also the author of aSSL.  

It’s too bad their employees couldn’t learn any lessons from this guy, because he apparently knows how to spam “Evangelize” properly… That’s what I call helpful spam.  Now if only he would stop giving 5 stars to his own product on Download.com.

In addition to this, PassPack does use some publicly available code. The system makes heavy use of the jQuery script library to do AJAX calls and perform other UI functions. jQuery alone has a number of browsers that it is compatible with, and a few others that it isn’t.

PassPack itself, thus, will be have the same browser compatibilities. In addition to this, you cannot use the iCab browser as well as OmniWeb. Both browsers are for the Mac.

Any attempt to use them or other incompatible browser, and you will be greeted with the following message :

Sorry. The version beta5 of PassPack 
has not been full tested with this browser version.

The Pros:

It works!
As far as betas go, it’s pretty decent. I would like to see more UI refinement and for God’s sake, get rid of the multiple stylesheets.
I think I will be keeping my account and I look forward to the next iteration. Hopefully they will continue to perform as advertised.

What exactly is PassPack? It’s a bookmarker on steroids. Except, substitute “Title” with “Location” and “URL” with “Password”. For what it is, it gets the job done.

The Cons:

It’s not accessible. The UI could use a bit more streamlining as I can see how novice users can get a bit tangled up during the registration process. They need to make it clearer ahead of time that there are three crucial bits of information required from the user during the registration :
Your Username
Your Password (Moderately complex)
Your PackingKey (Very complex)

The Password allows you to login to the system. The PackingKey unlocks all your entries.

They haven’t restricted access to the stylesheets and script libraries. Which means a compromised browser may be tapped by an unscrupulous individual and trick the user into entering in his/her info. I should not be able download any of the script files or CSS by plugging the URL into my browser. This is kind of important.

Fortunately the fix is simple; They just need to check the referrer… /css.php?getfile=stylesheet.css Or something similar to make sure the files are being requested by the same domain. This is especially important for the JavaScript files. In short, if it can be hot-linked, that’s a problem.

It would be nice if they can accommodate users with special needs.

What’s inherent to this system, and indeed endemic to all web-based solutions, it’s web-based. As in you need to have Internet access to retrieve your passwords.

Now if only they will fix their ridiculous marketing campaign, things would be dandy.