├── .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 |
--------------------------------------------------------------------------------