3 * The APR1 password hashing implementation
5 * Use this class to generate and validate APR1 password hashes. APR1 hashes
6 * are used primarrily by Apache for .htaccess password storage.
10 * @see http://httpd.apache.org/docs/2.2/misc/password_encryptions.html
11 * @category PHPCryptLib
13 * @subpackage Implementation
14 * @author Anthony Ferrara <ircmaxell@ircmaxell.com>
15 * @copyright 2011 The Authors
16 * @license http://www.opensource.org/licenses/mit-license.html MIT License
17 * @version Build @@version@@
20 namespace CryptLib\Password\Implementation
;
22 use CryptLib\Random\Factory
as RandomFactory
;
25 * The APR1 password hashing implementation
27 * Use this class to generate and validate APR1 password hashes. APR1 hashes
28 * are used primarrily by Apache for .htaccess password storage.
30 * @see http://httpd.apache.org/docs/2.2/misc/password_encryptions.html
31 * @category PHPCryptLib
33 * @subpackage Implementation
34 * @author Anthony Ferrara <ircmaxell@ircmaxell.com>
36 class APR1
implements \CryptLib\Password\Password
{
39 * @var Generator The random generator to use for seeds
41 protected $generator = null;
44 * @var Hash The hash function to use (MD5)
46 protected $hash = null;
49 * @var int The number of iterations to perform (1000 for APR1)
51 protected $iterations = 1000;
54 * Determine if the hash was made with this method
56 * @param string $hash The hashed data to check
58 * @return boolean Was the hash created by this method
60 public static function detect($hash) {
61 return strncmp($hash, '$apr1$', 6) === 0;
65 * Return the prefix used by this hashing method
67 * @return string The prefix used
69 public static function getPrefix() {
74 * Load an instance of the class based upon the supplied hash
76 * @param string $hash The hash to load from
78 * @return Password the created instance
79 * @throws InvalidArgumentException if the hash wasn't created here
81 public static function loadFromHash($hash) {
82 if (!static::detect($hash)) {
83 throw new \
InvalidArgumentException('Hash Not Created Here');
89 * Build a new instance
91 * @param Generator $generator The random generator to use for seeds
95 public function __construct(
96 \CryptLib\Random\Generator
$generator = null
98 if (is_null($generator)) {
99 $random = new RandomFactory();
100 $generator = $random->getMediumStrengthGenerator();
102 $this->generator
= $generator;
106 * Create a password hash for a given plain text password
108 * @param string $password The password to hash
110 * @return string The formatted password hash
112 public function create($password) {
113 $salt = $this->to64($this->generator
->generateInt(0, PHP_INT_MAX
), 8);
114 return $this->hash($password, $salt, $this->iterations
);
118 * Verify a password hash against a given plain text password
120 * @param string $password The password to hash
121 * @param string $hash The supplied ahsh to validate
123 * @return boolean Does the password validate against the hash
125 public function verify($password, $hash) {
126 $bits = explode('$', $hash);
127 if (!isset($bits[3]) ||
$bits[1] != 'apr1') {
130 $test = $this->hash($password, $bits[2], $this->iterations
);
131 return $test == $hash;
135 * Perform the hashing of the password
137 * @param string $password The plain text password to hash
138 * @param string $salt The 8 byte salt to use
139 * @param int $iterations The number of iterations to use
141 * @return string The hashed password
143 protected function hash($password, $salt, $iterations) {
144 $len = strlen($password);
145 $text = $password . '$apr1$' . $salt;
146 $bin = md5($password.$salt.$password, true);
147 for ($i = $len; $i > 0; $i -= 16) {
148 $text .= substr($bin, 0, min(16, $i));
150 for ($i = $len; $i > 0; $i >>= 1) {
151 $text .= ($i & 1) ?
chr(0) : $password[0];
153 $bin = $this->iterate($text, $iterations, $salt, $password);
154 return $this->convertToHash($bin, $salt);
157 protected function iterate($text, $iterations, $salt, $password) {
158 $bin = md5($text, true);
159 for ($i = 0; $i < $iterations; $i++
) {
160 $new = ($i & 1) ?
$password : $bin;
167 $new .= ($i & 1) ?
$bin : $password;
168 $bin = md5($new, true);
173 protected function convertToHash($bin, $salt) {
174 $tmp = '$apr1$'.$salt.'$';
176 (ord($bin[0])<<16) |
(ord($bin[6])<<8) |
ord($bin[12]),
180 (ord($bin[1])<<16) |
(ord($bin[7])<<8) |
ord($bin[13]),
184 (ord($bin[2])<<16) |
(ord($bin[8])<<8) |
ord($bin[14]),
188 (ord($bin[3])<<16) |
(ord($bin[9])<<8) |
ord($bin[15]),
192 (ord($bin[4])<<16) |
(ord($bin[10])<<8) |
ord($bin[5]),
203 * Convert the input number to a base64 number of the specified size
205 * @param int $num The number to convert
206 * @param int $size The size of the result string
208 * @return string The converted representation
210 protected function to64($num, $size) {
213 $seed = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
214 'abcdefghijklmnopqrstuvwxyz';
217 while (--$size >= 0) {
218 $result .= $seed[$num & 0x3f];