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;
	}
		
	/**#@-*/
}

PHP Crypto

I often have to work on projects that use passwords so poorly, or use encryption so haphazardly, that I just throw my hands up and just rewrite the whole damn thing. This is a replacement for just such a damn thing. And while, I don’t expect to be a drop-in replacement for most projects, I figure it gives the basics to what most people want. If you’re starting out with a new application, maybe this will help you.

There are some things done here that may be overkill (some would say “underkill”?) but I’ll leave it up to your discretion. If you find any errors/corrections/improvements, please let me know.

<?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 {
	
	/**
	 * Application specific random string to encode salts
	 * IMPORTANT:This should be changed in your own deployments (and never repeated)
	 * It will also be easier to move this to a defined variable
	 */
	private $saltKey = 'i^-ZsV[+vCHl)3D?-S7GiLSK~iv=qzQO%k`x%0?:Lps-[+88M//J+z- >+KKY``*';
	
	
	/**#@+
	 * Instance getter, constructor
	 */
	
	private static $instance;
	static function GetInstance() {
		if ( !isset( $instance ) ) {
			self::$instance = new Crypto();
		}
		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, $this->saltKey );
		$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 ) ); // Boyz II Men
		$pass = $this->Password( $pass, $salt );
		
		$salt = $this->Encode( $salt, $this->saltKey );
		
		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 ) {
		if ( empty( $len ) ) {
			$len = mt_rand( 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->Crypt( $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->Crypt( $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 Decrypted content
	 */
	public function Crypt( $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
	 */
	
	
	/**
	 * 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;
	}
		
	/**#@-*/
}

 

The CryptDeserialize and CryptSerialize functions were originally meant for storing encrypted cookies/sessions. You can use them for something similar, however the string values must be UTF-8 for it to work correctly (due to json_decode/encode).

7:30AM… Time for bed!

Unboxing the BlackBerry PlayBook

This is a bit of a present to myself. Normally, I eschew buying technology for technology’s sake, but over Christmas last year, I got a BlackBerry Playbook (2 actually, with one for my mom). The price was much less than an Android Nexus and since I already had an Android phone, I figured I’d give the BlackBerry a try. Yeah, I know… lots of people have done this ages ago, but I haven’t. So there!

I got the 32Gb version, mostly for the price. This doesn't have an expansion port, but I don't plan on keeping a lot of media on it. And yes, I did use a tin as a pot... so sue me!

I got the 32Gb version, mostly for the price. This doesn’t have an expansion port, but I don’t plan on keeping a lot of media on it. And yes, I did use a tin as a plant pot… so sue me!

The outer case comes out as a sleeve and underneath is a stylish BB logo box. And yes, I also used a cut up soda bottle and an old jug as pots. I'm a bachelor; I'm allowed to do stuff like that!

The outer case comes out as a sleeve and underneath is a stylish BB logo box. And yes, I used a cut up soda bottle and an old jug as pots too. I’m a bachelor; I’m allowed to do stuff like that!

The tablet inside comes in a typical "sock" like cover. If you use this for any length of time, a book sleeve is highly recommended.

The tablet inside comes in a typical “sock” like cover. If you use this for any length of time, a book sleeve is highly recommended.

Underneath is the documentation and charger.

Underneath is the documentation and charger.

Product info box has the booklet and the "Do and Don't" list in three languages. The charger box also has the USB cable and a nifty screen wipe cloth (which you will use a lot)

Product info box has the booklet and the “Do and Don’t” list in three languages. The charger box also has the USB cable and a nifty screen wipe cloth (which you will use a lot)

The startup time can be almost a minute, maybe more. I've been using it for 3 months now and it still hasn't changed

The startup time can be almost a minute, maybe more. I’ve been using it for 3 months now and it still hasn’t changed

You get to the next screen by swiping. And I can already see the fingerprints

You get to the next screen by swiping. And I can already see the fingerprints

After selecting language and region, you get to your BB ID signup. Note you get 10 tries after which, I think it can lock up. I didn't purposely lock myself out to find out ;)

After selecting language and region, you get to your BB ID signup. Note: You get 10 tries after which, I think, it can lock up. I didn’t purposely lock myself out to find out ;)

During the setup, you will be prompted to go through the "training" phase. Note: You *cannot* skip this part, which I found to be a little annoying. I guess they want to make sure people know how to use it before complaining.

During the setup, you will be prompted to go through the “training” phase. Note: You *cannot* skip this part, which I found to be a little annoying. I guess they want to make sure people know how to use it before complaining.

 I linked my BlackBerry 9930 phone to this tablet so I can use its data plan to browse. This is one feature I wished my Android had by default, but it's much smoother on the PlayBook. I get 3G speeds (that's the 9930 max) and no hiccups.

I linked my 9930 phone to this tablet using BlackBerry Bridge so I can use its data plan to browse. This is one feature I wished my Android had by default, but it’s much smoother on the PlayBook. I get 3G speeds (that’s the 9930 max) and no hiccups.

Update notification came up as soon as I connected to my WiFi. Update itself took about 10 minutes (to download and install)

Update notification came up as soon as I connected to my WiFi. You can keep your phone connected to the tablet while WiFi is on and it will use the WiFi to download. Update itself took about 10 minutes (to download and install)

During the restart after the update, I could see one of the bigger problems of the PlayBook. Now I know why they included the screen wipe cloth (see above). Even my phone isn't this bad.

During the restart after the update, I could see one of the bigger problems of the PlayBook. Now I know why they included the screen wipe cloth (see above). Even my phone isn’t this bad.

After the setup, I got to using the web browser, which I must say was very smooth. There are some issues after a second update with some YouTube videos in that I’m unable to skip ahead or rewind on certain HD videos and/or long videos, but I’m willing to chock this up to poor Flash compatibility than a fault of the PlayBook.

The apps are rather sparse, but does include a couple of games and the NFB (National Film Board) app, which is like the Canadian PBS. They have documentaries, art, culture and history related movies and I found this to be the most used app on it so far.

One thing I did note is that the popup keys while typing can get a little annoying.

Note, how there's a blue popup above the finger showing the key you just pressed. While some people might find this useful, I found it annoying.

Note, how there’s a blue popup above the finger showing the key you just pressed. While some people might find this useful, I found it annoying.

So I went into the keyboard settings (the cog icon on the top of the screen will bring up settings) and turned it off.

Swipe the black border on the top down to bring up the top border with the settings icon (along with the battery status and time), if you have another app running in the foreground.  Turn 'Keypress popup' to 'off'

Swipe the black border on the top down to bring up the top border with the settings icon (along with the battery status and time), if you have another app running in the foreground.  Turn “Keypress popup” to “off”

There was one minor issue with the PlayBook acting up a bit when I had the browser open. The browser would suddenly close for no particular reason as soon as I opened it. I solved this by going into settings and changing the mode to “Showcase” (Settings > General > Application Behavior ). I guess this prevents apps from going into hibernation when running in the background, which may eat into battery life, but so far, I haven’t seen much of a battery issue.

Which isn’t the case with the BlackBerry 9930 which eats battery life like nothing else.

So I think I like my PlayBook. It’s certainly not the biggest at just 7 inches, but I don’t see much of a problem with that considering what I’m using it for; that is browsing, email and maybe a video or two. My biggest problem so far has been that I keep forgetting it in the bathroom. I need to find a way to shorten poopie time or bring a newspaper next time instead.

Harshest Forum rules Ever!

I’m working on a little forum side project and sent out some feelers to a few friends on who would like to help me moderate. I got a reply back from someone (and this was totally not a surprise) who not only volunteered and happened to be a co-admin with me before, but also forwarded a set of block messages for filtered posts that I had written 12 years ago! My how times change.

He said this was on a backup CD of the code found in some drawer.

We have filtered your post for the following reason(s) :

  • Your post is mostly in uppercase.
    Besides being harder to read on a screen, writing in uppercase is considered rude in online forums and is the text equivalent (since we can’t hear you) of yelling.
  • Your post has a high number of exclamation marks.
    This is totally unnecessary and gets on the readers’ nerves. One is good, two is OK, three is pushing it and four or more is just plain idiotic.
  • Your post is incomprehensible.
    Your post appears to be written in English, but it is unlike any form of the language we know. It would be nice if you are able to post something rest us can understand. Grammar, logic and coherence are vitally important.
  • Your post is in the wrong language forum.
    Obviously not every visitor is completely fluent in English, but since English is the primary language of this forum, we’ll have to move it to the appropriate language forum.
  • You posted something that has been repeated ad nauseam.
    It’s good to drive home a point if you truly believe it, but it’s not good to do so in an almost identical fashion to previous posts. Please be original.
  • Your post is short, but still contained “ALL YOUR BASE”.
    This usually means there’s a high likelihood your post is really quite stupid, unproductive or quite possibly both.
  • Your post might be spam.
    Our community has flagged your post for our review. This could be a misidentification, in which case we’ll let it through shortly, or if it is spam, it will be deleted.

Please familiarise yourself with the etiquette of posting here; feel free to browse and get used to the posts that did get published so you may add to them soon.

Whenever a post was held in moderation on the forum, the commenter would be shown some combination of these.

These were the preprogrammed responses to one of the very, very, very old forum software I wrote, which ran on my Windows 2000 server with ASP and VBScript (HA!)

Can’t wait to take it apart and see how far I’ve come. Totally looking forward to a nostalgia trip.

PHP Membership Provider

I came across a post made a little while ago by Steven Benner on PHP’s notable lack of a consistent Membership Provider as in ASP.Net. All good points, but I think the hard bit isn’t so much session management since there are a million examples out there on how to provide a login interface.

The tough part seems to be a consistent method of accessing, creating and maintaining users in a database. Also a lot of those examples tend to be MySQL specific so if you need something easily customized for something else, like say PostgreSQL, you’re pretty much stuck customizing to no end, at which point you might as well write your own.

And that seems to be what an overwhelming majority of people are doing if they’re not getting an off-the-shelf CMS to start with. Of the few frameworks for membership management that are available out there, most are a bit over complicated and have licenses, fees and other strings attached.

So most people are stuck writing a user management system from scratch or getting a CMS off the shelf with its own unique (and usually incompatible) user management system.

I got home from work early on Friday and have this weekend off for a pretty good rest, so early this morning, I thought I’d write up a Membership Provider for PHP that anyone can use with no strings attached (and hopefully doesn’t suck too much). The idea is to have a simple drop-in user management interface.

Keep in mind that I haven’t actually tested any of this and it all may in fact blow up spectacularly (since I didn’t do any testing, everything is as-is and I’m not a professional PHP programmer). It’s a bit late for Steven’s post, but better late than never eh? ;)

AND YOU NEED TO VALIDATE ALL INPUTS BEFORE SENDING THEM… ahem.

I didn’t want to mimic everything in the Membership Provider for ASP.Net in that there are a lot of things that I don’t like, some features that could be improved and others that are completely missing.

Notably, I don’t see the need for password questions and answers since resetting by email is often a better solution than lowering the security barrier by introducing (essentially) a weaker password in the form of an answer to a known question. Also, people often forget the answers to questions too or repeat the same questions and answers on different sites, which defeats the purpose of a strong password anyway.

I’d also like to have a way to find and delete users by either Id, Name or Email and easily lock/unlock, approve/unapprove users and provide strong passwords with Blowfish. For this last option, I downloaded the Portable PHP password hashing framework, which I think is the best implementation for PHP I’ve found so far.

Now I need a table…

Although this example table is in MySQL, you could modify this for any other database. In the queries, I avoided any MySQL specific commands like sql_calc_found_rows and the like and because I’m using PDO the code itself should work with no modifications on PostgreSQL and SQLite provided it’s the same table structure.

CREATE TABLE `users` (
  `userId` int(20) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `hash` varchar(10) NOT NULL,
  `password` varchar(200) NOT NULL,
  `passwordSalt` varchar(100) NOT NULL,
  `email` varchar(100) NOT NULL,
  `avatar` varchar(200) DEFAULT NULL,
  `createdDate` datetime NOT NULL,
  `modifiedDate` datetime NOT NULL,
  `lastActivity` datetime NOT NULL,
  `bio` text DEFAULT NULL,
  `isApproved` tinyint(1) NOT NULL DEFAULT '1',
  `isLocked` tinyint(1) NOT NULL DEFAULT '0',

  PRIMARY KEY (`userId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Users' AUTO_INCREMENT=1 ;

Pretty typical for most websites. The “hash” there is just a unique key made by combining the username and email in a hash that will set apart users even with similar looking usernames. Think of it as a very primitive Tripcode shown alongside a username.

Configuration

If there’s one thing that makes configuring an ASP.Net web page consistent is the web.config file. It’s only appropriate then to put all the configuation stuff into one file, say a .ini, like so :

;*** Website configuration file ***
;
; All comments start with a semicolon (;), and this file can be something other than config.ini
; as long as the new file name is specified in index.php .

;*** Globally accessible settings ***

[Database]
dsn = "mysql:host=localhost;dbname=testdb"
username = "testuser"
password = "BKjSheQubKEufqHC"

[Tables]
usersTable = "users"
rolesTable = "roles"
tablePrefix = ""

;*** Membership specific settings ***

[MembershipProvider]
minRequiredPasswordLength = 5
maxInvalidPasswordAttempts = 3
passwordAttemptWindow = 10
userIsOnlineTimeWindow = 20
autoApproveUsers = "true"

Of course, for this example, we won’t be using all of that, but I just created a file called config.ini and put all that in there. Now how do we load all of this? First we need an index.php file.

index.php

<?php
ini_set( "display_errors", true );

// Set the application configuration file. You may store this outside the web root
// if your web host allows it.
define('INI', 'config.ini');


/*******************************

	End editing

********************************/


define('BASE_PATH', dirname(__FILE__));

// Platform specific include path separator
if ( strtolower(substr(PHP_OS, 0, 3)) === 'win') 
	define('SEP', ';');
else
	define('SEP', ':');

// There must be an /app folder in root with all the class files
define('CLASS_DIR', BASE_PATH . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR);

// Optionally there should also be a /mod folder in root where you can include modules/plugins in the future
define('MODULE_DIR', BASE_PATH . DIRECTORY_SEPARATOR . 'mod' . DIRECTORY_SEPARATOR);

// Modify include path
set_include_path( CLASS_DIR  . SEP . MODULE_DIR );


spl_autoload_extensions(".php");
spl_autoload_register();

?>

The down side to this approach is that all your file names must be lowercase, but it should be slightly faster than any other autoload implementation and I’m reasonably confident it should run on a *nix platform with no hiccups as well as on Windows.

For now, that autoload would force the application to look in the /app folder where all the magic happens and optionally in /mod for any future modules. And for that magic we’ll create a base class that all “action” classes would inherit from. This is basically to provide dynamic class properties.

<?php

/**
 * Base class provides dynamic class property functionality
 *
 * @package Base
 */
class Base {
	
	
	/**
	 * @var array Config properties storage
	 */
	protected $props = array();
	
	
	/**
	 * Get class accessible settings
	 */
	protected function __get( $key ) {
		return array_key_exists( $key, $this->props ) ? 
			$this->props[$key] : null;
	}
	
	
	/**
	 * Set class accessible settings
	 */
	protected function __set( $key, $value ) {
		$this->props[$key] = $value;
	}
	
	
	/**
	 * Checks whether accessible setting is available
	 */
	protected function __isset( $key ) {
		return isset( $this->props[$key] );
	}
}
?>

Now we need a configuration class that will load the settings from the defined .ini file into itself and make the settings accessible to all classes calling it.

<?php

/**
 * Application configuration file.
 * All settings are imported from the .ini file defined in index.php
 *
 * @package Config
 */

class Config extends Base {
	
	
	/**
	 * Class instance
	 */
	private static $instance;
	
	
	private function __construct() {
		$this->props = parse_ini_file(INI, true);
	}
	
	
	public static function getInstance() {
		if(!self::$instance)
			self::$instance = new Config();
		
		return self::$instance;
	}
}
?>

This is starting to get a bit long so onward to the next page