I wasn't happy with the level of encryption for user passwords within x-cart,
so based upon a freely available class that uses MCrypt password encrypt/decrypt
functionality, I've modified X-Cart to support user-encrypted passwords,
up to 256-bit level encryption :-)
(I read on Wikipedia that the currently "crackable" level is 66-bit, so hackers
would have a ways to go if they were trying to crack the encryption algorithm)
--------------------------------------
This mod
REQUIRES that the MCrypt module be compiled into PHP.
If you are on a server that runs CPanel/WHM, it is no problem at all for the server
admin to go into the Apache build settings and enable MCrypt.
DISCLAIMER: This modification works perfectly on our Centos v4 server
running WHM/CPanel, Apache 1.3.x, PHP 4.4.x, MySQL 4.0.25, with X-Cart 4.0.18.
We claim no responsibility for any damage you may cause by applying this mod.
We do not guarantee that this mod will work in your "environment". We do not
guarantee your own ability to install this mod successfully.
You install
this mod at your own risk!! If needed, I can install this mod for $25
(if for some reason I were to install the mod and it didn't work in your environment,
I would uninstall the mod and you would not be charged). You may contact me via
these forums in PM or email to contract my services, support can be handled in this thread.
IMPORTANT -- Create a back-up of all files that you will be modifying,
as well as your database, before applying this mod!!
--------------------------------------
This mod affects the following files:- config.php
- include/change_password.php
- include/class.mycrypt.php - ADDED
- include/func.php
- include/help.php
- include/login.php
- include/register.php
- sql/xcart_tables.sql
In a follow-up post, I'll copy/paste contents from a PATCH file that
can perform the alterations as needed
--------------------------------------
OPEN config.php
FIND
Code:
$START_CHAR_CODE = 100; # 'd' letter
AFTER, ADD
Code:
#
# Variables for MCrypt functionality, CRYPT_KEY should be changed per site
#
$CRYPT_KEY = 'YOUR_UNIQUE_SITE_KEY_HERE_ANY_NUMALPHA_KEY_MAKE_IT_LONG';
$CRYPT_CIPHER = 'rijndael-256';
$CRYPT_MODE = 'cbc';
Replace the value of CRYPT_KEY with any random string, long=good. You may use numbers and letters.
I have not tested the use of non-alphanumeric characters, feel free to test.
DO NOT TEST on a live site, once you set the CRYPT_KEY, you must not change it again.
Read the notes at the end regarding settings for CRYPT_CIPHER and CRYPT_MODE
OPEN include/change_password.php
FIND
Code:
db_query("UPDATE $sql_tbl[customers] SET password='".addslashes(text_crypt($new_password))."', change_password='N' WHERE login='".addslashes($xlogin)."'");
REPLACE WITH
Code:
$new_password = text_crypt($new_password,false,true,true);
db_query("UPDATE $sql_tbl[customers] SET password='".addslashes($new_password['s'])."', password_key='".addslashes($new_password['iv'])."', change_password='N' WHERE login='".addslashes($xlogin)."'");
OPEN include/func.php
FIND
Code:
function text_crypt($s, $is_blowfish = false) {
global $START_CHAR_CODE, $CRYPT_SALT, $merchant_password, $current_area, $active_modules, $blowfish, $config;
if ($s == "")
return $s;
if($is_blowfish && $merchant_password && ($current_area == 'A' || ($current_area == 'P' && $active_modules["Simple_Mode"])) && $blowfish && $config['Security']['blowfish_enabled'] == 'Y') {
$s = trim($s);
$result = "B".func_crc32($s).func_bf_crypt($s, $merchant_password);
} else {
$enc = rand(1,255); # generate random salt.
$result = "S".text_crypt_symbol($enc); # include salt in the result;
$enc ^= $CRYPT_SALT;
for ($i = 0; $i < strlen($s); $i++) {
$r = ord(substr($s, $i, 1)) ^ $enc++;
if ($enc > 255)
$enc = 0;
$result .= text_crypt_symbol($r);
}
}
return $result;
}
REPLACE WITH
Code:
function text_crypt($s, $is_blowfish = false, $ret_iv = false, $mcrypt = false) {
global $CRYPT_KEY, $CRYPT_CIPHER, $CRYPT_MODE, $START_CHAR_CODE, $CRYPT_SALT, $merchant_password, $current_area, $active_modules, $blowfish, $config, $xcart_dir;
if ($s == "")
return $s;
if($mcrypt){
require_once($xcart_dir.'/include/class.mcrypt.php');
$crypto = new phpMcrypt($CRYPT_KEY, $CRYPT_CIPHER, $CRYPT_MODE);
$encrypted_str = $crypto->encrypt($s);
$iv = base64_encode($crypto->initvector());
$crypto->__destruct();
$result = $ret_iv?array('s'=>$encrypted_str,'iv'=>$iv):$encrypted_str;
}else{
if($is_blowfish && $merchant_password && ($current_area == 'A' || ($current_area == 'P' && $active_modules["Simple_Mode"])) && $blowfish && $config['Security']['blowfish_enabled'] == 'Y') {
$s = trim($s);
$result = "B".func_crc32($s).func_bf_crypt($s, $merchant_password);
} else {
$enc = rand(1,255); # generate random salt.
$result = "S".text_crypt_symbol($enc); # include salt in the result;
$enc ^= $CRYPT_SALT;
for ($i = 0; $i < strlen($s); $i++) {
$r = ord(substr($s, $i, 1)) ^ $enc++;
if ($enc > 255)
$enc = 0;
$result .= text_crypt_symbol($r);
}
}
}
return $result;
}
FIND
Code:
function text_decrypt($s) {
global $START_CHAR_CODE, $CRYPT_SALT, $merchant_password, $current_area, $active_modules, $blowfish;
if ($s == "")
return $s;
$crypt_method = substr($s, 0, 1);
$s = substr($s, 1);
if($crypt_method == 'B') {
if($merchant_password && ($current_area == 'A' || ($current_area == 'P' && $active_modules["Simple_Mode"])) && $blowfish) {
$crc32 = substr($s, 0, 4);
$s = substr($s, 4);
$result = func_bf_decrypt($s, $merchant_password);
if(func_crc32($result) != $crc32) {
$result = func_get_langvar_by_name('err_data_corrupted');
}
} else {
return false;
}
} elseif($crypt_method != 'B') {
if($crypt_method != 'S') {
$s = $crypt_method.$s;
}
$enc = $CRYPT_SALT ^ text_decrypt_symbol($s, 0);
$result = "";
for ($i = 2; $i < strlen($s); $i+=2) { # $i=2 to skip salt
$result .= chr(text_decrypt_symbol($s, $i) ^ $enc++);
if ($enc > 255)
$enc = 0;
}
}
return $result;
}
REPLACE WITH
Code:
function text_decrypt($s, $iv = '', $mcrypt = false) {
global $CRYPT_KEY, $CRYPT_CIPHER, $CRYPT_MODE, $START_CHAR_CODE, $CRYPT_SALT, $merchant_password, $current_area, $active_modules, $blowfish, $xcart_dir;
if ($s == "")
return $s;
if($mcrypt){
require_once($xcart_dir.'/include/class.mcrypt.php');
if($iv!='')
$crypto = new phpMcrypt($CRYPT_KEY, $CRYPT_CIPHER, $CRYPT_MODE, base64_decode($iv));
else
$crypto = new phpMcrypt($CRYPT_KEY, $CRYPT_CIPHER, $CRYPT_MODE);
$decrypted_str = $crypto->decrypt($s);
$crypto->__destruct();
$result = $decrypted_str;
}else{
$crypt_method = substr($s, 0, 1);
$s = substr($s, 1);
if($crypt_method == 'B') {
if($merchant_password && ($current_area == 'A' || ($current_area == 'P' && $active_modules["Simple_Mode"])) && $blowfish) {
$crc32 = substr($s, 0, 4);
$s = substr($s, 4);
$result = func_bf_decrypt($s, $merchant_password);
if(func_crc32($result) != $crc32) {
$result = func_get_langvar_by_name('err_data_corrupted');
}
} else {
return false;
}
} elseif($crypt_method != 'B') {
if($crypt_method != 'S') {
$s = $crypt_method.$s;
}
$enc = $CRYPT_SALT ^ text_decrypt_symbol($s, 0);
$result = "";
for ($i = 2; $i < strlen($s); $i+=2) { # $i=2 to skip salt
$result .= chr(text_decrypt_symbol($s, $i) ^ $enc++);
if ($enc > 255)
$enc = 0;
}
}
}
return $result;
}
FIND
Code:
$userinfo["passwd1"] = stripslashes(text_decrypt($userinfo["password"]));
$userinfo["passwd2"] = stripslashes(text_decrypt($userinfo["password"]));
$userinfo["password"] = stripslashes(text_decrypt($userinfo["password"]));
REPLACE WITH
Code:
$userinfo["passwd1"] = stripslashes(text_decrypt($userinfo["password"], $userinfo["password_key"], true));
$userinfo["passwd2"] = stripslashes(text_decrypt($userinfo["password"], $userinfo["password_key"], true));
$userinfo["password"] = stripslashes(text_decrypt($userinfo["password"], $userinfo["password_key"], true));
FIND
Code:
$account = func_query_first("SELECT login, password FROM $sql_tbl[customers] WHERE login='$uname'");
if ($account["login"] == text_decrypt($account["password"]))
$return[] = $account["login"];
REPLACE WITH
Code:
$account = func_query_first("SELECT login, password, password_key FROM $sql_tbl[customers] WHERE login='$uname'");
if ($account["login"] == text_decrypt($account["password"], $account["password_key"], true))
$return[] = $account["login"];
FIND
Code:
$account = func_query_first("SELECT login, password FROM $sql_tbl[customers] WHERE login='$login_' AND usertype='$usertype'");
if (!empty($account)) {
if ($account["login"] == text_decrypt($account["password"]))
$return[] = $account["login"];
REPLACE WITH
Code:
$account = func_query_first("SELECT login, password, password_key FROM $sql_tbl[customers] WHERE login='$login_' AND usertype='$usertype'");
if (!empty($account)) {
if ($account["login"] == text_decrypt($account["password"], $account["password_key"], true))
$return[] = $account["login"];
OPEN include/help.php
FIND
Code:
$accounts = func_query("select login, password, usertype from $sql_tbl[customers] where email='$email' and status='Y'");
REPLACE WITH
Code:
$accounts = func_query("select login, password, password_key, usertype from $sql_tbl[customers] where email='$email' and status='Y'");
FIND
Code:
$accounts[$key]["password"]=text_decrypt($accounts[$key]["password"]);
REPLACE WITH
Code:
$accounts[$key]["password"]=text_decrypt($accounts[$key]["password"], $accounts[$key]["password_key"], true);
OPEN include/login.php
FIND
Code:
if (!empty($user_data) && $password == text_decrypt($user_data["password"]) && !empty($password) && $allow_login) {
REPLACE WITH
Code:
if ($user_data['password_key']==''){
$newpass = text_crypt(text_decrypt($user_data['password']),false,true,true);
$user_data['password'] = $newpass['s'];
$user_data['password_key'] = $newpass['iv'];
db_query("UPDATE $sql_tbl[customers] SET password='".addslashes($user_data["password"])."',password_key='".addslashes($user_data["password_key"])."' WHERE loginid='".$user_data["loginid"]."'");
unset($newpass);
}
if ($allow_login && !empty($user_data) && !empty($password) && $password == text_decrypt($user_data["password"], $user_data["password_key"], true)) {
OPEN include/register.php
FIND
Code:
$crypted = addslashes(text_crypt($passwd1));
REPLACE WITH
Code:
$crypted = addslashes(text_crypt($passwd1,false,true,true));
FIND
Code:
db_query("UPDATE $sql_tbl[customers] SET password='$crypted', password_hint='$password_hint', password_hint_answer='$password_hint_answer', title='$title', firstname='$firstname', lastname='$lastname', company='$company', b_address='".$b_address."\n".$b_address_2."', b_city='$b_city', b_county='".(@$b_county)."', b_state='$b_state', b_country='$b_country', b_zipcode='$b_zipcode', s_address='".$s_address."\n".$s_address_2."', s_city='$s_city', s_county='".(@$s_county)."', s_state='$s_state', s_country='$s_country', s_zipcode='$s_zipcode', phone='$phone', email='$email', fax='$fax', url='$url', card_name='$card_name', card_type='$card_type', card_number='".addslashes(text_crypt($card_number))."', card_expire='$card_expire', card_cvv2='$card_cvv2', pending_membership='$pending_membership', ssn='$ssn', change_password='$change_password', parent = '$parent', pending_plan_id = '$pending_plan_id' WHERE login='$login' AND usertype='$login_type'");
REPLACE WITH
Code:
db_query("UPDATE $sql_tbl[customers] SET password='".addslashes($crypted['s'])."', password_key='".addslashes($crypted['iv'])."', password_hint='$password_hint', password_hint_answer='$password_hint_answer', title='$title', firstname='$firstname', lastname='$lastname', company='$company', b_address='".$b_address."\n".$b_address_2."', b_city='$b_city', b_county='".(@$b_county)."', b_state='$b_state', b_country='$b_country', b_zipcode='$b_zipcode', s_address='".$s_address."\n".$s_address_2."', s_city='$s_city', s_county='".(@$s_county)."', s_state='$s_state', s_country='$s_country', s_zipcode='$s_zipcode', phone='$phone', email='$email', fax='$fax', url='$url', card_name='$card_name', card_type='$card_type', card_number='".addslashes(text_crypt($card_number))."', card_expire='$card_expire', card_cvv2='$card_cvv2', pending_membership='$pending_membership', ssn='$ssn', change_password='$change_password', parent = '$parent', pending_plan_id = '$pending_plan_id' WHERE login='$login' AND usertype='$login_type'");
FIND
Code:
db_query("INSERT INTO $sql_tbl[customers] (login,usertype,password,password_hint,password_hint_answer,title,firstname,lastname,company,b_address,b_city,b_county,b_state,b_country,b_zipcode,s_address,s_city,s_county,s_state,s_country,s_zipcode,phone,email,fax,url,card_name,card_type,card_number,card_expire,card_cvv2,first_login,status,referer,pending_membership,ssn,parent,pending_plan_id,change_password) VALUES ('$uname','$usertype','$crypted','".@$password_hint."','".@$password_hint_answer."','$title','$firstname','$lastname','$company','".$b_address."\n".$b_address_2."','$b_city','".(@$b_county)."','$b_state','$b_country','$b_zipcode','".$s_address."\n".$s_address_2."','$s_city','".(@$s_county)."','$s_state','$s_country','$s_zipcode','$phone','$email','$fax','$url','".@$card_name."','".@$card_type."','".addslashes(text_crypt(@$card_number))."','".@$card_expire."','".@$card_cvv2."','".time()."','Y','".@$RefererCookie."','".@$pending_membership."','".@$ssn."', '$parent', '$pending_plan_id','$change_password')");
REPLACE WITH
Code:
db_query("INSERT INTO $sql_tbl[customers] (login,usertype,password,password_key,password_hint,password_hint_answer,title,firstname,lastname,company,b_address,b_city,b_county,b_state,b_country,b_zipcode,s_address,s_city,s_county,s_state,s_country,s_zipcode,phone,email,fax,url,card_name,card_type,card_number,card_expire,card_cvv2,first_login,status,referer,pending_membership,ssn,parent,pending_plan_id,change_password) VALUES ('$uname','$usertype','".addslashes($crypted['s'])."','".addslashes($crypted['iv'])."','".@$password_hint."','".@$password_hint_answer."','$title','$firstname','$lastname','$company','".$b_address."\n".$b_address_2."','$b_city','".(@$b_county)."','$b_state','$b_country','$b_zipcode','".$s_address."\n".$s_address_2."','$s_city','".(@$s_county)."','$s_state','$s_country','$s_zipcode','$phone','$email','$fax','$url','".@$card_name."','".@$card_type."','".addslashes(text_crypt(@$card_number))."','".@$card_expire."','".@$card_cvv2."','".time()."','Y','".@$RefererCookie."','".@$pending_membership."','".@$ssn."', '$parent', '$pending_plan_id','$change_password')");
OPEN sql/xcart_tables.sql (OPTIONAL)
FIND
Code:
password varchar(128) NOT NULL default '',
REPLACE WITH
Code:
password blob NOT NULL,
password_key blob NOT NULL,
CREATE include/class.mcrypt.php
PASTE IN THE FOLLOWING CODE
Code:
<?php
/*
* class.mcrypt.php -> phpMcrypt Class (PHP4)
*/
/**
* @author Dustin Whittle
* @version 0.01
*/
if ( !defined('XCART_START') ) { header("Location: ../"); die("Access denied"); }
class phpMcrypt
{
var $td;
var $iv = false;
// this gets called when class is instantiated
function phpMcrypt($key = 'MyRandomStringThatWillAlwaysBeTheSame', $algorithm = 'tripledes', $mode = 'ecb', $iv = false)
{
if(extension_loaded('mcrypt') === FALSE)
{
$prefix = (PHP_SHLIB_SUFFIX == 'dll') ? 'php_' : '';
dl($prefix . 'mcrypt.' . PHP_SHLIB_SUFFIX) or die('The Mcrypt module could not be loaded.');
}
// set mcrypt mode and cipher
$this->td = mcrypt_module_open($algorithm, '', $mode, '') ;
// Unix has better pseudo random number generator then mcrypt, so if it is available lets use it!
$random_seed = strstr(PHP_OS, "WIN") ? MCRYPT_RAND : MCRYPT_DEV_RANDOM;
// if initialization vector set in constructor use it else, generate from random seed
$this->iv = ($iv === false) ? mcrypt_create_iv(mcrypt_enc_get_iv_size($this->td), $random_seed) : substr($iv, 0, mcrypt_enc_get_iv_size($this->td));
// get the expected key size based on mode and cipher
$expected_key_size = mcrypt_enc_get_key_size($this->td);
// we dont need to know the real key, we just need to be able to confirm a hashed version
$key = substr(md5($key), 0, $expected_key_size);
// initialize mcrypt library with mode/cipher, encryption key, and random initialization vector
mcrypt_generic_init($this->td, $key, $this->iv);
}
function initvector()
{
/*
return the $iv used
*/
return $this->iv;
}
function encrypt($plain_string)
{
/*
encrypt string using mcrypt and then encode any special characters
and then return the encrypted string
*/
return base64_encode(mcrypt_generic($this->td, $plain_string));
}
function decrypt($encrypted_string)
{
/*
remove any special characters then decrypt string using mcrypt and then trim null padding
and then finally return the encrypted string
*/
return trim(mdecrypt_generic($this->td, base64_decode($encrypted_string)));
}
// since php 4 does not have deconstructors, we will need to manually call this function
function __destruct()
{
// shutdown mcrypt
mcrypt_generic_deinit($this->td);
// close mcrypt cipher module
mcrypt_module_close($this->td);
}
}
/*** USAGE ***/
/*
require_once('include/class.mcrypt.php'); // require the phpMcrypt class
$cryptKey = 'va364hqm34ha9v7v3n2aj3nvva6rl3m4nv78s64hv8d6cl2v4235h4';
$cipher = 'rijndael-256';
$mode = 'cbc';
$crypto = new phpMcrypt($cryptKey, $cipher, $mode);
$the_string_to_be_encrypted = 'blah.blah.blah';
$the_string_that_is_encrypted = $crypto->encrypt($the_string_to_be_encrypted);
$the_iv = $crypto->initvector();
$crypto->__destruct();
$crypto = new phpMcrypt($cryptKey, $cipher, $mode, $the_iv);
$the_encrypted_string_decrypted = $crypto->decrypt($the_string_that_is_encrypted);
$crypto->__destruct();
echo 'Original: ' . $the_string_to_be_encrypted . '
';
echo 'Initialization Vector: ' . $the_iv . '
';
echo 'Encrypted: ' . $the_string_that_is_encrypted . '
';
echo 'Decrypted: ' . $the_encrypted_string_decrypted . '
';
*/
/* Possible ciphers:
cast-128
gost
rijndael-128
twofish
arcfour
cast-256
loki97
rijndael-192
saferplus
wake
blowfish-compat
des
rijndael-256
serpent
xtea
blowfish
enigma
rc2
tripledes
*/
/* Possible modes:
cfb
cbc // more secure than ECB, good security + speed compromise
ctr
ecb
ofb
*/
?>
EXECUTE THE FOLLOWING CODE VIA phpMyAdmin (changing SQL)
-- replace "xcart_" with your own prefix if needed
Code:
ALTER TABLE `xcart_customers`
CHANGE `password` `password` blob NOT NULL AFTER membership,
ADD `password_key` blob NOT NULL AFTER password;
SAVE AND CLOSE ALL FILES
I created a script to convert all passwords in the database, but that
proved to be a lengthy process (5000+ members would have taken several hours to process).
Due to the slow speed of that process, I altered this mod will automatically
convert a user's password when the login script is prompted in x-cart.
When an attempt is made to login, the script checks the user's password,
if it's not the new "standard", then the password is decrypted, and re-encrypted.
In config.php, you have the ability to change the encryption algorithm (CRYPT_CIPHER)
and the "mode" used for encryption (CRYPT_MODE).
The defaults provided are a strong combination (256-bit encryption), but feel
free to change. Once you set these values, you cannot alter them again.
On my own server, running latest Apache/PHP/MCrypt as provided by CPanel/WHM,
my options for CIPHER and MODE are listed below, they'll likely work for you:
Quote:
/* Possible ciphers:
cast-128
gost
rijndael-128
twofish
arcfour
cast-256
loki97
rijndael-192
saferplus
wake
blowfish-compat
des
rijndael-256
serpent
xtea
blowfish
enigma
rc2
tripledes
*/
/* Possible modes:
cfb
cbc // more secure than ECB, good security + speed compromise
ctr
ecb
ofb
*/
|
Cheers!
-intel352