Location: PHPKode > scripts > password > reines-password-1a842d4/password.php
<?php

/**
* Copyright (C) 2011 FluxBB (http://fluxbb.org)
* License: LGPL - GNU Lesser General Public License (http://www.gnu.org/licenses/lgpl.html)
*/

class PasswordHash
{
	/**
	 * Fetches random data from a secure source if possible -
	 * /dev/urandom on UNIX systems. Falls back to mt_rand()
	 * if no better source is available.
	 *
	 * @param string $length
	 * 		Number of bytes of random data to generate.
	 *
	 * @param bool $raw_output
	 * 		When set to TRUE, the data is returned in raw
	 * 		binary form, otherwise the returned value is a
	 * 		($length * 2)-character hexadecimal number.
	 *
	 * @return string
	 * 		The random data.
	 */
	public static function random_bytes($length, $raw_output = false)
	{
		if (function_exists('openssl_random_pseudo_bytes'))
			$data = openssl_random_pseudo_bytes($length);
		else
		{
			$data = '';

			// On a UNIX system use /dev/urandom
			if (is_readable('/dev/urandom'))
			{
				$handle = @fopen('/dev/urandom', 'rb');
				if ($handle !== false)
				{
					$data = fread($handle, $length);
					fclose($handle);
				}
			}

			// Fall back to using md_rand() - not cryptographically secure, but available everywhere
			while (strlen($data) < $length)
				$data .= pack('i', mt_rand());

			if (strlen($data) > $length)
				$data = substr($data, 0, $length);
		}

		// If requested return the raw output
		if ($raw_output)
			return $data;

		// Otherwise return the data as a hex string
		return bin2hex($data);
	}

	/**
	 * Generates a random key using the alphabet ./0-9A-Za-z.
	 *
	 * @param string $length
	 * 		Length of the string to generate.
	 *
	 * @return string
	 * 		The generated random string.
	 */
	public static function random_key($length)
	{
		$bytes = ceil($length / 1.33);
		$key = self::base64_encode(self::random_bytes($bytes, true));
		return substr($key, 0, $length);
	}

	/**
	 * Encodes data in base64 using the alphabet ./0-9A-Za-z.
	 *
	 * @param string $str
	 * 		The data to encode.
	 *
	 * @return string
	 * 		The encoded data, as a string.
	 */
	private static function base64_encode($str)
	{
		$from	= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
		$to		= './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

		$str = substr(base64_encode($str), 0, -2);
		return strtr($str, $from, $to);
	}

	/**
	 * Hashes the given password using PBKDF2.
	 * Salt takes the form $F$<cost>$<blocks>$<salt> where the cost is a 2 digit
	 * cost parameter, blocks is a 1 digit number defining how long the key should
	 * be (block * 32) bytes, and the salt is a 22 digit salt using the alphabet
	 * ./0-9A-Za-z.
	 *
	 * @param string $str
	 * 		The password to hash.
	 *
	 * @param string $salt
	 * 		A salt to base the hashing on.
	 *
	 * @return string
	 * 		The hashed string, including the original salt.
	 */
	private static function pbkdf2($str, $salt)
	{
		// Check if the given salt is valid or not
		if (!preg_match('%\$F\$(\d{2})\$(\d)\$([a-zA-Z0-9\./]{22})(.*)$%', $salt, $matches))
			return null;

		$workload = $matches[1];
		$key_blocks = $matches[2];
		$salt = $matches[3];

		unset ($matches);

		$repetitions = pow(2, $workload + 3); // Increase the workload since PBKDF2 is faster than blowfish
		$output = '';

		for ($block = 0;$block < $key_blocks;$block++)
		{
			// Initial hash for this block
			$ib = $b = hash_hmac('sha256', $salt.pack('N', $block), $str, true);

			// Perform block iterations
			for ($i = 0;$i < $repetitions;$i++)
				$ib ^= ($b = hash_hmac('sha256', $b, $str, true));

			$output .= $ib;
		}

		// Return the salt + hash
		return '$F$'.str_pad($workload, 2, '0', STR_PAD_LEFT).'$'.$key_blocks.'$'.$salt.self::base64_encode($output);
	}

	/**
	 * Hashes the given password, using blowfish when available, with
	 * fallback to repeated hashing.
	 *
	 * @param string $str
	 * 		The password to hash.
	 *
	 * @param int $workload
	 * 		A cost parameter - the base 2 logarithm of the iteration count
	 * 		for the underlying algorithm. Must be in the range 04-31.
	 *
	 * @return string
	 * 		The hashed string, including the generated salt.
	 */
	public static function hash($str, $workload = 8)
	{
		// Validate the workload is within sensible bounds
		if ($workload < 4)
			$workload = 4;

		if ($workload > 31)
			$workload = 31;

		// Generate a random salt and base64 encode it
		$salt = self::random_bytes(16, true);
		$salt = self::base64_encode($salt);

		// If we have blowfish, use it
		if (CRYPT_BLOWFISH === 1)
			return crypt($str, '$2a$'.str_pad($workload, 2, '0', STR_PAD_LEFT).'$'.$salt);

		// Fall back to PBKDF2
		return self::pbkdf2($str, '$F$'.str_pad($workload, 2, '0', STR_PAD_LEFT).'$1$'.$salt);
	}

	/**
	 * Checks the given input against the stored hash.
	 *
	 * @param string $str
	 * 		The user input to check.
	 *
	 * @param string $hash
	 * 		The stored salt + hash.
	 *
	 * @return bool
	 * 		TRUE if the given input matches the original
	 * 		password, otherwise FALSE.
	 */
	public static function validate($str, $hash)
	{
		// First try the fall back method, then crypt
		$answer = self::pbkdf2($str, $hash);
		if ($answer === null)
			$answer = crypt($str, $hash);

		return $answer === $hash;
	}
}
Return current item: password