Location: PHPKode > projects > Escher CMS > eschercms-0.9.2/escher/sparkplug/plugs/sparkhasher/sparkhasher.php
<?php

/*
Copyright 2009-2011 Sam Weiss
All Rights Reserved.

This file is part of Spark/Plug.

Spark/Plug is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

if (!defined('spark/plug'))
{
	header('HTTP/1.1 403 Forbidden');
	exit('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"><html><head><title>403 Forbidden</title></head><body><h1>Forbidden</h1><p>You don\'t have permission to access the requested resource on this server.</p></body></html>');
}

// -----------------------------------------------------------------------------

/*

This class is adpated from the public domain Portable PHP password hashing framework,
written by Solar Designer <solar at openwall.com>.

See the original project's web site: http://www.openwall.com/phpass/

Edited for style and to remove legacy code for PHP versions older than 5.2, which
Spark/Plug does not support.

Also, changed portable hash implementation to use sha1 instead of md5, and changed
the hash signature to "$Q$" so that the hash is not mistaken for an md5 hash.

*/

// -----------------------------------------------------------------------------

class _SparkHasher
{
	private $_itoa64;
	private $_iteration_count_log2;
	private $_portable_hashes;
	private $_random_state;

	// --------------------------------------------------------------------------

	public function __construct($iterationCountLog2 = 8, $portableHashes = false)
	{
		$this->_itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

		if ($iterationCountLog2 < 4 || $iterationCountLog2 > 31)
		{
			$iterationCountLog2 = 8;
		}
		
		$this->_iteration_count_log2 = $iterationCountLog2;
		$this->_portable_hashes = $portableHashes;
		$this->_random_state = microtime() . getmyinode() . '-' . getmypid();
	}

	// --------------------------------------------------------------------------

	public function hash($message)
	{
		$random = '';

		if (CRYPT_BLOWFISH === 1 && !$this->_portable_hashes)
		{
			$random = $this->getRandomBytes(16);
			$hash = crypt($message, $this->gensalt_blowfish($random));
			if (strlen($hash) === 60)
			{
				return $hash;
			}
		}

		if (CRYPT_EXT_DES === 1 && !$this->_portable_hashes)
		{
			if (strlen($random) < 3)
			{
				$random = $this->getRandomBytes(3);
			}
			$hash = crypt($message, $this->gensalt_ext_des($random));
			if (strlen($hash) === 20)
			{
				return $hash;
			}
		}

		if (strlen($random) < 6)
		{
			$random = $this->getRandomBytes(6);
		}
		$hash = $this->crypt_portable($message, $this->gensalt_portable($random));
		if (strlen($hash) === 39)
		{
			return $hash;
		}

		# Returning '*' on error is safe here, but would _not_ be safe
		# in a crypt(3)-like function used _both_ for generating new
		# hashes and for validating messages against existing hashes.

		return '*';
	}

	// --------------------------------------------------------------------------

	public function validate($message, $storedHash)
	{
		$hash = $this->crypt_portable($message, $storedHash);
		
		if ($hash[0] === '*')
		{
			$hash = crypt($message, $storedHash);
		}
		
		return ($hash === $storedHash);
	}

	// --------------------------------------------------------------------------

	protected function getRandomBytes($count)
	{
		$output = '';
		
		if (is_readable('/dev/urandom') && ($fh = @fopen('/dev/urandom', 'rb')))
		{
			$output = fread($fh, $count);
			fclose($fh);
		}

		if (strlen($output) < $count)
		{
			$output = '';
			for ($i = 0; $i < $count; $i += 16)
			{
				$this->_random_state = sha1(microtime() . $this->_random_state);
				$output .= sha1($this->_random_state, true);
			}
			$output = substr($output, 0, $count);
		}

		return $output;
	}

	// --------------------------------------------------------------------------

	protected function encode64($input, $count)
	{
		$output = '';
		$i = 0;
		
		do
		{
			$value = ord($input[$i++]);
			$output .= $this->_itoa64[$value & 0x3f];
			if ($i < $count)
			{
				$value |= ord($input[$i]) << 8;
			}
			$output .= $this->_itoa64[($value >> 6) & 0x3f];
			if ($i++ >= $count)
			{
				break;
			}
			if ($i < $count)
			{
				$value |= ord($input[$i]) << 16;
			}
			$output .= $this->_itoa64[($value >> 12) & 0x3f];
			if ($i++ >= $count)
			{
				break;
			}
			$output .= $this->_itoa64[($value >> 18) & 0x3f];
		} while ($i < $count);

		return $output;
	}

	// --------------------------------------------------------------------------

	protected function crypt_portable($message, $setting)
	{
		$output = '*0';
		
		if (substr($setting, 0, 2) === $output)
		{
			$output = '*1';
		}

		if (substr($setting, 0, 3) != '$Q$')
		{
			return $output;
		}

		$count_log2 = strpos($this->_itoa64, $setting[3]);
		if ($count_log2 < 7 || $count_log2 > 30)
		{
			return $output;
		}

		$count = 1 << $count_log2;

		$salt = substr($setting, 4, 8);
		if (strlen($salt) != 8)
		{
			return $output;
		}

		$hash = sha1($salt . $message, TRUE);
		do
		{
			$hash = sha1($hash . $message, TRUE);
		} while (--$count);

		$output = substr($setting, 0, 12) . $this->encode64($hash, 20);

		return $output;
	}

	// --------------------------------------------------------------------------

	protected function gensalt_portable($input)
	{
		$output = '$Q$';
		$output .= $this->_itoa64[min($this->_iteration_count_log2 + 5, 30)];
		$output .= $this->encode64($input, 6);

		return $output;
	}

	// --------------------------------------------------------------------------

	protected function gensalt_ext_des($input)
	{
		$count_log2 = min($this->_iteration_count_log2 + 8, 24);
		# This should be odd to not reveal weak DES keys, and the
		# maximum valid value is (2**24 - 1) which is odd anyway.
		$count = (1 << $count_log2) - 1;

		$output = '_';
		$output .= $this->_itoa64[$count & 0x3f];
		$output .= $this->_itoa64[($count >> 6) & 0x3f];
		$output .= $this->_itoa64[($count >> 12) & 0x3f];
		$output .= $this->_itoa64[($count >> 18) & 0x3f];

		$output .= $this->encode64($input, 3);

		return $output;
	}

	// --------------------------------------------------------------------------

	protected function gensalt_blowfish($input)
	{
		# This one needs to use a different order of characters and a
		# different encoding scheme from the one in encode64() above.
		# We care because the last character in our encoded string will
		# only represent 2 bits.  While two known implementations of
		# bcrypt will happily accept and correct a salt string which
		# has the 4 unused bits set to non-zero, we do not want to take
		# chances and we also do not want to waste an additional byte
		# of entropy.
		$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

		$output = '$2a$';
		$output .= chr(ord('0') + $this->_iteration_count_log2 / 10);
		$output .= chr(ord('0') + $this->_iteration_count_log2 % 10);
		$output .= '$';

		$i = 0;
		do
		{
			$c1 = ord($input[$i++]);
			$output .= $itoa64[$c1 >> 2];
			$c1 = ($c1 & 0x03) << 4;
			if ($i >= 16)
			{
				$output .= $itoa64[$c1];
				break;
			}

			$c2 = ord($input[$i++]);
			$c1 |= $c2 >> 4;
			$output .= $itoa64[$c1];
			$c1 = ($c2 & 0x0f) << 2;

			$c2 = ord($input[$i++]);
			$c1 |= $c2 >> 6;
			$output .= $itoa64[$c1];
			$output .= $itoa64[$c2 & 0x3f];
		} while (1);

		return $output;
	}

	// --------------------------------------------------------------------------
}
Return current item: Escher CMS