Fixed: Upgrade the password hashing from MD5 to Bcrypt

This commit is contained in:
Amit sharma
2026-03-13 15:14:53 +05:30
parent a27e55c049
commit 622e8d9c26
16 changed files with 204 additions and 34 deletions

View File

@@ -1179,9 +1179,10 @@ abstract class AdminTabCore
if ($key == 'passwd' && Tools::getValue('id_'.$table) && empty($value)) {
continue;
}
/* Automatically encrypt password in MD5 */
/* Automatically hash password */
if ($key == 'passwd' && !empty($value)) {
$value = Tools::encrypt($value);
$crypto = new Hashing();
$value = $crypto->hash($value);
}
$object->{$key} = $value;
}

View File

@@ -167,7 +167,7 @@ class CustomerCore extends ObjectModel
'lastname' => array('type' => self::TYPE_STRING, 'validate' => 'isName', 'required' => true, 'size' => 32),
'firstname' => array('type' => self::TYPE_STRING, 'validate' => 'isName', 'required' => true, 'size' => 32),
'email' => array('type' => self::TYPE_STRING, 'validate' => 'isEmail', 'required' => true, 'size' => 128),
'passwd' => array('type' => self::TYPE_STRING, 'validate' => 'isPasswd', 'required' => true, 'size' => 32),
'passwd' => array('type' => self::TYPE_STRING, 'validate' => 'isHashedPassword', 'required' => true, 'size' => 60),
'last_passwd_gen' => array('type' => self::TYPE_STRING, 'copy_post' => false),
'id_gender' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'birthday' => array('type' => self::TYPE_DATE, 'validate' => 'isBirthDate'),
@@ -374,7 +374,7 @@ class CustomerCore extends ObjectModel
*/
public function getByEmail($email, $passwd = null, $ignore_guest = true)
{
if (!Validate::isEmail($email) || ($passwd && !Validate::isPasswd($passwd))) {
if (!Validate::isEmail($email) || ($passwd && !Validate::isHashedPassword($passwd))) {
die(Tools::displayError());
}
@@ -383,19 +383,28 @@ class CustomerCore extends ObjectModel
FROM `'._DB_PREFIX_.'customer`
WHERE `email` = \''.pSQL($email).'\'
'.Shop::addSqlRestriction(Shop::SHARE_CUSTOMER).'
'.(isset($passwd) ? 'AND `passwd` = \''.pSQL(Tools::encrypt($passwd)).'\'' : '').'
AND `deleted` = 0
'.($ignore_guest ? ' AND `is_guest` = 0' : ''));
if (!$result) {
return false;
}
if (isset($passwd)) {
$crypto = new Hashing();
if (!$crypto->checkHash($passwd, $result['passwd'])) {
return false;
}
}
$this->id = $result['id_customer'];
foreach ($result as $key => $value) {
if (property_exists($this, $key)) {
$this->{$key} = $value;
}
}
if (isset($passwd) && !$crypto->isFirstHash($passwd, $result['passwd'])) {
$this->passwd = $crypto->hash($passwd);
$this->update();
}
return $this;
}
@@ -560,7 +569,7 @@ class CustomerCore extends ObjectModel
*/
public static function checkPassword($id_customer, $passwd)
{
if (!Validate::isUnsignedId($id_customer) || !Validate::isMd5($passwd)) {
if (!Validate::isUnsignedId($id_customer) || !Validate::isHashedPassword($passwd)) {
die(Tools::displayError());
}
$cache_id = 'Customer::checkPassword'.(int)$id_customer.'-'.$passwd;
@@ -847,12 +856,13 @@ class CustomerCore extends ObjectModel
if (empty($password)) {
$password = Tools::passwdGen(8, 'RANDOM');
}
if (!Validate::isPasswd($password)) {
if (!Validate::isHashedPassword($password)) {
return false;
}
$this->is_guest = 0;
$this->passwd = Tools::encrypt($password);
$crypto = new Hashing();
$this->passwd = $crypto->hash($password);
$this->cleanGroups();
$this->addGroups(array(Configuration::get('PS_CUSTOMER_GROUP'))); // add default customer group
$this->id_default_group = (int) Configuration::get('PS_CUSTOMER_GROUP');
@@ -887,7 +897,8 @@ class CustomerCore extends ObjectModel
public function setWsPasswd($passwd)
{
if ($this->id == 0 || $this->passwd != $passwd) {
$this->passwd = Tools::encrypt($passwd);
$crypto = new Hashing();
$this->passwd = $crypto->hash($passwd);
}
return true;
}

View File

@@ -102,7 +102,7 @@ class EmployeeCore extends ObjectModel
'firstname' => array('type' => self::TYPE_STRING, 'validate' => 'isName', 'required' => true, 'size' => 32),
'email' => array('type' => self::TYPE_STRING, 'validate' => 'isEmail', 'required' => true, 'size' => 128),
'id_lang' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true),
'passwd' => array('type' => self::TYPE_STRING, 'validate' => 'isPasswdAdmin', 'required' => true, 'size' => 32),
'passwd' => array('type' => self::TYPE_STRING, 'validate' => 'isPasswdAdmin', 'required' => true, 'size' => 60),
'last_passwd_gen' => array('type' => self::TYPE_STRING),
'active' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
'optin' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
@@ -273,7 +273,7 @@ class EmployeeCore extends ObjectModel
*/
public function getByEmail($email, $passwd = null, $active_only = true)
{
if (!Validate::isEmail($email) || ($passwd != null && !Validate::isPasswd($passwd))) {
if (!Validate::isEmail($email) || ($passwd != null && !Validate::isHashedPassword($passwd))) {
die(Tools::displayError());
}
@@ -281,11 +281,17 @@ class EmployeeCore extends ObjectModel
SELECT *
FROM `'._DB_PREFIX_.'employee`
WHERE `email` = \''.pSQL($email).'\'
'.($active_only ? ' AND `active` = 1' : '')
.($passwd !== null ? ' AND `passwd` = \''.Tools::encrypt($passwd).'\'' : ''));
'.($active_only ? ' AND `active` = 1' : ''));
if (!$result) {
return false;
}
if ($passwd !== null) {
$crypto = new Hashing();
if (!$crypto->checkHash($passwd, $result['passwd'])) {
return false;
}
}
$this->id = $result['id_employee'];
$this->id_profile = $result['id_profile'];
foreach ($result as $key => $value) {
@@ -293,6 +299,11 @@ class EmployeeCore extends ObjectModel
$this->{$key} = $value;
}
}
if ($passwd !== null && !$crypto->isFirstHash($passwd, $result['passwd'])) {
$this->passwd = $crypto->hash($passwd);
$this->update();
}
return $this;
}
@@ -316,7 +327,7 @@ class EmployeeCore extends ObjectModel
*/
public static function checkPassword($id_employee, $passwd)
{
if (!Validate::isUnsignedId($id_employee) || !Validate::isPasswd($passwd, 8)) {
if (!Validate::isUnsignedId($id_employee) || !Validate::isHashedPassword($passwd, 8)) {
die(Tools::displayError());
}
@@ -364,19 +375,20 @@ class EmployeeCore extends ObjectModel
// validate and set password for the employee
public function setWsPasswd($passwd)
{
$crypto = new Hashing();
if ($this->id != 0) {
if ($this->passwd != $passwd) {
if (!Validate::isPasswd($passwd, Validate::ADMIN_PASSWORD_LENGTH)) {
if (!Validate::isHashedPassword($passwd, Validate::ADMIN_PASSWORD_LENGTH)) {
WebserviceRequest::getInstance()->setError(400, 'The password must be at least '.Validate::ADMIN_PASSWORD_LENGTH.' characters long.', 134);
} else {
$this->passwd = Tools::encrypt($passwd);
$this->passwd = $crypto->hash($passwd);
}
}
} else {
if (!Validate::isPasswd($passwd, Validate::ADMIN_PASSWORD_LENGTH)) {
if (!Validate::isHashedPassword($passwd, Validate::ADMIN_PASSWORD_LENGTH)) {
WebserviceRequest::getInstance()->setError(400, 'The password must be at least '.Validate::ADMIN_PASSWORD_LENGTH.' characters long.', 134);
} else {
$this->passwd = Tools::encrypt($passwd);
$this->passwd = $crypto->hash($passwd);
}
}

125
classes/Hashing.php Normal file
View File

@@ -0,0 +1,125 @@
<?php
/**
* 2007-2017 PrestaShop
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to http://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2017 PrestaShop SA
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
class HashingCore
{
/** @var array should contain hashing methods */
private $hashMethods = array();
/**
* Check if it's the first function of the array that was used for hashing
*
* @param string $passwd The password you want to check
* @param string $hash The hash you want to check
* @param string $staticSalt A static salt
*
* @return bool Result of the verify function
*/
public function isFirstHash($passwd, $hash, $staticSalt = _COOKIE_KEY_)
{
if (!count($this->hashMethods)) {
$this->initHashMethods();
}
$closure = reset($this->hashMethods);
return $closure['verify']($passwd, $hash, $staticSalt);
}
/**
* Iterate on hash_methods array and return true if it matches
*
* @param string $passwd The password you want to check
* @param string $hash The hash you want to check
* @param string $staticSalt A static salt
*
* @return bool `true` is returned if the function find a match else false
*/
public function checkHash($passwd, $hash, $staticSalt = _COOKIE_KEY_)
{
if (!count($this->hashMethods)) {
$this->initHashMethods();
}
foreach ($this->hashMethods as $closure) {
if ($closure['verify']($passwd, $hash, $staticSalt)) {
return true;
}
}
return false;
}
/**
* Hash the `$plaintextPassword` string and return the result of the 1st hashing method
* contained in Hashing::hashMethods
*
* @param string $plaintextPassword The password you want to hash
* @param string $staticSalt The static salt
*
* @return string
*/
public function hash($plaintextPassword, $staticSalt = _COOKIE_KEY_)
{
if (!count($this->hashMethods)) {
$this->initHashMethods();
}
$closure = reset($this->hashMethods);
return $closure['hash']($plaintextPassword, $staticSalt, $closure['option']);
}
/**
* Init hash methods
*
* @return void
*/
private function initHashMethods()
{
$this->hashMethods = array(
'bcrypt' => array(
'option' => array(),
'hash' => function ($passwd, $staticSalt, $option) {
return password_hash($passwd, PASSWORD_BCRYPT);
},
'verify' => function ($passwd, $hash, $staticSalt) {
return password_verify($passwd, $hash);
},
),
'md5' => array(
'option' => array(),
'hash' => function ($passwd, $staticSalt, $option) {
return md5($staticSalt.$passwd);
},
'verify' => function ($passwd, $hash, $staticSalt) {
return md5($staticSalt.$passwd) === $hash;
},
),
);
}
}

View File

@@ -1188,7 +1188,8 @@ abstract class ObjectModelCore implements Core_Foundation_Database_EntityInterfa
}
if ($field == 'passwd') {
if ($value = Tools::getValue($field)) {
$this->{$field} = Tools::encrypt($value);
$crypto = new Hashing();
$this->{$field} = $crypto->hash($value);
}
} else {
$this->{$field} = $value;

View File

@@ -53,7 +53,7 @@ class ReferrerCore extends ObjectModel
'primary' => 'id_referrer',
'fields' => array(
'name' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true, 'size' => 64),
'passwd' => array('type' => self::TYPE_STRING, 'validate' => 'isPasswd', 'size' => 32),
'passwd' => array('type' => self::TYPE_STRING, 'validate' => 'isPasswd', 'size' => 60),
'http_referer_regexp' => array('type' => self::TYPE_STRING, 'validate' => 'isCleanHtml', 'size' => 64),
'request_uri_regexp' => array('type' => self::TYPE_STRING, 'validate' => 'isCleanHtml', 'size' => 64),
'http_referer_like' => array('type' => self::TYPE_STRING, 'validate' => 'isCleanHtml', 'size' => 64),

View File

@@ -88,6 +88,19 @@ class ValidateCore
return preg_match('/^[a-f0-9A-F]{32}$/', $md5);
}
/**
* Check for password hash validity (legacy MD5 or bcrypt), or plaintext password.
*
* @param string $hash
* @return bool Validity is ok or not
*/
public static function isHashedPassword($hashedPasswd)
{
return self::isPasswd($hashedPasswd)
|| Tools::strlen($hashedPasswd) == 32
|| Tools::strlen($hashedPasswd) == 60;
}
/**
* Check for SHA1 string validity
*
@@ -1140,4 +1153,4 @@ class ValidateCore
{
return (preg_match('/^(?:'.Configuration::get('PS_INVOICE_PREFIX', Context::getContext()->language->id).')\s*([0-9]+)$/i', $id));
}
}
}

View File

@@ -3838,9 +3838,10 @@ class AdminControllerCore extends Controller
if ($key == 'passwd' && Tools::getValue('id_'.$table) && empty($value)) {
continue;
}
/* Automatically encrypt password in MD5 */
/* Automatically hash password */
if ($key == 'passwd' && !empty($value)) {
$value = Tools::encrypt($value);
$crypto = new Hashing();
$value = $crypto->hash($value);
}
$object->{$key} = $value;
}
@@ -4817,4 +4818,4 @@ class AdminControllerCore extends Controller
return 0;
}
}
}
}

View File

@@ -1378,7 +1378,8 @@ class WebserviceSpecificManagementBookingsCore Extends ObjectModel implements We
$objCustomer->firstname = $customerDetails['firstname'];
$objCustomer->lastname = $customerDetails['lastname'];
$objCustomer->email = $customerDetails['email'];
$objCustomer->passwd = md5(time()._COOKIE_KEY_);
$crypto = new Hashing();
$objCustomer->passwd = $crypto->hash(Tools::passwdGen(32, 'RANDOM'));
$objCustomer->phone = (isset($customerDetails['phone']) ? $customerDetails['phone'] : '');
$objCustomer->cleanGroups();
$objCustomer->add();

View File

@@ -2835,7 +2835,8 @@ class AdminImportControllerCore extends AdminController
AdminImportController::arrayWalk($info, array('AdminImportController', 'fillInfo'), $customer);
if ($customer->passwd) {
$customer->passwd = Tools::encrypt($customer->passwd);
$crypto = new Hashing();
$customer->passwd = $crypto->hash($customer->passwd);
}
$customers_shop = array();

View File

@@ -263,7 +263,8 @@ class AdminLoginControllerCore extends AdminController
if (!count($this->errors)) {
$pwd = Tools::passwdGen(10, 'RANDOM');
$employee->passwd = Tools::encrypt($pwd);
$crypto = new Hashing();
$employee->passwd = $crypto->hash($pwd);
$employee->last_passwd_gen = date('Y-m-d H:i:s', time());
$params = array(

View File

@@ -71,7 +71,7 @@ class IdentityControllerCore extends FrontController
$this->errors[] = Tools::displayError('This email address is not valid');
} elseif ($this->customer->email != $email && Customer::customerExists($email, true)) {
$this->errors[] = Tools::displayError('An account using this email address has already been registered.');
} elseif (!Tools::getIsset('old_passwd') || (Tools::encrypt($old_passwd) != $this->context->cookie->passwd)) {
} elseif (!Tools::getIsset('old_passwd') || !(new Hashing())->checkHash($old_passwd, $this->context->cookie->passwd)) {
$this->errors[] = Tools::displayError('The password you entered is incorrect.');
} elseif (Tools::getValue('passwd') != Tools::getValue('confirmation')) {
$this->errors[] = Tools::displayError('The password and confirmation do not match.');

View File

@@ -73,7 +73,8 @@ class PasswordControllerCore extends FrontController
} elseif ((strtotime($customer->last_passwd_gen.'+'.(int)Configuration::get('PS_PASSWD_TIME_FRONT').' minutes') - time()) > 0) {
Tools::redirect('index.php?controller=authentication&error_regen_pwd');
} else {
$customer->passwd = Tools::encrypt($password = Tools::passwdGen(MIN_PASSWD_LENGTH, 'RANDOM'));
$crypto = new Hashing();
$customer->passwd = $crypto->hash($password = Tools::passwdGen(MIN_PASSWD_LENGTH, 'RANDOM'));
$customer->last_passwd_gen = date('Y-m-d H:i:s', time());
if ($customer->update()) {
Hook::exec('actionPasswordRenew', array('customer' => $customer, 'password' => $password));

View File

@@ -593,7 +593,7 @@ CREATE TABLE `PREFIX_customer` (
`firstname` varchar(32) NOT NULL,
`lastname` varchar(32) NOT NULL,
`email` varchar(128) NOT NULL,
`passwd` varchar(32) NOT NULL,
`passwd` varchar(60) NOT NULL,
`last_passwd_gen` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`birthday` date DEFAULT NULL,
`newsletter` tinyint(1) unsigned NOT NULL DEFAULT '0',
@@ -779,7 +779,7 @@ CREATE TABLE `PREFIX_employee` (
`lastname` varchar(32) NOT NULL,
`firstname` varchar(32) NOT NULL,
`email` varchar(128) NOT NULL,
`passwd` varchar(32) NOT NULL,
`passwd` varchar(60) NOT NULL,
`last_passwd_gen` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`stats_date_from` date DEFAULT NULL,
`stats_date_to` date DEFAULT NULL,
@@ -1781,7 +1781,7 @@ CREATE TABLE `PREFIX_range_weight` (
CREATE TABLE `PREFIX_referrer` (
`id_referrer` int(10) unsigned NOT NULL auto_increment,
`name` varchar(64) NOT NULL,
`passwd` varchar(32) DEFAULT NULL,
`passwd` varchar(60) DEFAULT NULL,
`http_referer_regexp` varchar(64) DEFAULT NULL,
`http_referer_like` varchar(64) DEFAULT NULL,
`request_uri_regexp` varchar(64) DEFAULT NULL,

View File

@@ -35,7 +35,8 @@ class InstallFixturesFashion extends InstallXmlLoader
public function createEntityCustomer($identifier, array $data, array $data_lang)
{
if ($identifier == 'John') {
$data['passwd'] = Tools::encrypt('123456789');
$crypto = new Hashing();
$data['passwd'] = $crypto->hash('123456789');
$data['last_passwd_gen'] = date('Y-m-d H:i:s');
$data['birthday'] = date('Y-m-d', strtotime('-30 years'));
$data['newsletter_date_add'] = date('Y-m-d H:i:s');

View File

@@ -566,7 +566,8 @@ class InstallModelInstall extends InstallAbstractModel
$employee->firstname = Tools::ucfirst($data['admin_firstname']);
$employee->lastname = Tools::ucfirst($data['admin_lastname']);
$employee->email = $data['admin_email'];
$employee->passwd = md5(_COOKIE_KEY_.$data['admin_password']);
$crypto = new Hashing();
$employee->passwd = $crypto->hash($data['admin_password']);
$employee->last_passwd_gen = date('Y-m-d h:i:s', strtotime('-360 minutes'));
$employee->bo_theme = 'default';
$employee->default_tab = 1;