primo commit
This commit is contained in:
277
libraries/fof30/Encrypt/Aes.php
Normal file
277
libraries/fof30/Encrypt/Aes.php
Normal file
@ -0,0 +1,277 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Encrypt;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Encrypt\AesAdapter\AdapterInterface;
|
||||
use FOF30\Encrypt\AesAdapter\OpenSSL;
|
||||
use FOF30\Utils\Phpfunc;
|
||||
|
||||
/**
|
||||
* A simple abstraction to AES encryption
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* // Create a new instance. The key is ignored – only use it if you have legacy encrypted content you need to decrypt
|
||||
* $aes = new Aes('ignored');
|
||||
* // Set the password. Do not use uf you have legacy encrypted content you need to decrypt
|
||||
* $aes->setPassword('yourRealPassword');
|
||||
* // 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.
|
||||
*
|
||||
* Note: If the key is not 16 bytes this class will do a stupid key expansion for legacy reasons (produce the
|
||||
* SHA-256 of the key string and throw away half of it).
|
||||
*
|
||||
* @param string $key The encryption key (password). It can be a raw key (16 bytes) or a passphrase.
|
||||
* @param int $strength Bit strength (128, 192 or 256) – ALWAYS USE 128 BITS. THIS PARAMETER IS DEPRECATED.
|
||||
* @param string $mode Encryption mode. Can be ebc or cbc. We recommend using cbc.
|
||||
* @param Phpfunc $phpfunc For testing
|
||||
*/
|
||||
public function __construct($key, $strength = 128, $mode = 'cbc', Phpfunc $phpfunc = null)
|
||||
{
|
||||
$this->adapter = new OpenSSL();
|
||||
|
||||
if (!$this->adapter->isSupported($phpfunc))
|
||||
{
|
||||
throw new \RuntimeException('Your server does not have the PHP OpenSSL extension enabled. This is required for encryption handling.');
|
||||
}
|
||||
|
||||
$this->adapter->setEncryptionMode($mode, $strength);
|
||||
$this->setPassword($key, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is AES encryption supported by this PHP installation?
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isSupported(Phpfunc $phpfunc = null)
|
||||
{
|
||||
if (!is_object($phpfunc) || !($phpfunc instanceof $phpfunc))
|
||||
{
|
||||
$phpfunc = new Phpfunc();
|
||||
}
|
||||
|
||||
$adapter = new OpenSSL();
|
||||
|
||||
if (!$adapter->isSupported($phpfunc))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('base64_encode'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('base64_decode'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('hash_algos'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$algorightms = $phpfunc->hash_algos();
|
||||
|
||||
if (!in_array('sha256', $algorightms))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the password for this instance.
|
||||
*
|
||||
* WARNING: Do not use the legacy mode, it's insecure
|
||||
*
|
||||
* @param string $password The password (either user-provided password or binary encryption key) to use
|
||||
* @param bool $legacyMode True to use the legacy key expansion. We recommend against using it.
|
||||
*/
|
||||
public function setPassword($password, $legacyMode = false)
|
||||
{
|
||||
$this->key = $password;
|
||||
|
||||
$passLength = strlen($password);
|
||||
|
||||
if (function_exists('mb_strlen'))
|
||||
{
|
||||
$passLength = mb_strlen($password, 'ASCII');
|
||||
}
|
||||
|
||||
// Legacy mode was doing something stupid, requiring a key of 32 bytes. DO NOT USE LEGACY MODE!
|
||||
if ($legacyMode && ($passLength != 32))
|
||||
{
|
||||
// Legacy mode: use the sha256 of the password
|
||||
$this->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...)
|
||||
$this->key = $this->adapter->resizeKey($this->key, $this->adapter->getBlockSize());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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($stringToEncrypt, $base64encoded = true)
|
||||
{
|
||||
$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?
|
||||
*
|
||||
* @return string The plain text string
|
||||
*/
|
||||
public function decryptString($stringToDecrypt, $base64encoded = true)
|
||||
{
|
||||
if ($base64encoded)
|
||||
{
|
||||
$stringToDecrypt = base64_decode($stringToDecrypt);
|
||||
}
|
||||
|
||||
// Extract IV
|
||||
$iv_size = $this->adapter->getBlockSize();
|
||||
$iv = substr($stringToDecrypt, 0, $iv_size);
|
||||
$key = $this->getExpandedKey($iv_size, $iv);
|
||||
|
||||
// Decrypt the data
|
||||
$plainText = $this->adapter->decrypt($stringToDecrypt, $key);
|
||||
|
||||
return $plainText;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $blockSize
|
||||
* @param $iv
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getExpandedKey($blockSize, $iv)
|
||||
{
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
88
libraries/fof30/Encrypt/AesAdapter/AbstractAdapter.php
Normal file
88
libraries/fof30/Encrypt/AesAdapter/AbstractAdapter.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\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($key, $size)
|
||||
{
|
||||
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, $blockSize)
|
||||
{
|
||||
$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);
|
||||
}
|
||||
}
|
||||
78
libraries/fof30/Encrypt/AesAdapter/AdapterInterface.php
Normal file
78
libraries/fof30/Encrypt/AesAdapter/AdapterInterface.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Encrypt\AesAdapter;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Utils\Phpfunc;
|
||||
|
||||
/**
|
||||
* Interface for AES encryption adapters
|
||||
*/
|
||||
interface AdapterInterface
|
||||
{
|
||||
/**
|
||||
* Sets the AES encryption mode.
|
||||
*
|
||||
* WARNING: The strength parameter is deprecated since FOF 3.1 and has no effect.
|
||||
*
|
||||
* @param string $mode Choose between CBC (recommended) or ECB
|
||||
* @param int $strength DEPRECATED AND UNUSED.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function setEncryptionMode($mode = 'cbc', $strength = 128);
|
||||
|
||||
/**
|
||||
* 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($plainText, $key, $iv = null);
|
||||
|
||||
/**
|
||||
* 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($cipherText, $key);
|
||||
|
||||
/**
|
||||
* Returns the encryption block size in bytes
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getBlockSize();
|
||||
|
||||
/**
|
||||
* Is this adapter supported?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSupported(Phpfunc $phpfunc = null);
|
||||
}
|
||||
192
libraries/fof30/Encrypt/AesAdapter/OpenSSL.php
Normal file
192
libraries/fof30/Encrypt/AesAdapter/OpenSSL.php
Normal file
@ -0,0 +1,192 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Encrypt\AesAdapter;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Encrypt\Randval;
|
||||
use FOF30\Utils\Phpfunc;
|
||||
|
||||
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. Here you go.
|
||||
*
|
||||
* Since PHP 5.3 does NOT have the relevant constants we must NOT run this bit of code under PHP 5.3.
|
||||
*
|
||||
* See http://stackoverflow.com/questions/24707007/using-openssl-raw-data-param-in-openssl-decrypt-with-php-5-3#24707117
|
||||
*/
|
||||
if (version_compare(PHP_VERSION, '5.4.0', 'ge'))
|
||||
{
|
||||
$this->openSSLOptions = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING;
|
||||
}
|
||||
}
|
||||
|
||||
public function setEncryptionMode($mode = 'cbc', $strength = 128)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$strength = (int) $strength;
|
||||
$mode = strtolower($mode);
|
||||
|
||||
if (!in_array($strength, [128, 192, 256]))
|
||||
{
|
||||
$strength = 256;
|
||||
}
|
||||
|
||||
if (!in_array($mode, ['cbc', 'ebc']))
|
||||
{
|
||||
$mode = 'cbc';
|
||||
}
|
||||
|
||||
$algo = 'aes-' . $strength . '-' . $mode;
|
||||
|
||||
if (!in_array($algo, $availableAlgorithms))
|
||||
{
|
||||
$algo = $defaultAlgo;
|
||||
}
|
||||
|
||||
$this->method = $algo;
|
||||
}
|
||||
|
||||
public function encrypt($plainText, $key, $iv = null)
|
||||
{
|
||||
$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);
|
||||
$cipherText = $iv . $cipherText;
|
||||
|
||||
return $cipherText;
|
||||
}
|
||||
|
||||
public function decrypt($cipherText, $key)
|
||||
{
|
||||
$iv_size = $this->getBlockSize();
|
||||
$key = $this->resizeKey($key, $iv_size);
|
||||
$iv = substr($cipherText, 0, $iv_size);
|
||||
$cipherText = substr($cipherText, $iv_size);
|
||||
$plainText = openssl_decrypt($cipherText, $this->method, $key, $this->openSSLOptions, $iv);
|
||||
|
||||
return $plainText;
|
||||
}
|
||||
|
||||
public function isSupported(Phpfunc $phpfunc = null)
|
||||
{
|
||||
if (!is_object($phpfunc) || !($phpfunc instanceof $phpfunc))
|
||||
{
|
||||
$phpfunc = new Phpfunc();
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('openssl_get_cipher_methods'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('openssl_random_pseudo_bytes'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('openssl_cipher_iv_length'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('openssl_encrypt'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('openssl_decrypt'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('hash'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('hash_algos'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$algorightms = $phpfunc->openssl_get_cipher_methods();
|
||||
|
||||
if (!in_array('aes-128-cbc', $algorightms))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$algorightms = $phpfunc->hash_algos();
|
||||
|
||||
if (!in_array('sha256', $algorightms))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getBlockSize()
|
||||
{
|
||||
return openssl_cipher_iv_length($this->method);
|
||||
}
|
||||
}
|
||||
208
libraries/fof30/Encrypt/Base32.php
Normal file
208
libraries/fof30/Encrypt/Base32.php
Normal file
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\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
|
||||
*/
|
||||
public 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($str)
|
||||
{
|
||||
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($str)
|
||||
{
|
||||
$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($str)
|
||||
{
|
||||
$chrs = unpack('C*', $str);
|
||||
|
||||
return vsprintf(str_repeat('%08b', count($chrs)), $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($str)
|
||||
{
|
||||
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($str)
|
||||
{
|
||||
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($str)
|
||||
{
|
||||
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 = implode('', 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($str)
|
||||
{
|
||||
// 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($chr)
|
||||
{
|
||||
return sprintf('%08b', strpos(self::CSRFC3548, $chr));
|
||||
}
|
||||
|
||||
}
|
||||
277
libraries/fof30/Encrypt/EncryptService.php
Normal file
277
libraries/fof30/Encrypt/EncryptService.php
Normal file
@ -0,0 +1,277 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Encrypt;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Container\Container;
|
||||
use FOF30\Utils\Phpfunc;
|
||||
|
||||
/**
|
||||
* 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 FOF30\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($data)
|
||||
{
|
||||
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###
|
||||
*
|
||||
* @return string The plaintext data
|
||||
*
|
||||
* @since 3.3.2
|
||||
*/
|
||||
public function decrypt($data)
|
||||
{
|
||||
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);
|
||||
|
||||
// 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()
|
||||
{
|
||||
if (is_object($this->aes))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$password = $this->getPassword();
|
||||
|
||||
if (empty($password))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$phpFunc = new Phpfunc();
|
||||
$this->aes = new Aes($password, 128, 'cbc', $phpFunc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the secret key file
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.3.2
|
||||
*/
|
||||
private function getPasswordFilePath()
|
||||
{
|
||||
$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()
|
||||
{
|
||||
$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
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.3.2
|
||||
*/
|
||||
private function getPassword()
|
||||
{
|
||||
$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()
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
$phpFunc = new Phpfunc();
|
||||
$randval = new Randval($phpFunc);
|
||||
$secretKey = $randval->getRandomPassword(64);
|
||||
$constantName = $this->getConstantName();
|
||||
|
||||
$fileContent = "<?" . 'ph' . "p\n\n";
|
||||
$fileContent .= <<< END
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
229
libraries/fof30/Encrypt/Randval.php
Normal file
229
libraries/fof30/Encrypt/Randval.php
Normal file
@ -0,0 +1,229 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Encrypt;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Utils\Phpfunc;
|
||||
|
||||
/**
|
||||
* Generates cryptographically-secure random values.
|
||||
*/
|
||||
class Randval implements RandvalInterface
|
||||
{
|
||||
/**
|
||||
* @var Phpfunc
|
||||
*/
|
||||
protected $phpfunc;
|
||||
|
||||
/**
|
||||
*
|
||||
* Constructor.
|
||||
*
|
||||
* @param Phpfunc $phpfunc An object to intercept PHP function calls;
|
||||
* this makes testing easier.
|
||||
*
|
||||
*/
|
||||
public function __construct(Phpfunc $phpfunc = null)
|
||||
{
|
||||
if (!is_object($phpfunc) || !($phpfunc instanceof Phpfunc))
|
||||
{
|
||||
$phpfunc = new Phpfunc();
|
||||
}
|
||||
|
||||
$this->phpfunc = $phpfunc;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns a cryptographically secure random value.
|
||||
*
|
||||
* @param integer $bytes How many bytes to return
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generate($bytes = 32)
|
||||
{
|
||||
if ($this->phpfunc->extension_loaded('openssl') && (version_compare(PHP_VERSION, '5.3.4') >= 0 || IS_WIN))
|
||||
{
|
||||
$strong = false;
|
||||
$randBytes = openssl_random_pseudo_bytes($bytes, $strong);
|
||||
|
||||
if ($strong)
|
||||
{
|
||||
return $randBytes;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->genRandomBytes($bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random bytes. Adapted from Joomla! 3.2.
|
||||
*
|
||||
* @param integer $length Length of the random data to generate
|
||||
*
|
||||
* @return string Random binary data
|
||||
*/
|
||||
public function genRandomBytes($length = 32)
|
||||
{
|
||||
$length = (int) $length;
|
||||
$sslStr = '';
|
||||
|
||||
/*
|
||||
* Collect any entropy available in the system along with a number
|
||||
* of time measurements of operating system randomness.
|
||||
*/
|
||||
$bitsPerRound = 2;
|
||||
$maxTimeMicro = 400;
|
||||
$shaHashLength = 20;
|
||||
$randomStr = '';
|
||||
$total = $length;
|
||||
|
||||
// Check if we can use /dev/urandom.
|
||||
$urandom = false;
|
||||
$handle = null;
|
||||
|
||||
// This is PHP 5.3.3 and up
|
||||
if ($this->phpfunc->function_exists('stream_set_read_buffer') && @is_readable('/dev/urandom'))
|
||||
{
|
||||
$handle = @fopen('/dev/urandom', 'rb');
|
||||
|
||||
if ($handle)
|
||||
{
|
||||
$urandom = true;
|
||||
}
|
||||
}
|
||||
|
||||
while ($length > strlen($randomStr))
|
||||
{
|
||||
$bytes = ($total > $shaHashLength) ? $shaHashLength : $total;
|
||||
$total -= $bytes;
|
||||
|
||||
/*
|
||||
* Collect any entropy available from the PHP system and filesystem.
|
||||
* If we have ssl data that isn't strong, we use it once.
|
||||
*/
|
||||
$entropy = random_int(0, mt_getrandmax()) . uniqid(random_int(0, mt_getrandmax()), true) . $sslStr;
|
||||
|
||||
$entropy .= implode('', @fstat(fopen(__FILE__, 'r')));
|
||||
$entropy .= memory_get_usage();
|
||||
$sslStr = '';
|
||||
|
||||
if ($urandom)
|
||||
{
|
||||
stream_set_read_buffer($handle, 0);
|
||||
$entropy .= @fread($handle, $bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* There is no external source of entropy so we repeat calls
|
||||
* to mt_rand until we are assured there's real randomness in
|
||||
* the result.
|
||||
*
|
||||
* Measure the time that the operations will take on average.
|
||||
*/
|
||||
$samples = 3;
|
||||
$duration = 0;
|
||||
|
||||
for ($pass = 0; $pass < $samples; ++$pass)
|
||||
{
|
||||
$microStart = microtime(true) * 1000000;
|
||||
$hash = sha1(random_int(0, mt_getrandmax()), true);
|
||||
|
||||
for ($count = 0; $count < 50; ++$count)
|
||||
{
|
||||
$hash = sha1($hash, true);
|
||||
}
|
||||
|
||||
$microEnd = microtime(true) * 1000000;
|
||||
$entropy .= $microStart . $microEnd;
|
||||
|
||||
if ($microStart >= $microEnd)
|
||||
{
|
||||
$microEnd += 1000000;
|
||||
}
|
||||
|
||||
$duration += $microEnd - $microStart;
|
||||
}
|
||||
|
||||
$duration = $duration / $samples;
|
||||
|
||||
/*
|
||||
* Based on the average time, determine the total rounds so that
|
||||
* the total running time is bounded to a reasonable number.
|
||||
*/
|
||||
$rounds = (int) (($maxTimeMicro / $duration) * 50);
|
||||
|
||||
/*
|
||||
* Take additional measurements. On average we can expect
|
||||
* at least $bitsPerRound bits of entropy from each measurement.
|
||||
*/
|
||||
$iter = $bytes * (int) ceil(8 / $bitsPerRound);
|
||||
|
||||
for ($pass = 0; $pass < $iter; ++$pass)
|
||||
{
|
||||
$microStart = microtime(true);
|
||||
$hash = sha1(random_int(0, mt_getrandmax()), true);
|
||||
|
||||
for ($count = 0; $count < $rounds; ++$count)
|
||||
{
|
||||
$hash = sha1($hash, true);
|
||||
}
|
||||
|
||||
$entropy .= $microStart . microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
$randomStr .= sha1($entropy, true);
|
||||
}
|
||||
|
||||
if ($urandom)
|
||||
{
|
||||
@fclose($handle);
|
||||
}
|
||||
|
||||
return substr($randomStr, 0, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
22
libraries/fof30/Encrypt/RandvalInterface.php
Normal file
22
libraries/fof30/Encrypt/RandvalInterface.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Encrypt;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
interface RandvalInterface
|
||||
{
|
||||
/**
|
||||
*
|
||||
* Returns a cryptographically secure random value.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function generate();
|
||||
}
|
||||
197
libraries/fof30/Encrypt/Totp.php
Normal file
197
libraries/fof30/Encrypt/Totp.php
Normal file
@ -0,0 +1,197 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\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 = null;
|
||||
|
||||
/**
|
||||
* 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($timeStep = 30, $passCodeLength = 6, $secretLength = 10, Base32 $base32 = null)
|
||||
{
|
||||
$this->timeStep = $timeStep;
|
||||
$this->passCodeLength = $passCodeLength;
|
||||
$this->secretLength = $secretLength;
|
||||
$this->pinModulo = 10 ** $this->passCodeLength;
|
||||
|
||||
if (is_null($base32))
|
||||
{
|
||||
$this->base32 = new Base32();
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->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($time = null)
|
||||
{
|
||||
if (is_null($time))
|
||||
{
|
||||
$time = time();
|
||||
}
|
||||
|
||||
$period = floor($time / $this->timeStep);
|
||||
|
||||
return $period;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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($secret, $code, $time = null)
|
||||
{
|
||||
$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($secret, $time = null)
|
||||
{
|
||||
$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 = $offset & 0xF;
|
||||
|
||||
$truncatedHash = $this->hashToInt($hash, $offset) & 0x7FFFFFFF;
|
||||
$pinValue = str_pad($truncatedHash % $this->pinModulo, $this->passCodeLength, "0", STR_PAD_LEFT);
|
||||
|
||||
return $pinValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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($user, $hostname, $secret)
|
||||
{
|
||||
$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=";
|
||||
$encoderURL = $encoder . urlencode($url);
|
||||
|
||||
return $encoderURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a (semi-)random Secret Key for TOTP generation
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateSecret()
|
||||
{
|
||||
$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($bytes, $start)
|
||||
{
|
||||
$input = substr($bytes, $start, strlen($bytes) - $start);
|
||||
$val2 = unpack("N", substr($input, 0, 4));
|
||||
|
||||
return $val2[1];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user