xref: /plugin/authwordpress/class-phpass.php (revision e74ba40746ec7d216e6ee800316592e4401623a8)
1db8c34b5SDamien Regad<?php
2db8c34b5SDamien Regad/**
3db8c34b5SDamien Regad * Portable PHP password hashing framework.
4db8c34b5SDamien Regad * @package phpass
5db8c34b5SDamien Regad * @since 2.5.0
6*e74ba407SDamien Regad * @version 0.5 / WordPress
7*e74ba407SDamien Regad * @link https://www.openwall.com/phpass/
8db8c34b5SDamien Regad */
9db8c34b5SDamien Regad
10db8c34b5SDamien Regad#
11*e74ba407SDamien Regad# Portable PHP password hashing framework.
12*e74ba407SDamien Regad#
13*e74ba407SDamien Regad# Version 0.5.4 / WordPress.
14*e74ba407SDamien Regad#
15db8c34b5SDamien Regad# Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
16db8c34b5SDamien Regad# the public domain.  Revised in subsequent years, still public domain.
17db8c34b5SDamien Regad#
18db8c34b5SDamien Regad# There's absolutely no warranty.
19db8c34b5SDamien Regad#
20*e74ba407SDamien Regad# The homepage URL for this framework is:
21*e74ba407SDamien Regad#
22*e74ba407SDamien Regad#	http://www.openwall.com/phpass/
23*e74ba407SDamien Regad#
24db8c34b5SDamien Regad# Please be sure to update the Version line if you edit this file in any way.
25db8c34b5SDamien Regad# It is suggested that you leave the main version number intact, but indicate
26db8c34b5SDamien Regad# your project name (after the slash) and add your own revision information.
27db8c34b5SDamien Regad#
28db8c34b5SDamien Regad# Please do not change the "private" password hashing method implemented in
29db8c34b5SDamien Regad# here, thereby making your hashes incompatible.  However, if you must, please
30db8c34b5SDamien Regad# change the hash type identifier (the "$P$") to something different.
31db8c34b5SDamien Regad#
32db8c34b5SDamien Regad# Obviously, since this code is in the public domain, the above are not
33db8c34b5SDamien Regad# requirements (there can be none), but merely suggestions.
34db8c34b5SDamien Regad#
35db8c34b5SDamien Regad
36db8c34b5SDamien Regad/**
37db8c34b5SDamien Regad * Portable PHP password hashing framework.
38db8c34b5SDamien Regad *
39db8c34b5SDamien Regad * @package phpass
40*e74ba407SDamien Regad * @version 0.5 / WordPress
41*e74ba407SDamien Regad * @link https://www.openwall.com/phpass/
42db8c34b5SDamien Regad * @since 2.5.0
43db8c34b5SDamien Regad */
44db8c34b5SDamien Regadclass PasswordHash {
45db8c34b5SDamien Regad    var $itoa64;
46db8c34b5SDamien Regad    var $iteration_count_log2;
47db8c34b5SDamien Regad    var $portable_hashes;
48db8c34b5SDamien Regad    var $random_state;
49db8c34b5SDamien Regad
50db8c34b5SDamien Regad    function __construct($iteration_count_log2, $portable_hashes)
51db8c34b5SDamien Regad    {
52db8c34b5SDamien Regad        $this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
53db8c34b5SDamien Regad
54*e74ba407SDamien Regad        if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) {
55db8c34b5SDamien Regad            $iteration_count_log2 = 8;
56*e74ba407SDamien Regad        }
57db8c34b5SDamien Regad        $this->iteration_count_log2 = $iteration_count_log2;
58db8c34b5SDamien Regad
59db8c34b5SDamien Regad        $this->portable_hashes = $portable_hashes;
60db8c34b5SDamien Regad
61*e74ba407SDamien Regad        $this->random_state = microtime();
62*e74ba407SDamien Regad        if (function_exists('getmypid')) {
63*e74ba407SDamien Regad            $this->random_state .= getmypid();
64*e74ba407SDamien Regad        }
65db8c34b5SDamien Regad    }
66db8c34b5SDamien Regad
67*e74ba407SDamien Regad    function PasswordHash($iteration_count_log2, $portable_hashes)
68*e74ba407SDamien Regad    {
69db8c34b5SDamien Regad        self::__construct($iteration_count_log2, $portable_hashes);
70db8c34b5SDamien Regad    }
71db8c34b5SDamien Regad
72db8c34b5SDamien Regad    function get_random_bytes($count)
73db8c34b5SDamien Regad    {
74db8c34b5SDamien Regad        $output = '';
75db8c34b5SDamien Regad        if (@is_readable('/dev/urandom') &&
76db8c34b5SDamien Regad            ($fh = @fopen('/dev/urandom', 'rb'))) {
77db8c34b5SDamien Regad            $output = fread($fh, $count);
78db8c34b5SDamien Regad            fclose($fh);
79db8c34b5SDamien Regad        }
80db8c34b5SDamien Regad
81db8c34b5SDamien Regad        if (strlen($output) < $count) {
82db8c34b5SDamien Regad            $output = '';
83db8c34b5SDamien Regad            for ($i = 0; $i < $count; $i += 16) {
84db8c34b5SDamien Regad                $this->random_state =
85db8c34b5SDamien Regad                    md5(microtime() . $this->random_state);
86*e74ba407SDamien Regad                $output .= md5($this->random_state, TRUE);
87db8c34b5SDamien Regad            }
88db8c34b5SDamien Regad            $output = substr($output, 0, $count);
89db8c34b5SDamien Regad        }
90db8c34b5SDamien Regad
91db8c34b5SDamien Regad        return $output;
92db8c34b5SDamien Regad    }
93db8c34b5SDamien Regad
94db8c34b5SDamien Regad    function encode64($input, $count)
95db8c34b5SDamien Regad    {
96db8c34b5SDamien Regad        $output = '';
97db8c34b5SDamien Regad        $i = 0;
98db8c34b5SDamien Regad        do {
99db8c34b5SDamien Regad            $value = ord($input[$i++]);
100db8c34b5SDamien Regad            $output .= $this->itoa64[$value & 0x3f];
101*e74ba407SDamien Regad            if ($i < $count) {
102db8c34b5SDamien Regad                $value |= ord($input[$i]) << 8;
103*e74ba407SDamien Regad            }
104db8c34b5SDamien Regad            $output .= $this->itoa64[($value >> 6) & 0x3f];
105*e74ba407SDamien Regad            if ($i++ >= $count) {
106db8c34b5SDamien Regad                break;
107*e74ba407SDamien Regad            }
108*e74ba407SDamien Regad            if ($i < $count) {
109db8c34b5SDamien Regad                $value |= ord($input[$i]) << 16;
110*e74ba407SDamien Regad            }
111db8c34b5SDamien Regad            $output .= $this->itoa64[($value >> 12) & 0x3f];
112*e74ba407SDamien Regad            if ($i++ >= $count) {
113db8c34b5SDamien Regad                break;
114*e74ba407SDamien Regad            }
115db8c34b5SDamien Regad            $output .= $this->itoa64[($value >> 18) & 0x3f];
116db8c34b5SDamien Regad        } while ($i < $count);
117db8c34b5SDamien Regad
118db8c34b5SDamien Regad        return $output;
119db8c34b5SDamien Regad    }
120db8c34b5SDamien Regad
121db8c34b5SDamien Regad    function gensalt_private($input)
122db8c34b5SDamien Regad    {
123db8c34b5SDamien Regad        $output = '$P$';
124*e74ba407SDamien Regad        $output .= $this->itoa64[min($this->iteration_count_log2 + 5,
125*e74ba407SDamien Regad            30)];
126db8c34b5SDamien Regad        $output .= $this->encode64($input, 6);
127db8c34b5SDamien Regad
128db8c34b5SDamien Regad        return $output;
129db8c34b5SDamien Regad    }
130db8c34b5SDamien Regad
131db8c34b5SDamien Regad    function crypt_private($password, $setting)
132db8c34b5SDamien Regad    {
133db8c34b5SDamien Regad        $output = '*0';
134*e74ba407SDamien Regad        if (substr($setting, 0, 2) === $output) {
135db8c34b5SDamien Regad            $output = '*1';
136*e74ba407SDamien Regad        }
137db8c34b5SDamien Regad
138db8c34b5SDamien Regad        $id = substr($setting, 0, 3);
139db8c34b5SDamien Regad        # We use "$P$", phpBB3 uses "$H$" for the same thing
140*e74ba407SDamien Regad        if ($id !== '$P$' && $id !== '$H$') {
141db8c34b5SDamien Regad            return $output;
142*e74ba407SDamien Regad        }
143db8c34b5SDamien Regad
144db8c34b5SDamien Regad        $count_log2 = strpos($this->itoa64, $setting[3]);
145*e74ba407SDamien Regad        if ($count_log2 < 7 || $count_log2 > 30) {
146db8c34b5SDamien Regad            return $output;
147*e74ba407SDamien Regad        }
148db8c34b5SDamien Regad
149db8c34b5SDamien Regad        $count = 1 << $count_log2;
150db8c34b5SDamien Regad
151db8c34b5SDamien Regad        $salt = substr($setting, 4, 8);
152*e74ba407SDamien Regad        if (strlen($salt) !== 8) {
153db8c34b5SDamien Regad            return $output;
154*e74ba407SDamien Regad        }
155db8c34b5SDamien Regad
156*e74ba407SDamien Regad        # We were kind of forced to use MD5 here since it's the only
157*e74ba407SDamien Regad        # cryptographic primitive that was available in all versions
158*e74ba407SDamien Regad        # of PHP in use.  To implement our own low-level crypto in PHP
159*e74ba407SDamien Regad        # would have resulted in much worse performance and
160db8c34b5SDamien Regad        # consequently in lower iteration counts and hashes that are
161db8c34b5SDamien Regad        # quicker to crack (by non-PHP code).
162db8c34b5SDamien Regad        $hash = md5($salt . $password, TRUE);
163db8c34b5SDamien Regad        do {
164db8c34b5SDamien Regad            $hash = md5($hash . $password, TRUE);
165db8c34b5SDamien Regad        } while (--$count);
166db8c34b5SDamien Regad
167db8c34b5SDamien Regad        $output = substr($setting, 0, 12);
168db8c34b5SDamien Regad        $output .= $this->encode64($hash, 16);
169db8c34b5SDamien Regad
170db8c34b5SDamien Regad        return $output;
171db8c34b5SDamien Regad    }
172db8c34b5SDamien Regad
173db8c34b5SDamien Regad    function gensalt_blowfish($input)
174db8c34b5SDamien Regad    {
175db8c34b5SDamien Regad        # This one needs to use a different order of characters and a
176db8c34b5SDamien Regad        # different encoding scheme from the one in encode64() above.
177db8c34b5SDamien Regad        # We care because the last character in our encoded string will
178db8c34b5SDamien Regad        # only represent 2 bits.  While two known implementations of
179db8c34b5SDamien Regad        # bcrypt will happily accept and correct a salt string which
180db8c34b5SDamien Regad        # has the 4 unused bits set to non-zero, we do not want to take
181db8c34b5SDamien Regad        # chances and we also do not want to waste an additional byte
182db8c34b5SDamien Regad        # of entropy.
183db8c34b5SDamien Regad        $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
184db8c34b5SDamien Regad
185db8c34b5SDamien Regad        $output = '$2a$';
186*e74ba407SDamien Regad        $output .= chr((int)(ord('0') + $this->iteration_count_log2 / 10));
187db8c34b5SDamien Regad        $output .= chr(ord('0') + $this->iteration_count_log2 % 10);
188db8c34b5SDamien Regad        $output .= '$';
189db8c34b5SDamien Regad
190db8c34b5SDamien Regad        $i = 0;
191db8c34b5SDamien Regad        do {
192db8c34b5SDamien Regad            $c1 = ord($input[$i++]);
193db8c34b5SDamien Regad            $output .= $itoa64[$c1 >> 2];
194db8c34b5SDamien Regad            $c1 = ($c1 & 0x03) << 4;
195db8c34b5SDamien Regad            if ($i >= 16) {
196db8c34b5SDamien Regad                $output .= $itoa64[$c1];
197db8c34b5SDamien Regad                break;
198db8c34b5SDamien Regad            }
199db8c34b5SDamien Regad
200db8c34b5SDamien Regad            $c2 = ord($input[$i++]);
201db8c34b5SDamien Regad            $c1 |= $c2 >> 4;
202db8c34b5SDamien Regad            $output .= $itoa64[$c1];
203db8c34b5SDamien Regad            $c1 = ($c2 & 0x0f) << 2;
204db8c34b5SDamien Regad
205db8c34b5SDamien Regad            $c2 = ord($input[$i++]);
206db8c34b5SDamien Regad            $c1 |= $c2 >> 6;
207db8c34b5SDamien Regad            $output .= $itoa64[$c1];
208db8c34b5SDamien Regad            $output .= $itoa64[$c2 & 0x3f];
209db8c34b5SDamien Regad        } while (1);
210db8c34b5SDamien Regad
211db8c34b5SDamien Regad        return $output;
212db8c34b5SDamien Regad    }
213db8c34b5SDamien Regad
214db8c34b5SDamien Regad    function HashPassword($password)
215db8c34b5SDamien Regad    {
216db8c34b5SDamien Regad        if ( strlen( $password ) > 4096 ) {
217db8c34b5SDamien Regad            return '*';
218db8c34b5SDamien Regad        }
219db8c34b5SDamien Regad
220db8c34b5SDamien Regad        $random = '';
221db8c34b5SDamien Regad
222*e74ba407SDamien Regad        if (CRYPT_BLOWFISH === 1 && !$this->portable_hashes) {
223db8c34b5SDamien Regad            $random = $this->get_random_bytes(16);
224db8c34b5SDamien Regad            $hash =
225db8c34b5SDamien Regad                crypt($password, $this->gensalt_blowfish($random));
226*e74ba407SDamien Regad            if (strlen($hash) === 60) {
227db8c34b5SDamien Regad                return $hash;
228db8c34b5SDamien Regad            }
229db8c34b5SDamien Regad        }
230db8c34b5SDamien Regad
231*e74ba407SDamien Regad        if (strlen($random) < 6) {
232db8c34b5SDamien Regad            $random = $this->get_random_bytes(6);
233*e74ba407SDamien Regad        }
234db8c34b5SDamien Regad        $hash =
235db8c34b5SDamien Regad            $this->crypt_private($password,
236db8c34b5SDamien Regad                $this->gensalt_private($random));
237*e74ba407SDamien Regad        if (strlen($hash) === 34) {
238db8c34b5SDamien Regad            return $hash;
239*e74ba407SDamien Regad        }
240db8c34b5SDamien Regad
241db8c34b5SDamien Regad        # Returning '*' on error is safe here, but would _not_ be safe
242db8c34b5SDamien Regad        # in a crypt(3)-like function used _both_ for generating new
243db8c34b5SDamien Regad        # hashes and for validating passwords against existing hashes.
244db8c34b5SDamien Regad        return '*';
245db8c34b5SDamien Regad    }
246db8c34b5SDamien Regad
247db8c34b5SDamien Regad    function CheckPassword($password, $stored_hash)
248db8c34b5SDamien Regad    {
249db8c34b5SDamien Regad        if ( strlen( $password ) > 4096 ) {
250db8c34b5SDamien Regad            return false;
251db8c34b5SDamien Regad        }
252db8c34b5SDamien Regad
253db8c34b5SDamien Regad        $hash = $this->crypt_private($password, $stored_hash);
254*e74ba407SDamien Regad        if ($hash[0] === '*') {
255db8c34b5SDamien Regad            $hash = crypt($password, $stored_hash);
256*e74ba407SDamien Regad        }
257db8c34b5SDamien Regad
258*e74ba407SDamien Regad        # This is not constant-time.  In order to keep the code simple,
259*e74ba407SDamien Regad        # for timing safety we currently rely on the salts being
260*e74ba407SDamien Regad        # unpredictable, which they are at least in the non-fallback
261*e74ba407SDamien Regad        # cases (that is, when we use /dev/urandom and bcrypt).
262db8c34b5SDamien Regad        return $hash === $stored_hash;
263db8c34b5SDamien Regad    }
264db8c34b5SDamien Regad}
265