<?php 
/**
* @version		$Id: crypt.helper.php 2610 2025-07-07 15:47:27Z IOS $
* @package		Elxis
* @subpackage	Helpers / Encryption
* @copyright	Copyright (c) 2006-2025 Elxis CMS (https://www.elxis.org). All rights reserved.
* @license		Elxis Public License ( https://www.elxis.org/elxis-public-license.html )
* @author		Elxis Team ( https://www.elxis.org )
* @description 	Elxis CMS is free software. Read the license for copyright notices and details
*/

defined('_ELXIS_') or die ('Direct access to this location is not allowed');


class elxisCryptHelper {
	
	private $method = 'xor';//mcrypt/xor/openssl
	private $key = '';
	private $hash_type = 'sha1'; //sha1/md5/sha256/sha384/sha512
	private $openssl_method = 'AES-256-CBC';//openssl_get_cipher_methods()


	/***************************************/
	/* DETERMINE ENCRYPTION METHOD AND KEY */
	/***************************************/
	public function __construct($params=null) {
		if ($params && is_array($params) && isset($params['method']) && isset($params['key'])) {
			$method = trim($params['method']);
			$this->key = trim($params['key']);
			if (isset($params['openssl_method']) && (trim($params['openssl_method']) != '')) { $this->openssl_method = $params['openssl_method']; }
			if (isset($params['hash_type'])) {
				if (trim($params['hash_type']) != '') {
					if (in_array($params['hash_type'], array('sha1', 'md5', 'sha256', 'sha384', 'sha512'))) {
						$this->hash_type = $params['hash_type'];
					}
				}
			}
		} else {
			$elxis = eFactory::getElxis();
			$method = $elxis->getConfig('ENCRYPT_METHOD');
			$this->key = trim($elxis->getConfig('ENCRYPT_KEY'));
		}

		if (($method == '') || ($method == 'auto') || ($method == 'mcrypt') || ($method == 'openssl')) {
			if (function_exists('mcrypt_encrypt')) {//php 5.x, php 7.0
				$this->method = 'mcrypt';
			} else if (function_exists('openssl_encrypt')) {//php 5.3.3+, php 7.1+
				$this->method = 'openssl';
			} else {
				$this->method = 'xor';
			}
		} else {
			$this->method = 'xor';
		}

		if ($this->key == '') {
			trigger_error('For security reasons an encryption key must be set in configuration.php file (parameter ENCRYPT_KEY).', E_USER_ERROR);
		}
	}


	/********************/
	/* ENCRYPT A STRING */
	/********************/
	public function encrypt($string, $key='') {
		$hashkey = $this->getMD5Key($key);
		$enc = $this->xor_encode($string, $hashkey);
		if ($this->method == 'mcrypt') {
			$enc = $this->mcrypt_encode($enc, $hashkey);
		} else if ($this->method == 'openssl') {
			$enc = $this->openssl_encode($enc, $hashkey);
		}

		return base64_encode($enc);
	}


	/********************/
	/* DECRYPT A STRING */
	/********************/
	public function decrypt($string, $key='') {
		$hashkey = $this->getMD5Key($key);
		if (preg_match('/[^a-zA-Z0-9\/\+=]/', $string)) { return false; }

		$dec = base64_decode($string);
		if ($this->method == 'mcrypt') {
			$dec = $this->mcrypt_decode($dec, $hashkey);
			if ($dec === false) { return false; }
		} else if ($this->method == 'openssl') {
			$dec = $this->openssl_decode($dec, $hashkey);
			if ($dec === false) { return false; }
		}

		$decrypted = $this->xor_decode($dec, $hashkey);
		return $decrypted;
	}


	/*****************************************/
	/* GET THE 128 BIT LENGTH ENCRYPTION KEY */
	/*****************************************/
	private function getMD5Key($key='') {
		if (trim($key) == '') { return md5($this->key); }
		return md5($key);
	}


	/******************************/
	/* ENCRYPT A STRING USING XOR */
	/******************************/
	private function xor_encode($string, $key) {
		$rand = '';
		while (strlen($rand) < 32) {
			$rand .= mt_rand(0, mt_getrandmax());
		}
		$rand = $this->hash($rand);
		$enc = '';
		for ($i = 0; $i < strlen($string); $i++) {			
			$enc .= substr($rand, ($i % strlen($rand)), 1).(substr($rand, ($i % strlen($rand)), 1) ^ substr($string, $i, 1));
		}

		return $this->xor_merge($enc, $key);
	}


	/********************************/
	/* DECRYPT A XOR ENCODED STRING */
	/********************************/
	private function xor_decode($string, $key) {
		$string = $this->xor_merge($string, $key);
		$dec = '';
		for ($i = 0; $i < strlen($string); $i++) {
			$dec .= (substr($string, $i++, 1) ^ substr($string, $i, 1));
		}
		return $dec;
	}


	/***********************************************************/
	/* COMPUTE THE DIFFERENCE BETWEEN STRING AND KEY USING XOR */
	/***********************************************************/
	private function xor_merge($string, $key) {
		$hash = $this->hash($key);
		$str = '';
		for ($i = 0; $i < strlen($string); $i++) {
			$str .= substr($string, $i, 1) ^ substr($hash, ($i % strlen($hash)), 1);
		}
		return $str;
	}


	/************************/
	/* HASH ENCODE A STRING */
	/************************/
	private function hash($string, $hash_type='') {
		if ($hash_type == '') { $hash_type = $this->hash_type; }

		switch ($hash_type) {
			case 'sha512': $hs = hash('sha512', $string); break;
			case 'sha384': $hs = hash('sha384', $string); break;
			case 'sha256': $hs = hash('sha256', $string); break;
			case 'md5': $hs = md5($string); break;
			case 'sha1': default: $hs = sha1($string); break;
		}
		return $hs;
	}


	/**********************************/
	/* ENCRYPT A STRING USING OPENSSL */
	/**********************************/
	private function openssl_encode($data, $key) {
		$iv_size = openssl_cipher_iv_length($this->openssl_method);
		$iv = openssl_random_pseudo_bytes($iv_size);
		$encrypted = openssl_encrypt($data, $this->openssl_method, $key, OPENSSL_RAW_DATA, $iv);
		return $this->addCipherNoise($iv.$encrypted, $key);
	}


	/*************************/
	/* DECRYPT USING OPENSSL */
	/*************************/
	private function openssl_decode($data, $key) {
		$data = $this->removeCipherNoise($data, $key);
		$iv_size = openssl_cipher_iv_length($this->openssl_method);
		if ($iv_size > strlen($data)) { return false; }
		$iv = substr($data, 0, $iv_size);
		$data = substr($data, $iv_size);
		$text = rtrim(openssl_decrypt($data, $this->openssl_method, $key, OPENSSL_RAW_DATA, $iv), "\0");
		return $text;
	}


	/*********************************/
	/* ENCRYPT A STRING USING MCRYPT */
	/*********************************/
	private function mcrypt_encode($data, $key) {
		$init_size = mcrypt_get_iv_size($this->getCipher(), $this->getMode());
		$init_vect = mcrypt_create_iv($init_size, MCRYPT_RAND);
		return $this->addCipherNoise($init_vect.mcrypt_encrypt($this->getCipher(), $key, $data, $this->getMode(), $init_vect), $key);
	}


	/************************/
	/* DECRYPT USING MCRYPT */
	/************************/
	private function mcrypt_decode($data, $key) {
		$data = $this->removeCipherNoise($data, $key);
		$init_size = mcrypt_get_iv_size($this->getCipher(), $this->getMode());
		if ($init_size > strlen($data)) { return false; }
		$init_vect = substr($data, 0, $init_size);
		$data = substr($data, $init_size);
		return rtrim(mcrypt_decrypt($this->getCipher(), $key, $data, $this->getMode(), $init_vect), "\0");
	}


	/*************************/
	/* GET CIPHER FOR MCRYPT */
	/*************************/
	private function getCipher() {
		return MCRYPT_RIJNDAEL_256;
	}


	/*************************/
	/* GET MCRYPT MODE VALUE */
	/*************************/
	private function getMode() {
		return MCRYPT_MODE_ECB;
	}


	/**********************************************/
	/* ADD PERMUTTED NOISE TO IV + ENCRYPTED DATA */
	/**********************************************/
	private function addCipherNoise($data, $key) {
		$keyhash = $this->hash($key);
		$keylen = strlen($keyhash);
		$str = '';
		for ($i = 0, $j = 0, $len = strlen($data); $i < $len; ++$i, ++$j) {
			if ($j >= $keylen) { $j = 0; }
			$str .= chr((ord($data[$i]) + ord($keyhash[$j])) % 256);
		}
		return $str;
	}


	/***************************************************/
	/* REMOVE PERMUTTED NOISE FROM IV + ENCRYPTED DATA */
	/***************************************************/
	private function removeCipherNoise($data, $key) {
		$keyhash = $this->hash($key);
		$keylen = strlen($keyhash);
		$str = '';
		for ($i = 0, $j = 0, $len = strlen($data); $i < $len; ++$i, ++$j) {
			if ($j >= $keylen) { $j = 0; }
			$temp = ord($data[$i]) - ord($keyhash[$j]);
			if ($temp < 0) { $temp = $temp + 256; }
			$str .= chr($temp);
		}
		return $str;
	}


	/****************************/
	/* GENERATE A PASSWORD SALT */
	/****************************/
	public function getEncryptedPassword($text) {
		$text = trim($text);
		if (strlen($text) < 4) { //not acceptable password, randomize it and make it useless
			$salt = $this->key.rand(10000000, 99999999);
			return $this->hash($salt);
		}
		$kparts = str_split($this->key);
		$tparts = str_split($text);
		$salt = '';
		if (count($kparts) > count($tparts)) {
			foreach ($kparts as $k => $char) {
				$salt .= $char;
				$salt .= (isset($tparts[$k])) ? $tparts[$k] : '';
			}
		} else {
			foreach ($tparts as $k => $char) {
				$salt .= $char;
				$salt .= (isset($kparts[$k])) ? $kparts[$k] : '';
			}
		}

		return $this->hash($salt);
	}

}

?>