--- /dev/null
+<?php
+/**
+ * The APR1 password hashing implementation
+ *
+ * Use this class to generate and validate APR1 password hashes. APR1 hashes
+ * are used primarrily by Apache for .htaccess password storage.
+ *
+ * PHP version 5.3
+ *
+ * @see http://httpd.apache.org/docs/2.2/misc/password_encryptions.html
+ * @category PHPCryptLib
+ * @package Password
+ * @subpackage Implementation
+ * @author Anthony Ferrara <ircmaxell@ircmaxell.com>
+ * @copyright 2011 The Authors
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @version Build @@version@@
+ */
+
+namespace CryptLib\Password\Implementation;
+
+use CryptLib\Random\Factory as RandomFactory;
+
+/**
+ * The APR1 password hashing implementation
+ *
+ * Use this class to generate and validate APR1 password hashes. APR1 hashes
+ * are used primarrily by Apache for .htaccess password storage.
+ *
+ * @see http://httpd.apache.org/docs/2.2/misc/password_encryptions.html
+ * @category PHPCryptLib
+ * @package Password
+ * @subpackage Implementation
+ * @author Anthony Ferrara <ircmaxell@ircmaxell.com>
+ */
+class APR1 implements \CryptLib\Password\Password {
+
+ /**
+ * @var Generator The random generator to use for seeds
+ */
+ protected $generator = null;
+
+ /**
+ * @var Hash The hash function to use (MD5)
+ */
+ protected $hash = null;
+
+ /**
+ * @var int The number of iterations to perform (1000 for APR1)
+ */
+ protected $iterations = 1000;
+
+ /**
+ * Determine if the hash was made with this method
+ *
+ * @param string $hash The hashed data to check
+ *
+ * @return boolean Was the hash created by this method
+ */
+ public static function detect($hash) {
+ return strncmp($hash, '$apr1$', 6) === 0;
+ }
+
+ /**
+ * Return the prefix used by this hashing method
+ *
+ * @return string The prefix used
+ */
+ public static function getPrefix() {
+ return '$apr1$';
+ }
+
+ /**
+ * Load an instance of the class based upon the supplied hash
+ *
+ * @param string $hash The hash to load from
+ *
+ * @return Password the created instance
+ * @throws InvalidArgumentException if the hash wasn't created here
+ */
+ public static function loadFromHash($hash) {
+ if (!static::detect($hash)) {
+ throw new \InvalidArgumentException('Hash Not Created Here');
+ }
+ return new static;
+ }
+
+ /**
+ * Build a new instance
+ *
+ * @param Generator $generator The random generator to use for seeds
+ *
+ * @return void
+ */
+ public function __construct(
+ \CryptLib\Random\Generator $generator = null
+ ) {
+ if (is_null($generator)) {
+ $random = new RandomFactory();
+ $generator = $random->getMediumStrengthGenerator();
+ }
+ $this->generator = $generator;
+ }
+
+ /**
+ * Create a password hash for a given plain text password
+ *
+ * @param string $password The password to hash
+ *
+ * @return string The formatted password hash
+ */
+ public function create($password) {
+ $salt = $this->to64($this->generator->generateInt(0, PHP_INT_MAX), 8);
+ return $this->hash($password, $salt, $this->iterations);
+ }
+
+ /**
+ * Verify a password hash against a given plain text password
+ *
+ * @param string $password The password to hash
+ * @param string $hash The supplied ahsh to validate
+ *
+ * @return boolean Does the password validate against the hash
+ */
+ public function verify($password, $hash) {
+ $bits = explode('$', $hash);
+ if (!isset($bits[3]) || $bits[1] != 'apr1') {
+ return false;
+ }
+ $test = $this->hash($password, $bits[2], $this->iterations);
+ return $test == $hash;
+ }
+
+ /**
+ * Perform the hashing of the password
+ *
+ * @param string $password The plain text password to hash
+ * @param string $salt The 8 byte salt to use
+ * @param int $iterations The number of iterations to use
+ *
+ * @return string The hashed password
+ */
+ protected function hash($password, $salt, $iterations) {
+ $len = strlen($password);
+ $text = $password . '$apr1$' . $salt;
+ $bin = md5($password.$salt.$password, true);
+ for ($i = $len; $i > 0; $i -= 16) {
+ $text .= substr($bin, 0, min(16, $i));
+ }
+ for ($i = $len; $i > 0; $i >>= 1) {
+ $text .= ($i & 1) ? chr(0) : $password[0];
+ }
+ $bin = $this->iterate($text, $iterations, $salt, $password);
+ return $this->convertToHash($bin, $salt);
+ }
+
+ protected function iterate($text, $iterations, $salt, $password) {
+ $bin = md5($text, true);
+ for ($i = 0; $i < $iterations; $i++) {
+ $new = ($i & 1) ? $password : $bin;
+ if ($i % 3) {
+ $new .= $salt;
+ }
+ if ($i % 7) {
+ $new .= $password;
+ }
+ $new .= ($i & 1) ? $bin : $password;
+ $bin = md5($new, true);
+ }
+ return $bin;
+ }
+
+ protected function convertToHash($bin, $salt) {
+ $tmp = '$apr1$'.$salt.'$';
+ $tmp .= $this->to64(
+ (ord($bin[0])<<16) | (ord($bin[6])<<8) | ord($bin[12]),
+ 4
+ );
+ $tmp .= $this->to64(
+ (ord($bin[1])<<16) | (ord($bin[7])<<8) | ord($bin[13]),
+ 4
+ );
+ $tmp .= $this->to64(
+ (ord($bin[2])<<16) | (ord($bin[8])<<8) | ord($bin[14]),
+ 4
+ );
+ $tmp .= $this->to64(
+ (ord($bin[3])<<16) | (ord($bin[9])<<8) | ord($bin[15]),
+ 4
+ );
+ $tmp .= $this->to64(
+ (ord($bin[4])<<16) | (ord($bin[10])<<8) | ord($bin[5]),
+ 4
+ );
+ $tmp .= $this->to64(
+ ord($bin[11]),
+ 2
+ );
+ return $tmp;
+ }
+
+ /**
+ * Convert the input number to a base64 number of the specified size
+ *
+ * @param int $num The number to convert
+ * @param int $size The size of the result string
+ *
+ * @return string The converted representation
+ */
+ protected function to64($num, $size) {
+ static $seed = '';
+ if (empty($seed)) {
+ $seed = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
+ 'abcdefghijklmnopqrstuvwxyz';
+ }
+ $result = '';
+ while (--$size >= 0) {
+ $result .= $seed[$num & 0x3f];
+ $num >>= 6;
+ }
+ return $result;
+ }
+
+}