├── .gitignore ├── .travis.yml ├── README.md ├── Tests ├── BasicTest.php ├── bootstrap.php └── phpunit.xml ├── composer.json ├── lib ├── Makefile └── crypt_private.c └── src └── Hautelook └── Phpass └── PasswordHash.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | - 5.5 7 | - 5.6 8 | - hhvm 9 | 10 | before_script: 11 | - composer install 12 | 13 | script: cd Tests && phpunit --configuration phpunit.xml --coverage-text 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### This repository is a fork from the original [hautelook/phpass](https://github.com/hautelook/phpass) which seems to have been deleted on _2021-09-09_. 2 | 3 | 4 | Openwall Phpass, modernized 5 | =========================== 6 | 7 | This is Openwall's [Phpass](http://openwall.com/phpass/), based on the 0.3 release, but modernized slightly: 8 | 9 | - Namespaced 10 | - Composer support (Autoloading) 11 | - PHP 5 style 12 | - Unit Tested 13 | 14 | The changes are minimal and only stylistic. The source code is in the public domain. We claim no ownership, but needed it for one of our projects, and wanted to make it available to other people as well. 15 | 16 | ## Installation ## 17 | 18 | Add this requirement to your `composer.json` file and run `composer.phar install`: 19 | 20 | { 21 | "require": { 22 | "bordoni/phpass": "dev-main" 23 | } 24 | } 25 | 26 | ## Usage ## 27 | 28 | The following example shows how to hash a password (to then store the hash in the database), and how to check whether a provided password is correct (hashes to the same value): 29 | 30 | ``` php 31 | HashPassword('secret'); 42 | var_dump($password); 43 | 44 | $passwordMatch = $passwordHasher->CheckPassword('secret', "$2a$08$0RK6Yw6j9kSIXrrEOc3dwuDPQuT78HgR0S3/ghOFDEpOGpOkARoSu"); 45 | var_dump($passwordMatch); 46 | 47 | -------------------------------------------------------------------------------- /Tests/BasicTest.php: -------------------------------------------------------------------------------- 1 | HashPassword($correct); 18 | 19 | $this->assertTrue($hasher->CheckPassword($correct, $hash)); 20 | } 21 | 22 | public function testIncorrectHash() 23 | { 24 | $hasher = new PasswordHash(8,false); 25 | $correct = 'test12345'; 26 | $hash = $hasher->HashPassword($correct); 27 | $wrong = 'test12346'; 28 | 29 | $this->assertFalse($hasher->CheckPassword($wrong, $hash)); 30 | } 31 | 32 | public function testWeakHashes() 33 | { 34 | $hasher = new PasswordHash(8, true); 35 | $correct = 'test12345'; 36 | $hash = $hasher->HashPassword($correct); 37 | $wrong = 'test12346'; 38 | 39 | $this->assertTrue($hasher->CheckPassword($correct, $hash)); 40 | $this->assertFalse($hasher->CheckPassword($wrong, $hash)); 41 | } 42 | 43 | public function testPortableHashes() 44 | { 45 | $hasher = new PasswordHash(8, true); 46 | $correct = 'test12345'; 47 | $wrong = 'test12346'; 48 | 49 | $this->assertTrue($hasher->CheckPassword($correct, self::PORTABLE_HASH)); 50 | $this->assertFalse($hasher->CheckPassword($wrong, self::PORTABLE_HASH)); 51 | } 52 | } -------------------------------------------------------------------------------- /Tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | . 19 | 20 | 21 | 22 | ../ 23 | 24 | 25 | ../src/Hautelook 26 | 27 | ../ 28 | ./bootstrap.php 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bordoni/phpass", 3 | "type": "library", 4 | "time": "2012-08-31", 5 | "license": "Public Domain", 6 | "description": "Portable PHP password hashing framework", 7 | "keywords": [ 8 | "Blowfish", 9 | "crypt", 10 | "password", 11 | "security" 12 | ], 13 | "homepage": "http://github.com/bordoni/phpass/", 14 | "authors": [ 15 | { 16 | "name": "Solar Designer", 17 | "email": "solar@openwall.com", 18 | "homepage": "http://openwall.com/phpass/" 19 | }, 20 | { 21 | "name": "Gustavo Bordoni", 22 | "email": "gustavo@bordoni.me", 23 | "homepage": "https://bordoni.me" 24 | } 25 | ], 26 | "require": { 27 | "php": ">=5.3.3" 28 | }, 29 | "autoload": { 30 | "psr-0": { 31 | "Hautelook": "src/" 32 | } 33 | }, 34 | "replace": { 35 | "hautelook/phpass": "0.3.*" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Written by Solar Designer and placed in the public domain. 3 | # See crypt_private.c for more information. 4 | # 5 | CC = gcc 6 | LD = $(CC) 7 | RM = rm -f 8 | CFLAGS = -Wall -O2 -fomit-frame-pointer -funroll-loops 9 | LDFLAGS = -s 10 | LIBS = -lcrypto 11 | 12 | all: crypt_private-test 13 | 14 | crypt_private-test: crypt_private-test.o 15 | $(LD) $(LDFLAGS) $(LIBS) crypt_private-test.o -o $@ 16 | 17 | crypt_private-test.o: crypt_private.c 18 | $(CC) -c $(CFLAGS) crypt_private.c -DTEST -o $@ 19 | 20 | clean: 21 | $(RM) crypt_private-test* 22 | -------------------------------------------------------------------------------- /lib/crypt_private.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This code exists for the sole purpose to serve as another implementation 3 | * of the "private" password hashing method implemened in PasswordHash.php 4 | * and thus to confirm that these password hashes are indeed calculated as 5 | * intended. 6 | * 7 | * Other uses of this code are discouraged. There are much better password 8 | * hashing algorithms available to C programmers; one of those is bcrypt: 9 | * 10 | * http://www.openwall.com/crypt/ 11 | * 12 | * Written by Solar Designer in 2005 and placed in 13 | * the public domain. 14 | * 15 | * There's absolutely no warranty. 16 | */ 17 | 18 | #include 19 | #include 20 | 21 | #ifdef TEST 22 | #include 23 | #endif 24 | 25 | static char *itoa64 = 26 | "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 27 | 28 | static void encode64(char *dst, char *src, int count) 29 | { 30 | int i, value; 31 | 32 | i = 0; 33 | do { 34 | value = (unsigned char)src[i++]; 35 | *dst++ = itoa64[value & 0x3f]; 36 | if (i < count) 37 | value |= (unsigned char)src[i] << 8; 38 | *dst++ = itoa64[(value >> 6) & 0x3f]; 39 | if (i++ >= count) 40 | break; 41 | if (i < count) 42 | value |= (unsigned char)src[i] << 16; 43 | *dst++ = itoa64[(value >> 12) & 0x3f]; 44 | if (i++ >= count) 45 | break; 46 | *dst++ = itoa64[(value >> 18) & 0x3f]; 47 | } while (i < count); 48 | } 49 | 50 | char *crypt_private(char *password, char *setting) 51 | { 52 | static char output[35]; 53 | MD5_CTX ctx; 54 | char hash[MD5_DIGEST_LENGTH]; 55 | char *p, *salt; 56 | int count_log2, length, count; 57 | 58 | strcpy(output, "*0"); 59 | if (!strncmp(setting, output, 2)) 60 | output[1] = '1'; 61 | 62 | if (strncmp(setting, "$P$", 3)) 63 | return output; 64 | 65 | p = strchr(itoa64, setting[3]); 66 | if (!p) 67 | return output; 68 | count_log2 = p - itoa64; 69 | if (count_log2 < 7 || count_log2 > 30) 70 | return output; 71 | 72 | salt = setting + 4; 73 | if (strlen(salt) < 8) 74 | return output; 75 | 76 | length = strlen(password); 77 | 78 | MD5_Init(&ctx); 79 | MD5_Update(&ctx, salt, 8); 80 | MD5_Update(&ctx, password, length); 81 | MD5_Final(hash, &ctx); 82 | 83 | count = 1 << count_log2; 84 | do { 85 | MD5_Init(&ctx); 86 | MD5_Update(&ctx, hash, MD5_DIGEST_LENGTH); 87 | MD5_Update(&ctx, password, length); 88 | MD5_Final(hash, &ctx); 89 | } while (--count); 90 | 91 | memcpy(output, setting, 12); 92 | encode64(&output[12], hash, MD5_DIGEST_LENGTH); 93 | 94 | return output; 95 | } 96 | 97 | #ifdef TEST 98 | int main(int argc, char **argv) 99 | { 100 | if (argc != 3) return 1; 101 | 102 | puts(crypt_private(argv[1], argv[2])); 103 | 104 | return 0; 105 | } 106 | #endif 107 | -------------------------------------------------------------------------------- /src/Hautelook/Phpass/PasswordHash.php: -------------------------------------------------------------------------------- 1 | in 2004-2006 and placed in 12 | * 13 | * There's absolutely no warranty. 14 | * 15 | * The homepage URL for this framework is: 16 | * 17 | * http://www.openwall.com/phpass/ 18 | * 19 | * Please be sure to update the Version line if you edit this file in any way. 20 | * It is suggested that you leave the main version number intact, but indicate 21 | * your project name (after the slash) and add your own revision information. 22 | * 23 | * Please do not change the "private" password hashing method implemented in 24 | * here, thereby making your hashes incompatible. However, if you must, please 25 | * change the hash type identifier (the "$P$") to something different. 26 | * 27 | * Obviously, since this code is in the public domain, the above are not 28 | * requirements (there can be none), but merely suggestions. 29 | * 30 | * @author Solar Designer 31 | */ 32 | class PasswordHash 33 | { 34 | private $itoa64; 35 | private $iteration_count_log2; 36 | private $portable_hashes; 37 | private $random_state; 38 | 39 | /** 40 | * Constructor 41 | * 42 | * @param int $iteration_count_log2 43 | * @param boolean $portable_hashes 44 | */ 45 | public function __construct($iteration_count_log2, $portable_hashes) 46 | { 47 | $this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; 48 | 49 | if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) { 50 | $iteration_count_log2 = 8; 51 | } 52 | $this->iteration_count_log2 = $iteration_count_log2; 53 | 54 | $this->portable_hashes = $portable_hashes; 55 | 56 | $this->random_state = microtime(); 57 | if (function_exists('getmypid')) { 58 | $this->random_state .= getmypid(); 59 | } 60 | } 61 | 62 | /** 63 | * @param int $count 64 | * @return String 65 | */ 66 | public function get_random_bytes($count) 67 | { 68 | $output = ''; 69 | 70 | if (is_callable('random_bytes')) { 71 | return random_bytes($count); 72 | } 73 | 74 | if (@is_readable('/dev/urandom') && 75 | ($fh = @fopen('/dev/urandom', 'rb'))) { 76 | $output = fread($fh, $count); 77 | fclose($fh); 78 | } 79 | 80 | if (strlen($output) < $count) { 81 | $output = ''; 82 | for ($i = 0; $i < $count; $i += 16) { 83 | $this->random_state = 84 | md5(microtime() . $this->random_state); 85 | $output .= 86 | pack('H*', md5($this->random_state)); 87 | } 88 | $output = substr($output, 0, $count); 89 | } 90 | 91 | return $output; 92 | } 93 | 94 | /** 95 | * @param String $input 96 | * @param int $count 97 | * @return String 98 | */ 99 | public function encode64($input, $count) 100 | { 101 | $output = ''; 102 | $i = 0; 103 | do { 104 | $value = ord($input[$i++]); 105 | $output .= $this->itoa64[$value & 0x3f]; 106 | if ($i < $count) { 107 | $value |= ord($input[$i]) << 8; 108 | } 109 | $output .= $this->itoa64[($value >> 6) & 0x3f]; 110 | if ($i++ >= $count) { 111 | break; 112 | } 113 | if ($i < $count) { 114 | $value |= ord($input[$i]) << 16; 115 | } 116 | $output .= $this->itoa64[($value >> 12) & 0x3f]; 117 | if ($i++ >= $count) { 118 | break; 119 | } 120 | $output .= $this->itoa64[($value >> 18) & 0x3f]; 121 | } while ($i < $count); 122 | 123 | return $output; 124 | } 125 | 126 | /** 127 | * @param String $input 128 | * @return String 129 | */ 130 | public function gensalt_private($input) 131 | { 132 | $output = '$P$'; 133 | $output .= $this->itoa64[min($this->iteration_count_log2 + 134 | ((PHP_VERSION >= '5') ? 5 : 3), 30)]; 135 | $output .= $this->encode64($input, 6); 136 | 137 | return $output; 138 | } 139 | 140 | /** 141 | * @param String $password 142 | * @param String $setting 143 | * @return String 144 | */ 145 | public function crypt_private($password, $setting) 146 | { 147 | $output = '*0'; 148 | if (substr($setting, 0, 2) == $output) { 149 | $output = '*1'; 150 | } 151 | 152 | $id = substr($setting, 0, 3); 153 | # We use "$P$", phpBB3 uses "$H$" for the same thing 154 | if ($id != '$P$' && $id != '$H$') { 155 | return $output; 156 | } 157 | 158 | $count_log2 = strpos($this->itoa64, $setting[3]); 159 | if ($count_log2 < 7 || $count_log2 > 30) { 160 | return $output; 161 | } 162 | 163 | $count = 1 << $count_log2; 164 | 165 | $salt = substr($setting, 4, 8); 166 | if (strlen($salt) != 8) { 167 | return $output; 168 | } 169 | 170 | // We're kind of forced to use MD5 here since it's the only 171 | // cryptographic primitive available in all versions of PHP 172 | // currently in use. To implement our own low-level crypto 173 | // in PHP would result in much worse performance and 174 | // consequently in lower iteration counts and hashes that are 175 | // quicker to crack (by non-PHP code). 176 | if (PHP_VERSION >= '5') { 177 | $hash = md5($salt . $password, TRUE); 178 | do { 179 | $hash = md5($hash . $password, TRUE); 180 | } while (--$count); 181 | } else { 182 | $hash = pack('H*', md5($salt . $password)); 183 | do { 184 | $hash = pack('H*', md5($hash . $password)); 185 | } while (--$count); 186 | } 187 | 188 | $output = substr($setting, 0, 12); 189 | $output .= $this->encode64($hash, 16); 190 | 191 | return $output; 192 | } 193 | 194 | /** 195 | * @param String $input 196 | * @return String 197 | */ 198 | public function gensalt_extended($input) 199 | { 200 | $count_log2 = min($this->iteration_count_log2 + 8, 24); 201 | // This should be odd to not reveal weak DES keys, and the 202 | // maximum valid value is (2**24 - 1) which is odd anyway. 203 | $count = (1 << $count_log2) - 1; 204 | 205 | $output = '_'; 206 | $output .= $this->itoa64[$count & 0x3f]; 207 | $output .= $this->itoa64[($count >> 6) & 0x3f]; 208 | $output .= $this->itoa64[($count >> 12) & 0x3f]; 209 | $output .= $this->itoa64[($count >> 18) & 0x3f]; 210 | 211 | $output .= $this->encode64($input, 3); 212 | 213 | return $output; 214 | } 215 | 216 | /** 217 | * @param String $input 218 | * @return String 219 | */ 220 | public function gensalt_blowfish($input) 221 | { 222 | // This one needs to use a different order of characters and a 223 | // different encoding scheme from the one in encode64() above. 224 | // We care because the last character in our encoded string will 225 | // only represent 2 bits. While two known implementations of 226 | // bcrypt will happily accept and correct a salt string which 227 | // has the 4 unused bits set to non-zero, we do not want to take 228 | // chances and we also do not want to waste an additional byte 229 | // of entropy. 230 | $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 231 | 232 | $output = '$2a$'; 233 | $output .= chr(ord('0') + intval($this->iteration_count_log2 / 10)); 234 | $output .= chr(ord('0') + $this->iteration_count_log2 % 10); 235 | $output .= '$'; 236 | 237 | $i = 0; 238 | do { 239 | $c1 = ord($input[$i++]); 240 | $output .= $itoa64[$c1 >> 2]; 241 | $c1 = ($c1 & 0x03) << 4; 242 | if ($i >= 16) { 243 | $output .= $itoa64[$c1]; 244 | break; 245 | } 246 | 247 | $c2 = ord($input[$i++]); 248 | $c1 |= $c2 >> 4; 249 | $output .= $itoa64[$c1]; 250 | $c1 = ($c2 & 0x0f) << 2; 251 | 252 | $c2 = ord($input[$i++]); 253 | $c1 |= $c2 >> 6; 254 | $output .= $itoa64[$c1]; 255 | $output .= $itoa64[$c2 & 0x3f]; 256 | } while (1); 257 | 258 | return $output; 259 | } 260 | 261 | /** 262 | * @param String $password 263 | */ 264 | public function HashPassword($password) 265 | { 266 | $random = ''; 267 | 268 | if (CRYPT_BLOWFISH == 1 && !$this->portable_hashes) { 269 | $random = $this->get_random_bytes(16); 270 | $hash = 271 | crypt($password, $this->gensalt_blowfish($random)); 272 | if (strlen($hash) == 60) { 273 | return $hash; 274 | } 275 | } 276 | 277 | if (CRYPT_EXT_DES == 1 && !$this->portable_hashes) { 278 | if (strlen($random) < 3) { 279 | $random = $this->get_random_bytes(3); 280 | } 281 | $hash = 282 | crypt($password, $this->gensalt_extended($random)); 283 | if (strlen($hash) == 20) { 284 | return $hash; 285 | } 286 | } 287 | 288 | if (strlen($random) < 6) { 289 | $random = $this->get_random_bytes(6); 290 | } 291 | 292 | $hash = 293 | $this->crypt_private($password, 294 | $this->gensalt_private($random)); 295 | if (strlen($hash) == 34) { 296 | return $hash; 297 | } 298 | 299 | // Returning '*' on error is safe here, but would _not_ be safe 300 | // in a crypt(3)-like function used _both_ for generating new 301 | // hashes and for validating passwords against existing hashes. 302 | return '*'; 303 | } 304 | 305 | /** 306 | * @param String $password 307 | * @param String $stored_hash 308 | * @return boolean 309 | */ 310 | public function CheckPassword($password, $stored_hash) 311 | { 312 | $hash = $this->crypt_private($password, $stored_hash); 313 | if ($hash[0] == '*') { 314 | $hash = crypt($password, $stored_hash); 315 | } 316 | 317 | return $hash === $stored_hash; 318 | } 319 | } 320 | --------------------------------------------------------------------------------