Your IP : 216.73.216.61


Current Path : /home/w/u/e/wuectly/www/03cbe/
Upload File :
Current File : /home/w/u/e/wuectly/www/03cbe/Encrypt.tar

EncryptService.php000060400000016557152455311230010235 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Encrypt;

defined('_JEXEC') || die;

use FOF40\Container\Container;

/**
 * Data encryption service for FOF-based components.
 *
 * This service allows you to transparently encrypt and decrypt *text* plaintext data. Use it to provide encryption for
 * sensitive or personal data stored in your database. Please remember:
 *
 * - The default behavior is to create a file with a random key on your component's root. If the file cannot be created
 *   the encryption is turned off.
 * - The key file is only created when you access the service. If you never use this service nothing happens (for
 *   backwards compatibility).
 * - You have to manually encrypt and decrypt data. It won't happen magically.
 * - Encrypted data cannot be searched unless you implement your own, slow, search algorithm.
 * - Data encryption is meant to be used on top of, not instead of, any other security measures for your site.
 * - Data encryption only protects against exploits targeting the database. If the attacker *also* gains read access to
 *   your filesystem OR if the attacker gains read / write access to the filesystem the encryption won't protect you.
 *   This is a full compromise of your site. At this point you're pwned and nothing can protect you. If you don't
 *   understand this simple truth do NOT use encryption.
 * - This is meant as a simple and basic encryption layer. It has not been independently verified. Use at your own risk.
 *
 * This service has the following FOF application configuration parameters which can be declared under the "container"
 * key (e.g. the "name" attribute of the fof.xml elements under fof > common > container > option):
 *
 * - encrypt_key_file  The path to the key file, relative to the component's backend root and WITHOUT the .php extension
 * - encrypt_key_const The constant for the key. By default it is COMPONENTNAME_FOF_ENCRYPT_SERVICE_SECRETKEY where
 *                     COMPONENTNAME corresponds to the uppercase com_componentname without the com_ prefix.
 *
 * @package     FOF40\Encrypt
 *
 * @since       3.3.2
 */
class EncryptService
{
	/**
	 * The component's container
	 *
	 * @var    Container
	 * @since  3.3.2
	 */
	private $container;

	/**
	 * The encryption engine used by this service
	 *
	 * @var    Aes
	 * @since  3.3.2
	 */
	private $aes;

	/**
	 * EncryptService constructor.
	 *
	 * @param Container $c       The FOF component container
	 *
	 * @since   3.3.2
	 */
	public function __construct(Container $c)
	{
		$this->container = $c;
		$this->initialize();
	}

	/**
	 * Encrypt the plaintext $data and return the ciphertext prefixed by ###AES128###
	 *
	 * @param string $data The plaintext data
	 *
	 * @return  string  The ciphertext, prefixed by ###AES128###
	 *
	 * @since   3.3.2
	 */
	public function encrypt(string $data): string
	{
		if (!is_object($this->aes))
		{
			return $data;
		}

		$encrypted = $this->aes->encryptString($data, true);

		return '###AES128###' . $encrypted;
	}

	/**
	 * Decrypt the ciphertext, prefixed by ###AES128###, and return the plaintext.
	 *
	 * @param   string  $data    The ciphertext, prefixed by ###AES128###
	 * @param   bool    $legacy  Use legacy key expansion? Use it to decrypt data encrypted with FOF 3.
	 *
	 * @return  string  The plaintext data
	 *
	 * @since   3.3.2
	 */
	public function decrypt(string $data, bool $legacy = false): string
	{
		if (substr($data, 0, 12) != '###AES128###')
		{
			return $data;
		}

		$data = substr($data, 12);

		if (!is_object($this->aes))
		{
			return $data;
		}

		$decrypted = $this->aes->decryptString($data, true, $legacy);

		// Decrypted data is null byte padded. We have to remove the padding before proceeding.
		return rtrim($decrypted, "\0");
	}

	/**
	 * Initialize the AES cryptography object
	 *
	 * @return  void
	 * @since   3.3.2
	 *
	 */
	private function initialize(): void
	{
		if (is_object($this->aes))
		{
			return;
		}

		$password = $this->getPassword();

		if (empty($password))
		{
			return;
		}

		$this->aes = new Aes('cbc');
		$this->aes->setPassword($password);
	}

	/**
	 * Returns the path to the secret key file
	 *
	 * @return  string
	 *
	 * @since   3.3.2
	 */
	private function getPasswordFilePath(): string
	{
		$default  = 'encrypt_service_key';
		$baseName = $this->container->appConfig->get('container.encrypt_key_file', $default);
		$baseName = trim($baseName, '/\\');

		return $this->container->backEndPath . '/' . $baseName . '.php';
	}

	/**
	 * Get the name of the constant where the secret key is stored. Remember that this is searched first, before a new
	 * key file is created. You can define this constant anywhere in your code loaded before the encryption service is
	 * first used to prevent a key file being created.
	 *
	 * @return  string
	 *
	 * @since   3.3.2
	 */
	private function getConstantName(): string
	{
		$default = strtoupper($this->container->bareComponentName) . '_FOF_ENCRYPT_SERVICE_SECRETKEY';

		return $this->container->appConfig->get('container.encrypt_key_const', $default);
	}

	/**
	 * Returns the password used to encrypt information in the component
	 *
	 * @return  string
	 *
	 * @since   3.3.2
	 */
	private function getPassword(): string
	{
		$constantName = $this->getConstantName();

		// If we have already read the file just return the key
		if (defined($constantName))
		{
			return constant($constantName);
		}

		// Do I have a secret key file?
		$filePath = $this->getPasswordFilePath();

		// I can't get the path to the file. Cut our losses and assume we can get no key.
		if (empty($filePath))
		{
			define($constantName, '');

			return '';
		}

		// If not, try to create one.
		if (!file_exists($filePath))
		{
			$this->makePasswordFile();
		}

		// We failed to create a new file? Cut our losses and assume we can get no key.
		if (!file_exists($filePath) || !is_readable($filePath))
		{
			define($constantName, '');

			return '';
		}

		// Try to include the key file
		include_once $filePath;

		// The key file contains garbage. Treason! Cut our losses and assume we can get no key.
		if (!defined($constantName))
		{
			define($constantName, '');

			return '';
		}

		// Finally, return the key which was defined in the file (happy path).
		return constant($constantName);
	}

	/**
	 * Create a new secret key file using a long, randomly generated password. The password generator uses a crypto-safe
	 * pseudorandom number generator (PRNG) to ensure suitability of the password for encrypting data at rest.
	 *
	 * @return  void
	 *
	 * @since   3.3.2
	 */
	private function makePasswordFile(): void
	{
		// Get the path to the new secret key file.
		$filePath = $this->getPasswordFilePath();

		// I can't get the path to the file. Sorry.
		if (empty($filePath))
		{
			return;
		}

		$randval      = new Randval();
		$secretKey    = $randval->getRandomPassword(64);
		$constantName = $this->getConstantName();

		$fileContent = "<?" . 'ph' . "p\n\n";
		$fileContent .= <<< END
defined('_JEXEC') or die;

/**
 * This file is automatically generated. It contains a secret key used for encrypting data by the component. Please do
 * not remove, edit or manually replace this file. It will render your existing encrypted data unreadable forever.
 */
 
define('$constantName', '$secretKey');

END;

		$this->container->filesystem->fileWrite($filePath, $fileContent);
	}
}Randval.php000060400000003730152455311230006644 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Encrypt;

defined('_JEXEC') || die();

/**
 * Generates cryptographically-secure random values.
 */
class Randval implements RandvalInterface
{
	/**
	 * Returns a cryptographically secure random value.
	 *
	 * Since we only run on PHP 7+ we can use random_bytes(), which internally uses a crypto safe PRNG. If the function
	 * doesn't exist, Joomla already loads a secure polyfill.
	 *
	 * The reason this method exists is backwards compatibility with older versions of FOF. It also allows us to quickly
	 * address any future issues if Joomla drops the polyfill or otherwise find problems with PHP's random_bytes() on
	 * some weird host (you can't be too carefull when releasing mass-distributed software).
	 *
	 * @param   integer  $bytes  How many bytes to return
	 *
	 * @return  string
	 */
	public function generate(int $bytes = 32): string
	{
		return random_bytes($bytes);
	}

	/**
	 * Return a randomly generated password using safe characters (a-z, A-Z, 0-9).
	 *
	 * @param   int  $length  How many characters long should the password be. Default is 64.
	 *
	 * @return  string
	 *
	 * @since   3.3.2
	 */
	public function getRandomPassword($length = 64)
	{
		$salt     = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
		$base     = strlen($salt);
		$makepass = '';

		/*
		 * Start with a cryptographic strength random string, then convert it to
		 * a string with the numeric base of the salt.
		 * Shift the base conversion on each character so the character
		 * distribution is even, and randomize the start shift so it's not
		 * predictable.
		 */
		$random = $this->generate($length + 1);
		$shift  = ord($random[0]);

		for ($i = 1; $i <= $length; ++$i)
		{
			$makepass .= $salt[($shift + ord($random[$i])) % $base];
			$shift    += ord($random[$i]);
		}

		return $makepass;
	}
}
RandvalInterface.php000060400000000726152455311230010467 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Encrypt;

defined('_JEXEC') || die();

interface RandvalInterface
{
	/**
	 * Returns a cryptographically secure random value.
	 *
	 * @param int $bytes How many random bytes do you want to be returned?
	 *
	 * @return string
	 */
	public function generate(int $bytes = 32): string;
}
Totp.php000060400000011422152455311230006200 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Encrypt;

defined('_JEXEC') || die;

class Totp
{
	/**
	 * @var int The length of the resulting passcode (default: 6 digits)
	 */
	private $passCodeLength = 6;

	/**
	 * @var number The PIN modulo. It is set automatically to log10(passCodeLength)
	 */
	private $pinModulo;

	/**
	 * The length of the secret key, in characters (default: 10)
	 *
	 * @var int
	 */
	private $secretLength = 10;

	/**
	 * The time step between successive TOTPs in seconds (default: 30 seconds)
	 *
	 * @var int
	 */
	private $timeStep = 30;

	/**
	 * The Base32 encoder class
	 *
	 * @var Base32|null
	 */
	private $base32;

	/**
	 * Initialises an RFC6238-compatible TOTP generator. Please note that this
	 * class does not implement the constraint in the last paragraph of §5.2
	 * of RFC6238. It's up to you to ensure that the same user/device does not
	 * retry validation within the same Time Step.
	 *
	 * @param   int     $timeStep        The Time Step (in seconds). Use 30 to be compatible with Google Authenticator.
	 * @param   int     $passCodeLength  The generated passcode length. Default: 6 digits.
	 * @param   int     $secretLength    The length of the secret key. Default: 10 bytes (80 bits).
	 * @param   Base32  $base32          The base32 en/decrypter
	 */
	public function __construct(int $timeStep = 30, int $passCodeLength = 6, int $secretLength = 10, Base32 $base32 = null)
	{
		$this->timeStep       = $timeStep;
		$this->passCodeLength = $passCodeLength;
		$this->secretLength   = $secretLength;
		$this->pinModulo      = 10 ** $this->passCodeLength;

		$this->base32 = is_null($base32) ? new Base32() : $base32;
	}

	/**
	 * Get the time period based on the $time timestamp and the Time Step
	 * defined. If $time is skipped or set to null the current timestamp will
	 * be used.
	 *
	 * @param   int|null  $time  Timestamp
	 *
	 * @return  int  The time period since the UNIX Epoch
	 */
	public function getPeriod(?int $time = null): int
	{
		if (is_null($time))
		{
			$time = time();
		}

		return floor($time / $this->timeStep);
	}

	/**
	 * Check is the given passcode $code is a valid TOTP generated using secret
	 * key $secret
	 *
	 * @param   string  $secret  The Base32-encoded secret key
	 * @param   string  $code    The passcode to check
	 * @param   int     $time    The time to check it against. Leave null to check for the current server time.
	 *
	 * @return boolean True if the code is valid
	 */
	public function checkCode(string $secret, string $code, int $time = null): bool
	{
		$time = $this->getPeriod($time);

		for ($i = -1; $i <= 1; $i++)
		{
			if ($this->getCode($secret, ($time + $i) * $this->timeStep) === $code)
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * Gets the TOTP passcode for a given secret key $secret and a given UNIX
	 * timestamp $time
	 *
	 * @param   string  $secret  The Base32-encoded secret key
	 * @param   int     $time    UNIX timestamp
	 *
	 * @return string
	 */
	public function getCode(string $secret, ?int $time = null): string
	{
		$period = $this->getPeriod($time);
		$secret = $this->base32->decode($secret);

		$time = pack("N", $period);
		$time = str_pad($time, 8, chr(0), STR_PAD_LEFT);

		$hash   = hash_hmac('sha1', $time, $secret, true);
		$offset = ord(substr($hash, -1));
		$offset &= 0xF;

		$truncatedHash = $this->hashToInt($hash, $offset) & 0x7FFFFFFF;

		return str_pad($truncatedHash % $this->pinModulo, $this->passCodeLength, "0", STR_PAD_LEFT);
	}

	/**
	 * Returns a QR code URL for easy setup of TOTP apps like Google Authenticator
	 *
	 * @param   string  $user      User
	 * @param   string  $hostname  Hostname
	 * @param   string  $secret    Secret string
	 *
	 * @return  string
	 */
	public function getUrl(string $user, string $hostname, string $secret): string
	{
		$url     = sprintf("otpauth://totp/%s@%s?secret=%s", $user, $hostname, $secret);
		$encoder = "https://chart.googleapis.com/chart?chs=200x200&chld=Q|2&cht=qr&chl=";

		return $encoder . urlencode($url);
	}

	/**
	 * Generates a (semi-)random Secret Key for TOTP generation
	 *
	 * @return  string
	 */
	public function generateSecret(): string
	{
		$secret = "";

		for ($i = 1; $i <= $this->secretLength; $i++)
		{
			$c      = random_int(0, 255);
			$secret .= pack("c", $c);
		}

		return $this->base32->encode($secret);
	}

	/**
	 * Extracts a part of a hash as an integer
	 *
	 * @param   string  $bytes  The hash
	 * @param   string  $start  The char to start from (0 = first char)
	 *
	 * @return  string
	 */
	protected function hashToInt(string $bytes, string $start): string
	{
		$input = substr($bytes, $start, strlen($bytes) - $start);
		$val2  = unpack("N", substr($input, 0, 4));

		return $val2[1];
	}
}
Base32.php000060400000011263152455311230006274 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Encrypt;

defined('_JEXEC') || die;

use InvalidArgumentException;

/**
 * Base32 encoding class, used by the TOTP
 */
class Base32
{
	/**
	 * CSRFC3548
	 *
	 * The character set as defined by RFC3548
	 * @link http://www.ietf.org/rfc/rfc3548.txt
	 */
	const CSRFC3548 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';

	/**
	 * Convert any string to a base32 string
	 * This should be binary safe...
	 *
	 * @param   string  $str  The string to convert
	 *
	 * @return  string  The converted base32 string
	 */
	public function encode(string $str): string
	{
		return $this->fromBin($this->str2bin($str));
	}

	/**
	 * Convert any base32 string to a normal sctring
	 * This should be binary safe...
	 *
	 * @param   string  $str  The base32 string to convert
	 *
	 * @return  string  The normal string
	 */
	public function decode(string $str): string
	{
		$str = strtoupper($str);

		return $this->bin2str($this->tobin($str));
	}

	/**
	 * Converts any ascii string to a binary string
	 *
	 * @param   string  $str  The string you want to convert
	 *
	 * @return  string  String of 0's and 1's
	 */
	private function str2bin(string $str): string
	{
		$chrs = unpack('C*', $str);

		return vsprintf(str_repeat('%08b', is_array($chrs) || $chrs instanceof \Countable ? count($chrs) : 0), $chrs);
	}

	/**
	 * Converts a binary string to an ascii string
	 *
	 * @param   string  $str  The string of 0's and 1's you want to convert
	 *
	 * @return  string  The ascii output
	 *
	 * @throws  InvalidArgumentException
	 */
	private function bin2str(string $str): string
	{
		if (strlen($str) % 8 > 0)
		{
			throw new InvalidArgumentException('Length must be divisible by 8');
		}

		if (!preg_match('/^[01]+$/', $str))
		{
			throw new InvalidArgumentException('Only 0\'s and 1\'s are permitted');
		}

		preg_match_all('/.{8}/', $str, $chrs);
		$chrs = array_map('bindec', $chrs[0]);

		// I'm just being slack here
		array_unshift($chrs, 'C*');

		return call_user_func_array('pack', $chrs);
	}

	/**
	 * Converts a correct binary string to base32
	 *
	 * @param   string  $str  The string of 0's and 1's you want to convert
	 *
	 * @return  string  String encoded as base32
	 *
	 * @throws  InvalidArgumentException
	 */
	private function fromBin(string $str): string
	{
		if (strlen($str) % 8 > 0)
		{
			throw new InvalidArgumentException('Length must be divisible by 8');
		}

		if (!preg_match('/^[01]+$/', $str))
		{
			throw new InvalidArgumentException('Only 0\'s and 1\'s are permitted');
		}

		// Base32 works on the first 5 bits of a byte, so we insert blanks to pad it out
		$str = preg_replace('/(.{5})/', '000$1', $str);

		// We need a string divisible by 5
		$length = strlen($str);
		$rbits  = $length & 7;

		if ($rbits > 0)
		{
			// Excessive bits need to be padded
			$ebits = substr($str, $length - $rbits);
			$str   = substr($str, 0, $length - $rbits);
			$str   .= "000$ebits" . str_repeat('0', 5 - strlen($ebits));
		}

		preg_match_all('/.{8}/', $str, $chrs);
		$chrs = array_map([$this, 'mapCharset'], $chrs[0]);

		return implode('', $chrs);
	}

	/**
	 * Accepts a base32 string and returns an ascii binary string
	 *
	 * @param   string  $str  The base32 string to convert
	 *
	 * @return  string  Ascii binary string
	 *
	 * @throws  InvalidArgumentException
	 */
	private function toBin(string $str): string
	{
		if (!preg_match('/^[' . self::CSRFC3548 . ']+$/', $str))
		{
			throw new InvalidArgumentException('Base64 string must match character set');
		}

		// Convert the base32 string back to a binary string
		$str = join('', array_map([$this, 'mapBin'], str_split($str)));

		// Remove the extra 0's we added
		$str = preg_replace('/000(.{5})/', '$1', $str);

		// Remove padding if necessary
		$length = strlen($str);
		$rbits  = $length & 7;

		if ($rbits > 0)
		{
			$str = substr($str, 0, $length - $rbits);
		}

		return $str;
	}

	/**
	 * Used with array_map to map the bits from a binary string
	 * directly into a base32 character set
	 *
	 * @param   string  $str  The string of 0's and 1's you want to convert
	 *
	 * @return  string  Resulting base32 character
	 *
	 * @access private
	 */
	private function mapCharset(string $str): string
	{
		// Huh!
		$x = self::CSRFC3548;

		return $x[bindec($str)];
	}

	/**
	 * Used with array_map to map the characters from a base32
	 * character set directly into a binary string
	 *
	 * @param   string  $chr  The character to map
	 *
	 * @return  string  String of 0's and 1's
	 *
	 * @access private
	 */
	private function mapBin(string $chr): string
	{
		return sprintf('%08b', strpos(self::CSRFC3548, $chr));
	}

} 
Aes.php000060400000017320152455311230005765 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Encrypt;

defined('_JEXEC') || die;

use FOF40\Encrypt\AesAdapter\AdapterInterface;
use FOF40\Encrypt\AesAdapter\OpenSSL;

/**
 * A simple abstraction to AES encryption
 *
 * Usage:
 *
 * // Create a new instance.
 * $aes = new Aes();
 * // Set the encryption password. It's expanded to a key automatically.
 * $aes->setPassword('yourPassword');
 * // Encrypt something.
 * $cipherText = $aes->encryptString($sourcePlainText);
 * // Decrypt something
 * $plainText = $aes->decryptString($sourceCipherText);
 */
class Aes
{
	/**
	 * The cipher key.
	 *
	 * @var   string
	 */
	private $key = '';

	/**
	 * The AES encryption adapter in use.
	 *
	 * @var  AdapterInterface
	 */
	private $adapter;

	/**
	 * Initialise the AES encryption object.
	 *
	 * @param   string   $mode     Encryption mode. Can be ebc or cbc. We recommend using cbc.
	 */
	public function __construct(string $mode = 'cbc')
	{
		$this->adapter = new OpenSSL();

		$this->adapter->setEncryptionMode($mode);
	}

	/**
	 * Is AES encryption supported by this PHP installation?
	 *
	 * @return boolean
	 */
	public static function isSupported(): bool
	{
		$adapter = new OpenSSL();

		if (!$adapter->isSupported())
		{
			return false;
		}

		if (!\function_exists('base64_encode'))
		{
			return false;
		}

		if (!\function_exists('base64_decode'))
		{
			return false;
		}

		if (!\function_exists('hash_algos'))
		{
			return false;
		}

		$algorithms = \hash_algos();

		return in_array('sha256', $algorithms);
	}

	/**
	 * Sets the password for this instance.
	 *
	 * @param   string  $password  The password (either user-provided password or binary encryption key) to use
	 */
	public function setPassword(string $password)
	{
		$this->key = $password;
	}

	/**
	 * Encrypts a string using AES
	 *
	 * @param   string  $stringToEncrypt  The plaintext to encrypt
	 * @param   bool    $base64encoded    Should I Base64-encode the result?
	 *
	 * @return   string  The cryptotext. Please note that the first 16 bytes of
	 *                   the raw string is the IV (initialisation vector) which
	 *                   is necessary for decoding the string.
	 */
	public function encryptString(string $stringToEncrypt, bool $base64encoded = true): string
	{
		$blockSize = $this->adapter->getBlockSize();
		$randVal   = new Randval();
		$iv        = $randVal->generate($blockSize);

		$key        = $this->getExpandedKey($blockSize, $iv);
		$cipherText = $this->adapter->encrypt($stringToEncrypt, $key, $iv);

		// Optionally pass the result through Base64 encoding
		if ($base64encoded)
		{
			$cipherText = base64_encode($cipherText);
		}

		// Return the result
		return $cipherText;
	}

	/**
	 * Decrypts a ciphertext into a plaintext string using AES
	 *
	 * @param   string  $stringToDecrypt  The ciphertext to decrypt. The first 16 bytes of the raw string must contain
	 *                                    the IV (initialisation vector).
	 * @param   bool    $base64encoded    Should I Base64-decode the data before decryption?
	 * @param   bool    $legacy           Use legacy key expansion? Use it to decrypt date encrypted with FOF 3.
	 *
	 * @return   string  The plain text string
	 */
	public function decryptString(string $stringToDecrypt, bool $base64encoded = true, bool $legacy = false): string
	{
		if ($base64encoded)
		{
			$stringToDecrypt = base64_decode($stringToDecrypt);
		}

		// Extract IV
		$iv_size = $this->adapter->getBlockSize();
		$strLen  = function_exists('mb_strlen') ? mb_strlen($stringToDecrypt, 'ASCII') : strlen($stringToDecrypt);

		// If the string is not big enough to have an Initialization Vector in front then, clearly, it is not encrypted.
		if ($strLen < $iv_size)
		{
			return '';
		}

		// Get the IV, the key and decrypt the string
		$iv  = substr($stringToDecrypt, 0, $iv_size);
		$key = $this->getExpandedKey($iv_size, $iv, $legacy);

		return $this->adapter->decrypt($stringToDecrypt, $key);
	}

	/**
	 * Performs key expansion using PBKDF2
	 *
	 * CAVEAT: If your password ($this->key) is the same size as $blockSize you don't get key expansion. Practically,
	 * it means that you should avoid using 16 byte passwords.
	 *
	 * @param   int     $blockSize  Block size in bytes. This should always be 16 since we only deal with 128-bit AES
	 *                              here.
	 * @param   string  $iv         The initial vector. Use Randval::generate($blockSize)
	 * @param   bool    $legacy     Use legacy key expansion? Only ever use to decrypt data encrypted with FOF 3.
	 *
	 * @return string
	 */
	public function getExpandedKey(int $blockSize, string $iv, bool $legacy = false): string
	{
		$key        = $legacy ? $this->legacyKey($this->key) : $this->key;
		$passLength = strlen($key);

		if (function_exists('mb_strlen'))
		{
			$passLength = mb_strlen($key, 'ASCII');
		}

		if ($passLength !== $blockSize)
		{
			$iterations = 1000;
			$salt       = $this->adapter->resizeKey($iv, 16);
			$key        = hash_pbkdf2('sha256', $this->key, $salt, $iterations, $blockSize, true);
		}

		return $key;
	}

	/**
	 * Process the password the same way FOF 3 did.
	 *
	 * This is a very bad idea. It would get a password, calculate its SHA-256 and throw half of it away. The rest was
	 * used as the encryption key. In FOF 4 we use a far more sane key expansion using PKKDF2 with SHA-256 and 1000
	 * rounds.
	 *
	 * @param   $password
	 *
	 * @return  string
	 * @since   4.0.0
	 */
	private function legacyKey($password): string
	{
		$passLength = strlen($password);

		if (function_exists('mb_strlen'))
		{
			$passLength = mb_strlen($password, 'ASCII');
		}

		if ($passLength === 32)
		{
			return $password;
		}

		// Legacy mode was doing something stupid, requiring a key of 32 bytes. DO NOT USE LEGACY MODE!
		// Legacy mode: use the sha256 of the password
		$key = hash('sha256', $password, true);
		// We have to trim or zero pad the password (we end up throwing half of it away in Rijndael-128 / AES...)
		$key = $this->adapter->resizeKey($key, $this->adapter->getBlockSize());

		return $key;
	}
}

/**
 * Compatibility mode for servers lacking the hash_pbkdf2 PHP function (typically, the hash extension is installed but
 * PBKDF2 was not compiled into it). This is really slow but since it's used sparingly you shouldn't notice a
 * substantial performance degradation under most circumstances.
 */
if (!function_exists('hash_pbkdf2'))
{
	function hash_pbkdf2($algo, $password, $salt, $count, $length = 0, $raw_output = false)
	{
		if (!in_array(strtolower($algo), hash_algos()))
		{
			trigger_error(__FUNCTION__ . '(): Unknown hashing algorithm: ' . $algo, E_USER_WARNING);
		}

		if (!is_numeric($count))
		{
			trigger_error(__FUNCTION__ . '(): expects parameter 4 to be long, ' . gettype($count) . ' given', E_USER_WARNING);
		}

		if (!is_numeric($length))
		{
			trigger_error(__FUNCTION__ . '(): expects parameter 5 to be long, ' . gettype($length) . ' given', E_USER_WARNING);
		}

		if ($count <= 0)
		{
			trigger_error(__FUNCTION__ . '(): Iterations must be a positive integer: ' . $count, E_USER_WARNING);
		}

		if ($length < 0)
		{
			trigger_error(__FUNCTION__ . '(): Length must be greater than or equal to 0: ' . $length, E_USER_WARNING);
		}

		$output      = '';
		$block_count = $length ? ceil($length / strlen(hash($algo, '', $raw_output))) : 1;

		for ($i = 1; $i <= $block_count; $i++)
		{
			$last = $xorsum = hash_hmac($algo, $salt . pack('N', $i), $password, true);

			for ($j = 1; $j < $count; $j++)
			{
				$xorsum ^= ($last = hash_hmac($algo, $last, $password, true));
			}

			$output .= $xorsum;
		}

		if (!$raw_output)
		{
			$output = bin2hex($output);
		}

		return $length ? substr($output, 0, $length) : $output;
	}
}
AesAdapter/AdapterInterface.php000060400000004444152455311230012472 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Encrypt\AesAdapter;

defined('_JEXEC') || die;

/**
 * Interface for AES encryption adapters
 */
interface AdapterInterface
{
	/**
	 * Sets the AES encryption mode.
	 *
	 * @param string $mode Choose between CBC (recommended) or ECB
	 *
	 * @return  void
	 */
	public function setEncryptionMode(string $mode = 'cbc'): void;

	/**
	 * Encrypts a string. Returns the raw binary ciphertext.
	 *
	 * WARNING: The plaintext is zero-padded to the algorithm's block size. You are advised to store the size of the
	 * plaintext and trim the string to that length upon decryption.
	 *
	 * @param string      $plainText The plaintext to encrypt
	 * @param string      $key       The raw binary key (will be zero-padded or chopped if its size is different than the block size)
	 * @param null|string $iv        The initialization vector (for CBC mode algorithms)
	 *
	 * @return  string  The raw encrypted binary string.
	 */
	public function encrypt(string $plainText, string $key, ?string $iv = null): string;

	/**
	 * Decrypts a string. Returns the raw binary plaintext.
	 *
	 * $ciphertext MUST start with the IV followed by the ciphertext, even for EBC data (the first block of data is
	 * dropped in EBC mode since there is no concept of IV in EBC).
	 *
	 * WARNING: The returned plaintext is zero-padded to the algorithm's block size during encryption. You are advised
	 * to trim the string to the original plaintext's length upon decryption. While rtrim($decrypted, "\0") sounds
	 * appealing it's NOT the correct approach for binary data (zero bytes may actually be part of your plaintext, not
	 * just padding!).
	 *
	 * @param string $cipherText The ciphertext to encrypt
	 * @param string $key        The raw binary key (will be zero-padded or chopped if its size is different than the block size)
	 *
	 * @return  string  The raw unencrypted binary string.
	 */
	public function decrypt(string $cipherText, string $key): string;

	/**
	 * Returns the encryption block size in bytes
	 *
	 * @return  int
	 */
	public function getBlockSize(): int;

	/**
	 * Is this adapter supported?
	 *
	 * @return  bool
	 */
	public function isSupported(): bool;
}
AesAdapter/OpenSSL.php000060400000007131152455311230010550 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Encrypt\AesAdapter;

defined('_JEXEC') || die;

use FOF40\Encrypt\Randval;

class OpenSSL extends AbstractAdapter implements AdapterInterface
{
	/**
	 * The OpenSSL options for encryption / decryption
	 *
	 * PHP 5.3 does not have the constants OPENSSL_RAW_DATA and OPENSSL_ZERO_PADDING. In fact, the parameter
	 * is called $raw_data and is a boolean. Since integer 1 is equivalent to boolean TRUE in PHP we can get
	 * away with initializing this parameter with the integer 1.
	 *
	 * @var  int
	 */
	protected $openSSLOptions = 1;

	/**
	 * The encryption method to use
	 *
	 * @var  string
	 */
	protected $method = 'aes-128-cbc';

	public function __construct()
	{
		/**
		 * PHP 5.4 and later replaced the $raw_data parameter with the $options parameter. Instead of a boolean we need
		 * to pass some flags.
		 *
		 * See http://stackoverflow.com/questions/24707007/using-openssl-raw-data-param-in-openssl-decrypt-with-php-5-3#24707117
		 */
		$this->openSSLOptions = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING;
	}

	public function setEncryptionMode(string $mode = 'cbc'): void
	{
		static $availableAlgorithms = null;
		static $defaultAlgo = 'aes-128-cbc';

		if (!is_array($availableAlgorithms))
		{
			$availableAlgorithms = openssl_get_cipher_methods();

			foreach ([
				         'aes-256-cbc', 'aes-256-ecb', 'aes-192-cbc',
				         'aes-192-ecb', 'aes-128-cbc', 'aes-128-ecb',
			         ] as $algo)
			{
				if (in_array($algo, $availableAlgorithms))
				{
					$defaultAlgo = $algo;
					break;
				}
			}
		}

		$mode = strtolower($mode);

		if (!in_array($mode, ['cbc', 'ebc']))
		{
			$mode = 'cbc';
		}

		$algo = 'aes-128-' . $mode;

		if (!in_array($algo, $availableAlgorithms))
		{
			$algo = $defaultAlgo;
		}

		$this->method = $algo;
	}

	public function encrypt(string $plainText, string $key, ?string $iv = null): string
	{
		$iv_size = $this->getBlockSize();
		$key     = $this->resizeKey($key, $iv_size);
		$iv      = $this->resizeKey($iv, $iv_size);

		if (empty($iv))
		{
			$randVal = new Randval();
			$iv      = $randVal->generate($iv_size);
		}

		$plainText  .= $this->getZeroPadding($plainText, $iv_size);
		$cipherText = openssl_encrypt($plainText, $this->method, $key, $this->openSSLOptions, $iv);

		return $iv . $cipherText;
	}

	public function decrypt(string $cipherText, string $key): string
	{
		$iv_size    = $this->getBlockSize();
		$key        = $this->resizeKey($key, $iv_size);
		$iv         = substr($cipherText, 0, $iv_size);
		$cipherText = substr($cipherText, $iv_size);

		return openssl_decrypt($cipherText, $this->method, $key, $this->openSSLOptions, $iv);
	}

	public function isSupported(): bool
	{
		if (!\function_exists('openssl_get_cipher_methods'))
		{
			return false;
		}

		if (!\function_exists('openssl_random_pseudo_bytes'))
		{
			return false;
		}

		if (!\function_exists('openssl_cipher_iv_length'))
		{
			return false;
		}

		if (!\function_exists('openssl_encrypt'))
		{
			return false;
		}

		if (!\function_exists('openssl_decrypt'))
		{
			return false;
		}

		if (!\function_exists('hash'))
		{
			return false;
		}

		if (!\function_exists('hash_algos'))
		{
			return false;
		}

		$algorithms = \openssl_get_cipher_methods();

		if (!in_array('aes-128-cbc', $algorithms))
		{
			return false;
		}

		$algorithms = \hash_algos();

		return in_array('sha256', $algorithms);
	}

	/**
	 * @return int
	 */
	public function getBlockSize(): int
	{
		return openssl_cipher_iv_length($this->method);
	}
}
AesAdapter/AbstractAdapter.php000060400000003503152455311230012330 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Encrypt\AesAdapter;

defined('_JEXEC') || die();

/**
 * Abstract AES encryption class
 */
abstract class AbstractAdapter
{
	/**
	 * Trims or zero-pads a key / IV
	 *
	 * @param string $key  The key or IV to treat
	 * @param int    $size The block size of the currently used algorithm
	 *
	 * @return  null|string  Null if $key is null, treated string of $size byte length otherwise
	 */
	public function resizeKey(string $key, int $size): ?string
	{
		if (empty($key))
		{
			return null;
		}

		$keyLength = strlen($key);

		if (function_exists('mb_strlen'))
		{
			$keyLength = mb_strlen($key, 'ASCII');
		}

		if ($keyLength === $size)
		{
			return $key;
		}

		if ($keyLength > $size)
		{
			if (function_exists('mb_substr'))
			{
				return mb_substr($key, 0, $size, 'ASCII');
			}

			return substr($key, 0, $size);
		}

		return $key . str_repeat("\0", ($size - $keyLength));
	}

	/**
	 * Returns null bytes to append to the string so that it's zero padded to the specified block size
	 *
	 * @param string $string    The binary string which will be zero padded
	 * @param int    $blockSize The block size
	 *
	 * @return  string  The zero bytes to append to the string to zero pad it to $blockSize
	 */
	protected function getZeroPadding(string $string, int $blockSize): string
	{
		$stringSize = strlen($string);

		if (function_exists('mb_strlen'))
		{
			$stringSize = mb_strlen($string, 'ASCII');
		}

		if ($stringSize === $blockSize)
		{
			return '';
		}

		if ($stringSize < $blockSize)
		{
			return str_repeat("\0", $blockSize - $stringSize);
		}

		$paddingBytes = $stringSize % $blockSize;

		return str_repeat("\0", $blockSize - $paddingBytes);
	}
}