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