mirror of
https://github.com/e107inc/e107.git
synced 2025-01-17 20:58:30 +01:00
411 lines
11 KiB
PHP
411 lines
11 KiB
PHP
<?php
|
|
/*
|
|
* e107 website system
|
|
*
|
|
* Copyright (C) 2008-2013 e107 Inc (e107.org)
|
|
* Released under the terms and conditions of the
|
|
* GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
|
|
*
|
|
* Extended password handler for alt_auth plugin
|
|
*
|
|
* $URL$
|
|
* $Id$
|
|
*/
|
|
|
|
/**
|
|
* e107 Alternate authorisation plugin
|
|
*
|
|
* @package e107_plugins
|
|
* @subpackage alt_auth
|
|
* @version $Id$;
|
|
*/
|
|
|
|
/**
|
|
EXTENDED PASSWORD HANDLER CLASS
|
|
- supports many password formats used on other systems
|
|
- implements checking of existing passwords only
|
|
|
|
To use:
|
|
Instantiate ExtendedPasswordHandler
|
|
call CheckPassword(plaintext_password,login_name, stored_value)
|
|
or, optionally:
|
|
call CheckPassword(plaintext_password,login_name, stored_value, password_type)
|
|
|
|
@todo:
|
|
1. Check that public/private declarations of functions are correct
|
|
*/
|
|
|
|
|
|
if (!defined('e107_INIT')) { exit; }
|
|
|
|
|
|
require_once(e_HANDLER.'user_handler.php');
|
|
|
|
|
|
// @todo make these class constants
|
|
/*define('PASSWORD_PHPBB_SALT',2);
|
|
define('PASSWORD_MAMBO_SALT',3);
|
|
define('PASSWORD_JOOMLA_SALT',4);
|
|
define('PASSWORD_GENERAL_MD5',5);
|
|
define('PASSWORD_PLAINTEXT',6);
|
|
define('PASSWORD_GENERAL_SHA1',7);
|
|
define('PASSWORD_WORDPRESS_SALT', 8);
|
|
define('PASSWORD_MAGENTO_SALT', 9);
|
|
define('PASSWORD_PHPFUSION_SHA256', 10);
|
|
|
|
// Supported formats:
|
|
define('PASSWORD_PHPBB_ID', '$H$'); // PHPBB salted
|
|
define('PASSWORD_ORIG_ID', '$P$'); // 'Original' code
|
|
define('PASSWORD_WORDPRESS_ID', '$P$'); // WordPress 2.8
|
|
*/
|
|
|
|
|
|
|
|
class ExtendedPasswordHandler extends UserHandler
|
|
{
|
|
private $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; // Holds a string of 64 characters for base64 conversion
|
|
var $random_state = ''; // A (hopefully) random number
|
|
|
|
const PASSWORD_E107_MD5 = 0;
|
|
const PASSWORD_E107_SALT = 1;
|
|
const PASSWORD_PHPBB_SALT = 2;
|
|
const PASSWORD_MAMBO_SALT = 3;
|
|
const PASSWORD_JOOMLA_SALT = 4;
|
|
const PASSWORD_GENERAL_MD5 = 5;
|
|
const PASSWORD_PLAINTEXT = 6;
|
|
const PASSWORD_GENERAL_SHA1 = 7;
|
|
const PASSWORD_WORDPRESS_SALT = 8;
|
|
const PASSWORD_MAGENTO_SALT = 9;
|
|
const PASSWORD_PHPFUSION_SALT = 10;
|
|
const PASSWORD_ABANTECART_SALT = 11;
|
|
|
|
const PASSWORD_PHPBB_ID = '$H$'; // PHPBB salted
|
|
const PASSWORD_ORIG_ID = '$P$'; // 'Original' code
|
|
const PASSWORD_WORDPRESS_ID = '$P$'; // WordPress 2.8
|
|
|
|
/**
|
|
* Constructor - just call parent
|
|
*/
|
|
function __construct()
|
|
{
|
|
// Ancestor constructor
|
|
parent::__construct();
|
|
}
|
|
|
|
|
|
/**
|
|
* Return a number of random bytes as specified by $count
|
|
*/
|
|
private function get_random_bytes($count)
|
|
{
|
|
$this->random_state = md5($this->random_state.microtime().random_int(0,10000)); // This will 'auto seed'
|
|
|
|
$output = '';
|
|
for ($i = 0; $i < $count; $i += 16)
|
|
{ // Only do this loop once unless we need more than 16 bytes
|
|
$this->random_state = md5(microtime() . $this->random_state);
|
|
$output .= pack('H*', md5($this->random_state)); // Becomes an array of 16 bytes
|
|
}
|
|
$output = substr($output, 0, $count);
|
|
|
|
return $output;
|
|
}
|
|
|
|
|
|
/**
|
|
* Encode to base64 (each block of three 8-bit chars becomes 4 printable chars)
|
|
* Use first $count characters of $input string
|
|
*/
|
|
private function encode64($input, $count)
|
|
{
|
|
return base64_encode(substr($input, 0, $count)); // @todo - check this works OK
|
|
/*
|
|
$output = '';
|
|
$i = 0;
|
|
do
|
|
{
|
|
$value = ord($input[$i++]);
|
|
$output .= $this->itoa64[$value & 0x3f];
|
|
if ($i < $count) $value |= ord($input[$i]) << 8;
|
|
$output .= $this->itoa64[($value >> 6) & 0x3f];
|
|
if ($i++ >= $count) break;
|
|
if ($i < $count) $value |= ord($input[$i]) << 16;
|
|
$output .= $this->itoa64[($value >> 12) & 0x3f];
|
|
if ($i++ >= $count) break;
|
|
$output .= $this->itoa64[($value >> 18) & 0x3f];
|
|
} while ($i < $count);
|
|
|
|
return $output;
|
|
*/
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Method for PHPBB3-style salted passwords, which begin '$H$', and WordPress-style salted passwords, which begin '$P$'
|
|
* Given a plaintext password and the complete password/hash function (which includes any salt), calculate hash
|
|
* Returns FALSE on error
|
|
*/
|
|
private function crypt_private($password, $stored_password, $password_type = self::PASSWORD_PHPBB_SALT)
|
|
{
|
|
$output = '*0';
|
|
if (substr($stored_password, 0, 2) == $output)
|
|
{
|
|
$output = '*1';
|
|
}
|
|
|
|
$prefix = '';
|
|
switch ($password_type)
|
|
{
|
|
case self::PASSWORD_PHPBB_SALT :
|
|
$prefix = self::PASSWORD_PHPBB_ID;
|
|
break;
|
|
case self::PASSWORD_WORDPRESS_SALT :
|
|
$prefix = self::PASSWORD_WORDPRESS_ID;
|
|
break;
|
|
default :
|
|
$prefix = '';
|
|
}
|
|
|
|
if ($prefix != substr($stored_password, 0, 3))
|
|
{
|
|
return $output;
|
|
}
|
|
|
|
$count_log2 = strpos($this->itoa64, $stored_password[3]); // 4th character indicates hash depth count
|
|
if ($count_log2 < 7 || $count_log2 > 30)
|
|
{
|
|
return $output;
|
|
}
|
|
|
|
$count = 1 << $count_log2;
|
|
|
|
$salt = substr($stored_password, 4, 8); // Salt is characters 5..12
|
|
if (strlen($salt) != 8)
|
|
{
|
|
return $output;
|
|
}
|
|
|
|
# We're kind of forced to use MD5 here since it's the only
|
|
# cryptographic primitive available in all versions of PHP
|
|
# currently in use. To implement our own low-level crypto
|
|
# in PHP would result in much worse performance and
|
|
# consequently in lower iteration counts and hashes that are
|
|
# quicker to crack (by non-PHP code).
|
|
// Get raw binary output (always 16 bytes) - we assume PHP5 here
|
|
$hash = md5($salt.$password, TRUE);
|
|
do
|
|
{
|
|
$hash = md5($hash.$password, TRUE);
|
|
} while (--$count);
|
|
|
|
$output = substr($setting, 0, 12); // Identifier, shift count and salt - total 12 chars
|
|
$output .= $this->encode64($hash, 16); // Returns 22-character string
|
|
|
|
return $output;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return array of supported password types - key is used internally, text is displayed
|
|
*/
|
|
public function getPasswordTypes($includeExtended = TRUE)
|
|
{
|
|
$vals = array( // Methods supported in core
|
|
'md5' => IMPORTDB_LAN_7,
|
|
'e107_salt' => IMPORTDB_LAN_8
|
|
);
|
|
|
|
if ($includeExtended)
|
|
{
|
|
$vals = array_merge($vals,array(
|
|
'plaintext' => IMPORTDB_LAN_2,
|
|
'joomla_salt' => IMPORTDB_LAN_3,
|
|
'mambo_salt' => IMPORTDB_LAN_4,
|
|
'smf_sha1' => IMPORTDB_LAN_5,
|
|
'sha1' => IMPORTDB_LAN_6,
|
|
'phpbb3_salt' => IMPORTDB_LAN_12,
|
|
'wordpress_salt' => IMPORTDB_LAN_13,
|
|
'magento_salt' => IMPORTDB_LAN_14,
|
|
'phpfusion_salt' => "PHPFusion",
|
|
'abantecart_salt' => "AbanteCart Salt",
|
|
));
|
|
}
|
|
return $vals;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return password type which relates to a specific foreign system
|
|
*/
|
|
public function passwordMapping($ptype)
|
|
{
|
|
$maps = array(
|
|
'plaintext' => self::PASSWORD_PLAINTEXT,
|
|
'joomla_salt' => self::PASSWORD_JOOMLA_SALT,
|
|
'mambo_salt' => self::PASSWORD_MAMBO_SALT,
|
|
'smf_sha1' => self::PASSWORD_GENERAL_SHA1,
|
|
'sha1' => self::PASSWORD_GENERAL_SHA1,
|
|
'mambo' => self::PASSWORD_GENERAL_MD5,
|
|
'phpbb2' => self::PASSWORD_GENERAL_MD5,
|
|
'e107' => self::PASSWORD_GENERAL_MD5,
|
|
'md5' => self::PASSWORD_GENERAL_MD5,
|
|
'e107_salt' => self::PASSWORD_E107_SALT,
|
|
'phpbb2_salt' => self::PASSWORD_PHPBB_SALT,
|
|
'phpbb3_salt' => self::PASSWORD_PHPBB_SALT,
|
|
'wordpress_salt' => self::PASSWORD_WORDPRESS_SALT,
|
|
'magento_salt' => self::PASSWORD_MAGENTO_SALT,
|
|
'phpfusion_salt' => self::PASSWORD_PHPFUSION_SALT,
|
|
'abantecart_salt' => self::PASSWORD_ABANTECART_SALT,
|
|
);
|
|
if (isset($maps[$ptype])) return $maps[$ptype];
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/**
|
|
* Extension of password validation to handle more types
|
|
*
|
|
* @param string $password - plaintext password as entered by user
|
|
* @param string $login_name - string used to log in (could actually be email address)
|
|
* @param string $stored_hash - required value for password to match
|
|
* @param integer $password_type - constant specifying the type of password to check against
|
|
*
|
|
* @return string PASSWORD_INVALID|PASSWORD_VALID|string
|
|
* PASSWORD_INVALID if no match
|
|
* PASSWORD_VALID if valid password
|
|
* Return a new hash to store if valid password but non-preferred encoding
|
|
*/
|
|
public function CheckPassword($password, $login_name, $stored_hash, $password_type = PASSWORD_DEFAULT_TYPE)
|
|
{
|
|
switch ($password_type)
|
|
{
|
|
case self::PASSWORD_GENERAL_MD5 :
|
|
case self::PASSWORD_E107_MD5 :
|
|
$pwHash = md5($password);
|
|
|
|
break;
|
|
|
|
case self::PASSWORD_GENERAL_SHA1 :
|
|
if (strlen($stored_hash) != 40) return PASSWORD_INVALID;
|
|
$pwHash = sha1($password);
|
|
break;
|
|
|
|
case self::PASSWORD_JOOMLA_SALT :
|
|
case self::PASSWORD_MAMBO_SALT :
|
|
if ((strpos($stored_hash, ':') === false) || (strlen($stored_hash) < 40))
|
|
{
|
|
return PASSWORD_INVALID;
|
|
}
|
|
// Mambo/Joomla salted hash - should be 32-character md5 hash, ':', 16-character salt (but could be 8-char salt, maybe)
|
|
list($hash, $salt) = explode(':', $stored_hash);
|
|
$pwHash = md5($password.$salt);
|
|
$stored_hash = $hash;
|
|
break;
|
|
|
|
|
|
case self::PASSWORD_MAGENTO_SALT :
|
|
$hash = $salt = '';
|
|
if ((strpos($stored_hash, ':') !== false))
|
|
{
|
|
list($hash, $salt) = explode(':', $stored_hash);
|
|
}
|
|
// Magento salted hash - should be 32-character md5 hash, ':', 2-character salt, but could be also only md5 hash
|
|
else
|
|
{
|
|
$hash = $stored_hash;
|
|
}
|
|
// if(strlen($hash) !== 32)
|
|
// {
|
|
//return PASSWORD_INVALID;
|
|
// }
|
|
|
|
$pwHash = $salt ? md5($salt.$password) : md5($password);
|
|
$stored_hash = $hash;
|
|
break;
|
|
|
|
case self::PASSWORD_E107_SALT :
|
|
//return e107::getUserSession()->CheckPassword($password, $login_name, $stored_hash);
|
|
return parent::CheckPassword($password, $login_name, $stored_hash);
|
|
// break;
|
|
|
|
case self::PASSWORD_PHPBB_SALT :
|
|
case self::PASSWORD_WORDPRESS_SALT :
|
|
if (strlen($stored_hash) != 34) return PASSWORD_INVALID;
|
|
$pwHash = $this->crypt_private($password, $stored_hash, $password_type);
|
|
if ($pwHash[0] === '*')
|
|
{
|
|
return PASSWORD_INVALID;
|
|
}
|
|
$stored_hash = substr($stored_hash,12);
|
|
break;
|
|
|
|
case self::PASSWORD_PHPFUSION_SALT:
|
|
|
|
list($hash, $salt) = explode(':', $stored_hash);
|
|
|
|
if (strlen($hash) !== 32)
|
|
{
|
|
$pwHash = hash_hmac('sha256',$password, $salt);
|
|
}
|
|
else
|
|
{
|
|
e107::getMessage()->addDebug("PHPFusion Md5 Hash Detected ");
|
|
$pwHash = md5(md5($password));
|
|
}
|
|
|
|
$stored_hash = $hash;
|
|
break;
|
|
|
|
case self::PASSWORD_PLAINTEXT :
|
|
$pwHash = $password;
|
|
break;
|
|
|
|
|
|
case self::PASSWORD_ABANTECART_SALT :
|
|
$hash = $salt = '';
|
|
|
|
if ((strpos($stored_hash, ':') !== false))
|
|
{
|
|
list($hash, $salt) = explode(':', $stored_hash);
|
|
}
|
|
// Magento salted hash - should be 32-character md5 hash, ':', 2-character salt, but could be also only md5 hash
|
|
else
|
|
{
|
|
$hash = $stored_hash;
|
|
}
|
|
|
|
/*
|
|
password = SHA1(CONCAT(salt, SHA1(CONCAT(salt, SHA1('".$this->db->escape($password)."')))
|
|
*/
|
|
//$pwHash = $salt ? md5($salt.$pword) : md5($pword);
|
|
$pwHash = sha1($salt.sha1($salt.sha1($password)));
|
|
$stored_hash = $hash;
|
|
break;
|
|
|
|
|
|
default :
|
|
return PASSWORD_INVALID;
|
|
}
|
|
|
|
if(deftrue('ADMIN_AREA'))
|
|
{
|
|
e107::getMessage()->addDebug("Stored Hash: ".$stored_hash);
|
|
|
|
if(!empty($salt))
|
|
{
|
|
e107::getMessage()->addDebug("Stored Salt: ".$salt);
|
|
}
|
|
|
|
e107::getMessage()->addDebug("Generated Hash: ".$pwHash);
|
|
}
|
|
|
|
if ($stored_hash != $pwHash) return PASSWORD_INVALID;
|
|
|
|
return PASSWORD_VALID;
|
|
}
|
|
|
|
}
|
|
|
|
|