diff --git a/admin/functions.php b/admin/functions.php
index ae72f6340..64d10c28c 100644
--- a/admin/functions.php
+++ b/admin/functions.php
@@ -96,15 +96,11 @@ function rewriteSettingsFile($base_urls = null, $theme = null, $array_db = null)
$defines['_DB_PASSWD_'] = (($array_db && isset($array_db['_DB_PASSWD_'])) ? $array_db['_DB_PASSWD_'] : _DB_PASSWD_);
$defines['_COOKIE_KEY_'] = addslashes(_COOKIE_KEY_);
$defines['_COOKIE_IV_'] = addslashes(_COOKIE_IV_);
+ $defines['_NEW_COOKIE_KEY_'] = addslashes(_NEW_COOKIE_KEY_);
$defines['_PS_CREATION_DATE_'] = addslashes(_PS_CREATION_DATE_);
- if (defined('_RIJNDAEL_KEY_')) {
- $defines['_RIJNDAEL_KEY_'] = addslashes(_RIJNDAEL_KEY_);
- }
- if (defined('_RIJNDAEL_IV_')) {
- $defines['_RIJNDAEL_IV_'] = addslashes(_RIJNDAEL_IV_);
- }
$defines['_PS_VERSION_'] = addslashes(_PS_VERSION_);
+ $defines['_QLOAPPS_VERSION_'] = addslashes(_QLOAPPS_VERSION_);
$content = " $value) {
if ($k == '_PS_VERSION_') {
diff --git a/classes/ConfigurationTest.php b/classes/ConfigurationTest.php
index 4474c9ec0..8282698e0 100644
--- a/classes/ConfigurationTest.php
+++ b/classes/ConfigurationTest.php
@@ -87,6 +87,7 @@ class ConfigurationTestCore
'config_dir' => 'config',
'files' => false,
'mails_dir' => 'mails',
+ 'openssl' => false,
'curl' => false,
'soap' => false,
'simplexml' => false,
@@ -113,7 +114,6 @@ class ConfigurationTestCore
'new_phpversion' => false,
'register_globals' => false,
'gz' => false,
- 'mcrypt' => false,
'mbstring' => false,
'magicquotes' => false,
'dom' => false,
@@ -146,12 +146,12 @@ class ConfigurationTestCore
public static function test_phpversion()
{
- return version_compare(substr(phpversion(), 0, 5), '5.4.0', '>=');
+ return version_compare(substr(phpversion(), 0, 5), '5.6.0', '>=');
}
public static function test_new_phpversion()
{
- return version_compare(substr(phpversion(), 0, 5), '5.4.0', '>=');
+ return version_compare(substr(phpversion(), 0, 5), '5.6.0', '>=');
}
public static function test_mysql_support()
@@ -401,9 +401,9 @@ class ConfigurationTestCore
return function_exists('mb_strtolower');
}
- public static function test_mcrypt()
+ public static function test_openssl()
{
- return function_exists('mcrypt_encrypt');
+ return function_exists('openssl_encrypt');
}
public static function test_sessions()
diff --git a/classes/Cookie.php b/classes/Cookie.php
index 89ee0ef6e..77115986a 100644
--- a/classes/Cookie.php
+++ b/classes/Cookie.php
@@ -76,7 +76,7 @@ class CookieCore
$this->_name = 'PrestaShop-'.md5(($this->_standalone ? '' : _PS_VERSION_).$name.$this->_domain);
$this->_allow_writing = true;
$this->_salt = $this->_standalone ? str_pad('', 8, md5('ps'.__FILE__)) : _COOKIE_IV_;
- $this->_cipherTool = new Rijndael(_RIJNDAEL_KEY_, _RIJNDAEL_IV_);
+ $this->_cipherTool = new PhpEncryption(_NEW_COOKIE_KEY_);
$this->_secure = (bool)$secure;
$this->update();
@@ -324,6 +324,11 @@ class CookieCore
*/
protected function _setcookie($cookie = null)
{
+ $length = (ini_get('mbstring.func_overload') & 2) ? mb_strlen($cookie, ini_get('default_charset')) : strlen($cookie);
+ if ($length >= 1048576) {
+ return false;
+ }
+
if ($cookie) {
$content = $this->_cipherTool->encrypt($cookie);
$time = $this->_expire;
diff --git a/classes/PhpEncryption.php b/classes/PhpEncryption.php
new file mode 100644
index 000000000..13d4fa7a1
--- /dev/null
+++ b/classes/PhpEncryption.php
@@ -0,0 +1,122 @@
+
+ * @copyright 2007-2018 PrestaShop SA
+ * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * International Registered Trademark & Property of PrestaShop SA
+ */
+use Defuse\Crypto\Exception\EnvironmentIsBrokenException;
+
+/**
+ * Class PhpEncryptionCore for openSSL 1.0.1+.
+ */
+class PhpEncryptionCore
+{
+ const ENGINE = 'PhpEncryptionEngine';
+ const LEGACY_ENGINE = 'PhpEncryptionLegacyEngine';
+
+ private static $engine;
+
+ /**
+ * PhpEncryptionCore constructor.
+ *
+ * @param string $hexString A string that only contains hexadecimal characters
+ * Bother upper and lower case are allowed
+ */
+ public function __construct($hexString)
+ {
+ $engineClass = self::resolveEngineToUse();
+ self::$engine = new $engineClass($hexString);
+ }
+
+ /**
+ * Encrypt the plaintext.
+ *
+ * @param string $plaintext Plaintext
+ *
+ * @return string Cipher text
+ */
+ public function encrypt($plaintext)
+ {
+ return self::$engine->encrypt($plaintext);
+ }
+
+ /**
+ * Decrypt the cipher text.
+ *
+ * @param string $cipherText Cipher text
+ *
+ * @return bool|string Plaintext
+ * `false` if unable to decrypt
+ *
+ * @throws Exception
+ */
+ public function decrypt($cipherText)
+ {
+ return self::$engine->decrypt($cipherText);
+ }
+
+ /**
+ * @param $header
+ * @param $bytes
+ *
+ * @return string
+ *
+ * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException
+ */
+ public static function saveBytesToChecksummedAsciiSafeString($header, $bytes)
+ {
+ $engine = self::resolveEngineToUse();
+
+ return $engine::saveBytesToChecksummedAsciiSafeString($header, $bytes);
+ }
+
+ /**
+ * @return string
+ *
+ * @throws Exception
+ */
+ public static function createNewRandomKey()
+ {
+ $engine = self::resolveEngineToUse();
+
+ try {
+ $randomKey = $engine::createNewRandomKey();
+ } catch (EnvironmentIsBrokenException $exception) {
+ $buf = $engine::randomCompat();
+ $randomKey = $engine::saveToAsciiSafeString($buf);
+ }
+
+ return $randomKey;
+ }
+
+ /**
+ * Choose which engine use regarding the OpenSSL cipher methods available.
+ */
+ public static function resolveEngineToUse()
+ {
+ if (false === in_array(\Defuse\Crypto\Core::CIPHER_METHOD, openssl_get_cipher_methods())) {
+ return self::LEGACY_ENGINE;
+ }
+
+ return self::ENGINE;
+ }
+}
diff --git a/classes/PhpEncryptionEngine.php b/classes/PhpEncryptionEngine.php
new file mode 100644
index 000000000..3f496f4a2
--- /dev/null
+++ b/classes/PhpEncryptionEngine.php
@@ -0,0 +1,168 @@
+
+ * @copyright 2007-2018 PrestaShop SA
+ * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * International Registered Trademark & Property of PrestaShop SA
+ */
+use Defuse\Crypto\Crypto;
+use Defuse\Crypto\Encoding;
+use Defuse\Crypto\Key;
+
+/**
+ * Class PhpEncryption engine for openSSL 1.0.1+.
+ */
+class PhpEncryptionEngineCore
+{
+ protected $key;
+
+ /**
+ * PhpEncryptionCore constructor.
+ *
+ * @param string $hexString A string that only contains hexadecimal characters
+ * Bother upper and lower case are allowed
+ */
+ public function __construct($hexString)
+ {
+ $this->key = self::loadFromAsciiSafeString($hexString);
+ }
+
+ /**
+ * Encrypt the plaintext.
+ *
+ * @param string $plaintext Plaintext
+ *
+ * @return string Cipher text
+ */
+ public function encrypt($plaintext)
+ {
+ return Crypto::encrypt($plaintext, $this->key);
+ }
+
+ /**
+ * Decrypt the cipher text.
+ *
+ * @param string $cipherText Cipher text
+ *
+ * @return bool|string Plaintext
+ * `false` if unable to decrypt
+ *
+ * @throws Exception
+ */
+ public function decrypt($cipherText)
+ {
+ try {
+ $plaintext = Crypto::decrypt($cipherText, $this->key);
+ } catch (Exception $e) {
+ if ($e instanceof \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException) {
+ return false;
+ }
+
+ throw $e;
+ }
+
+ return $plaintext;
+ }
+
+ /**
+ * @param $header
+ * @param $bytes
+ *
+ * @return string
+ *
+ * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException
+ */
+ public static function saveBytesToChecksummedAsciiSafeString($header, $bytes)
+ {
+ return Encoding::saveBytesToChecksummedAsciiSafeString($header, $bytes);
+ }
+
+ /**
+ * @return string
+ */
+ public static function createNewRandomKey()
+ {
+ $key = Key::createNewRandomKey();
+
+ return $key->saveToAsciiSafeString();
+ }
+
+ /**
+ * @param $hexString
+ *
+ * @return Key
+ */
+ public static function loadFromAsciiSafeString($hexString)
+ {
+ return Key::loadFromAsciiSafeString($hexString);
+ }
+
+ /**
+ * @return string
+ *
+ * @throws Exception
+ *
+ * @see https://github.com/paragonie/random_compat/blob/v1.4.1/lib/random_bytes_openssl.php
+ * @see https://github.com/paragonie/random_compat/blob/v1.4.1/lib/random_bytes_mcrypt.php
+ */
+ public static function randomCompat()
+ {
+ $bytes = Key::KEY_BYTE_SIZE;
+
+ $secure = true;
+ $buf = openssl_random_pseudo_bytes($bytes, $secure);
+ if (
+ $buf !== false
+ &&
+ $secure
+ &&
+ RandomCompat_strlen($buf) === $bytes
+ ) {
+ return $buf;
+ }
+
+ $buf = @mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
+ if (
+ $buf !== false
+ &&
+ RandomCompat_strlen($buf) === $bytes
+ ) {
+ return $buf;
+ }
+
+ throw new Exception(
+ 'Could not gather sufficient random data'
+ );
+ }
+
+ /**
+ * @param $buf
+ *
+ * @return string
+ */
+ public static function saveToAsciiSafeString($buf)
+ {
+ return Encoding::saveBytesToChecksummedAsciiSafeString(
+ Key::KEY_CURRENT_VERSION,
+ $buf
+ );
+ }
+}
diff --git a/classes/PhpEncryptionLegacyEngine.php b/classes/PhpEncryptionLegacyEngine.php
new file mode 100644
index 000000000..3033024ca
--- /dev/null
+++ b/classes/PhpEncryptionLegacyEngine.php
@@ -0,0 +1,168 @@
+
+ * @copyright 2007-2018 PrestaShop SA
+ * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * International Registered Trademark & Property of PrestaShop SA
+ */
+
+/**
+ * Class PhpEncryption engine for openSSL < 0.9.8.
+ *
+ * @doc http://php.net/manual/fr/function.mcrypt-encrypt.php#refsect1-function.mcrypt-encrypt-examples
+ *
+ * This class will be deprecated when web hosting providers will update their version of OpenSSL.
+ */
+class PhpEncryptionLegacyEngineCore extends PhpEncryptionEngine
+{
+ protected $key;
+ protected $hmacIv;
+ protected $iv;
+ protected $ivSize;
+
+ protected $mode = MCRYPT_MODE_CBC;
+ protected $cipher = MCRYPT_RIJNDAEL_128;
+
+ /**
+ * PhpEncryptionCore constructor.
+ *
+ * @param string $hexString A string that only contains hexadecimal characters
+ * Bother upper and lower case are allowed
+ */
+ public function __construct($hexString)
+ {
+ $this->key = substr($hexString, 0, 32);
+ $this->ivSize = mcrypt_get_iv_size($this->cipher, $this->mode);
+ $this->iv = mcrypt_create_iv($this->ivSize, MCRYPT_RAND);
+ $this->hmacIv = substr(sha1(_COOKIE_KEY_), 0, $this->ivSize);
+ }
+
+ /**
+ * Encrypt the plaintext.
+ *
+ * @param string $plaintext Plaintext
+ *
+ * @return string Cipher text
+ */
+ public function encrypt($plaintext)
+ {
+ $blockSize = mcrypt_get_block_size($this->cipher, $this->mode);
+ $pad = $blockSize - (strlen($plaintext) % $blockSize);
+
+ $cipherText = mcrypt_encrypt(
+ $this->cipher,
+ $this->key,
+ $plaintext . str_repeat(chr($pad), $pad),
+ $this->mode,
+ $this->iv
+ );
+ $cipherText = $this->iv . $cipherText;
+
+ return $this->generateHmac($cipherText) . ':' . base64_encode($cipherText);
+ }
+
+ /**
+ * Decrypt the cipher text.
+ *
+ * @param string $cipherText Cipher text
+ *
+ * @return bool|string Plaintext
+ * `false` if unable to decrypt
+ *
+ * @throws Exception
+ */
+ public function decrypt($cipherText)
+ {
+ $data = explode(':', $cipherText);
+ if (count($data) != 2) {
+ return false;
+ }
+
+ list($hmac, $encrypted) = $data;
+ $encrypted = base64_decode($encrypted);
+ $newHmac = $this->generateHmac($encrypted);
+ if ($hmac !== $newHmac) {
+ return false;
+ }
+
+ $ivDec = substr($encrypted, 0, $this->ivSize);
+ $cipherText = substr($encrypted, $this->ivSize);
+
+ $data = mcrypt_decrypt(
+ $this->cipher,
+ $this->key,
+ $cipherText,
+ $this->mode,
+ $ivDec
+ );
+
+ $pad = ord($data[strlen($data) - 1]);
+
+ return substr($data, 0, -$pad);
+ }
+
+ /**
+ * Generate Hmac.
+ *
+ * @param string $encrypted
+ *
+ * @return string
+ */
+ protected function generateHmac($encrypted)
+ {
+ $macKey = $this->generateKeygenS2k('sha256', $this->key, $this->hmacIv, 32);
+
+ return hash_hmac(
+ 'sha256',
+ $this->hmacIv . $this->cipher . $encrypted,
+ $macKey
+ );
+ }
+
+ /**
+ * Alternative to mhash_keygen_s2k for security reason
+ * and php compatibilities.
+ *
+ * @param string $hash
+ * @param string $password
+ * @param string $salt
+ * @param int $bytes
+ *
+ * @return string
+ */
+ protected function generateKeygenS2k($hash, $password, $salt, $bytes)
+ {
+ $result = '';
+ foreach (range(0, ceil($bytes / strlen(hash($hash, null, true))) - 1) as $i) {
+ $result .= hash(
+ $hash,
+ str_repeat("\0", $i) . str_pad(substr($salt, 0, 8), 8, "\0", STR_PAD_RIGHT) . $password,
+ true
+ );
+ }
+
+ return substr(
+ $result,
+ 0,
+ intval($bytes)
+ );
+ }
+}
diff --git a/classes/Rijndael.php b/classes/Rijndael.php
deleted file mode 100644
index 16edb83bb..000000000
--- a/classes/Rijndael.php
+++ /dev/null
@@ -1,157 +0,0 @@
-
-* @copyright 2007-2018 PrestaShop SA
-* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
-* International Registered Trademark & Property of PrestaShop SA
-*/
-
-class RijndaelCore
-{
- protected $_key;
- protected $_iv;
-
- public function __construct($key, $iv)
- {
- $this->_key = $key;
- $this->_iv = base64_decode($iv);
- }
-
- /**
- * Base64 is not required, but it is be more compact than urlencode
- *
- * @param string $plaintext
- * @return bool|string
- */
- public function encrypt($plaintext)
- {
- if (strlen($plaintext) >= 1048576) {
- return false;
- }
-
- $cipherText = null;
- if (function_exists('openssl_encrypt') && version_compare(phpversion(), '5.3.3', '>=')) {
- $cipherText = openssl_encrypt($plaintext, 'AES-128-CBC', $this->_key, OPENSSL_RAW_DATA, $this->_iv);
- } elseif (function_exists('mcrypt_encrypt')) {
- $ivSize = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
- $iv = mcrypt_create_iv($ivSize, MCRYPT_RAND);
- $blockSize = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
- $pad = $blockSize - (strlen($plaintext) % $blockSize);
-
- $cipherText = mcrypt_encrypt(
- MCRYPT_RIJNDAEL_128,
- $this->_key,
- $plaintext . str_repeat(chr($pad), $pad),
- MCRYPT_MODE_CBC,
- $iv
- );
- $cipherText = $iv.$cipherText;
- } else {
- throw new RuntimeException('Either Mcrypt or OpenSSL extension is required to run Prestashop');
- }
-
- return $this->generateHmac($cipherText) . ':' . base64_encode($cipherText);
- }
-
- public function decrypt($cipherText)
- {
- $data = explode(':', $cipherText);
- if (count($data) != 2) {
- return false;
- }
-
- list($hmac, $encrypted) = $data;
-
- $encrypted = base64_decode($encrypted);
- $newHmac = $this->generateHmac($encrypted);
- if ($hmac !== $newHmac) {
- return false;
- }
-
- $output = null;
- if (function_exists('openssl_decrypt') && version_compare(phpversion(), '5.3.3', '>=')) {
- $output = openssl_decrypt($encrypted, 'AES-128-CBC', $this->_key, OPENSSL_RAW_DATA, $this->_iv);
- } elseif (function_exists('mcrypt_decrypt')) {
- $ivSize = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
- $ivDec = substr($encrypted, 0, $ivSize);
- $encrypted = substr($encrypted, $ivSize);
- $output = mcrypt_decrypt(
- MCRYPT_RIJNDAEL_128,
- $this->_key,
- $encrypted,
- MCRYPT_MODE_CBC,
- $ivDec
- );
- $pad = ord($output[strlen($output) - 1]);
- $output = substr($output, 0, -$pad);
- } else {
- throw new RuntimeException('Either Mcrypt or OpenSSL extension is required to run Prestashop');
- }
-
- return $output;
- }
-
- /**
- * Generate Hmac
- *
- * @param string $encrypted
- *
- * @return string
- */
- protected function generateHmac($encrypted)
- {
- $macKey = $this->generateKeygenS2k('sha256', $this->_key, $this->_iv, 32);
- return hash_hmac(
- 'sha256',
- $this->_iv . MCRYPT_RIJNDAEL_128 . $encrypted,
- $macKey
- );
- }
-
- /**
- * Alternative to mhash_keygen_s2k for security reason
- * and php compatibilities.
- *
- * @param string $hash
- * @param string $password
- * @param string $salt
- * @param integer $bytes
- *
- * @return string
- */
- protected function generateKeygenS2k($hash, $password, $salt, $bytes)
- {
- $result = '';
- foreach (range(0, ceil($bytes / strlen(hash($hash, null, true))) - 1) as $i) {
- $result .= hash(
- $hash,
- str_repeat("\0", $i) . str_pad(substr($salt, 0, 8), 8, "\0", STR_PAD_RIGHT) . $password,
- true
- );
- }
-
- return substr(
- $result,
- 0,
- intval($bytes)
- );
- }
-}
diff --git a/config/autoload.php b/config/autoload.php
index 20a6c05fa..0cfc7a14c 100644
--- a/config/autoload.php
+++ b/config/autoload.php
@@ -23,6 +23,7 @@
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
+require_once (_PS_TOOL_DIR_.'defuse/php-encryption/defuse-crypto.phar');
// Include some alias functions
require_once(_PS_CONFIG_DIR_.'alias.php');
diff --git a/controllers/admin/AdminInformationController.php b/controllers/admin/AdminInformationController.php
index cf96c1f69..a34b48278 100644
--- a/controllers/admin/AdminInformationController.php
+++ b/controllers/admin/AdminInformationController.php
@@ -128,7 +128,8 @@ class AdminInformationControllerCore extends AdminController
'register_globals' => $this->l('Set PHP "register_globals" option to "Off".'),
'gz' => $this->l('Enable GZIP compression on your server.'),
'files' => $this->l('Some PrestaShop files are missing from your server.'),
- 'new_phpversion' => sprintf($this->l('You are using PHP %s version. Soon, the latest PHP version supported by PrestaShop will be PHP 5.4. To make sure you’re ready for the future, we recommend you to upgrade to PHP 5.4 now!'), phpversion())
+ 'new_phpversion' => sprintf($this->l('You are using PHP %s version. Soon, the latest PHP version supported by PrestaShop will be PHP 5.4. To make sure you’re ready for the future, we recommend you to upgrade to PHP 5.4 now!'), phpversion()),
+ 'openssl' => $this->l('PHP OpenSSL extension is not loaded'),
);
// Functions list to test with 'test_system'
diff --git a/controllers/admin/AdminPerformanceController.php b/controllers/admin/AdminPerformanceController.php
index 3e5976b34..415196036 100644
--- a/controllers/admin/AdminPerformanceController.php
+++ b/controllers/admin/AdminPerformanceController.php
@@ -460,49 +460,6 @@ class AdminPerformanceControllerCore extends AdminController
$this->fields_value['_MEDIA_SERVER_3_'] = Configuration::get('PS_MEDIA_SERVER_3');
}
- public function initFieldsetCiphering()
- {
- $phpdoc_langs = array('en', 'zh', 'fr', 'de', 'ja', 'pl', 'ro', 'ru', 'fa', 'es', 'tr');
- $php_lang = in_array($this->context->language->iso_code, $phpdoc_langs) ? $this->context->language->iso_code : 'en';
-
- $warning_mcrypt = ' '.$this->l('(you must install the [a]Mcrypt extension[/a])');
- $warning_mcrypt = str_replace('[a]', '', $warning_mcrypt);
- $warning_mcrypt = str_replace('[/a]', '', $warning_mcrypt);
-
- if (defined('_RIJNDAEL_KEY_') && defined('_RIJNDAEL_IV_')) {
- $this->fields_form[5]['form'] = array(
-
- 'legend' => array(
- 'title' => $this->l('Ciphering'),
- 'icon' => 'icon-desktop'
- ),
- 'input' => array(
- array(
- 'type' => 'hidden',
- 'name' => 'ciphering_up'
- ),
- array(
- 'type' => 'radio',
- 'label' => $this->l('Algorithm'),
- 'name' => 'PS_CIPHER_ALGORITHM',
- 'values' => array(
- array(
- 'id' => 'PS_CIPHER_ALGORITHM_1',
- 'value' => 1,
- 'label' => $this->l('Use Rijndael with mcrypt lib.').(function_exists('mcrypt_encrypt') ? '' : $warning_mcrypt)
- ),
- )
- )
- ),
- 'submit' => array(
- 'title' => $this->l('Save')
- )
- );
- }
-
- $this->fields_value['PS_CIPHER_ALGORITHM'] = Configuration::get('PS_CIPHER_ALGORITHM');
- }
-
public function initFieldsetCaching()
{
$phpdoc_langs = array('en', 'zh', 'fr', 'de', 'ja', 'pl', 'ro', 'ru', 'fa', 'es', 'tr');
@@ -618,7 +575,6 @@ class AdminPerformanceControllerCore extends AdminController
if (!defined('_PS_HOST_MODE_')) {
$this->initFieldsetMediaServer();
- $this->initFieldsetCiphering();
$this->initFieldsetCaching();
}
@@ -840,51 +796,6 @@ class AdminPerformanceControllerCore extends AdminController
$this->errors[] = Tools::displayError('You do not have permission to edit this.');
}
}
- if ((bool)Tools::getValue('ciphering_up') && Configuration::get('PS_CIPHER_ALGORITHM') != (int)Tools::getValue('PS_CIPHER_ALGORITHM')) {
- if ($this->tabAccess['edit'] === '1') {
- $algo = (int)Tools::getValue('PS_CIPHER_ALGORITHM');
- $prev_settings = file_get_contents(_PS_ROOT_DIR_.'/config/settings.inc.php');
- $new_settings = $prev_settings;
- if ($algo) {
- if (!function_exists('mcrypt_encrypt')) {
- $this->errors[] = Tools::displayError('The "Mcrypt" PHP extension is not activated on this server.');
- } else {
- if (!strstr($new_settings, '_RIJNDAEL_KEY_')) {
- $key_size = mcrypt_get_key_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
- $key = Tools::passwdGen($key_size);
- $new_settings = preg_replace(
- '/define\(\'_COOKIE_KEY_\', \'([a-z0-9=\/+-_]+)\'\);/i',
- 'define(\'_COOKIE_KEY_\', \'\1\');'."\n".'define(\'_RIJNDAEL_KEY_\', \''.$key.'\');',
- $new_settings
- );
- }
- if (!strstr($new_settings, '_RIJNDAEL_IV_')) {
- $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
- $iv = base64_encode(mcrypt_create_iv($iv_size, MCRYPT_RAND));
- $new_settings = preg_replace(
- '/define\(\'_COOKIE_IV_\', \'([a-z0-9=\/+-_]+)\'\);/i',
- 'define(\'_COOKIE_IV_\', \'\1\');'."\n".'define(\'_RIJNDAEL_IV_\', \''.$iv.'\');',
- $new_settings
- );
- }
- }
- }
- if (!count($this->errors)) {
- // If there is not settings file modification or if the backup and replacement of the settings file worked
- if ($new_settings == $prev_settings || (
- copy(_PS_ROOT_DIR_.'/config/settings.inc.php', _PS_ROOT_DIR_.'/config/settings.old.php')
- && (bool)file_put_contents(_PS_ROOT_DIR_.'/config/settings.inc.php', $new_settings)
- )) {
- Configuration::updateValue('PS_CIPHER_ALGORITHM', $algo);
- $redirectAdmin = true;
- } else {
- $this->errors[] = Tools::displayError('The settings file cannot be overwritten.');
- }
- }
- } else {
- $this->errors[] = Tools::displayError('You do not have permission to edit this.');
- }
- }
if ((bool)Tools::getValue('cache_up')) {
if ($this->tabAccess['edit'] === '1') {
diff --git a/install/models/install.php b/install/models/install.php
index 5648853e9..96cafd57d 100644
--- a/install/models/install.php
+++ b/install/models/install.php
@@ -24,6 +24,8 @@
* International Registered Trademark & Property of PrestaShop SA
*/
+require_once (_PS_TOOL_DIR_.'defuse/php-encryption/defuse-crypto.phar');
+
class InstallModelInstall extends InstallAbstractModel
{
const SETTINGS_FILE = 'config/settings.inc.php';
@@ -69,6 +71,7 @@ class InstallModelInstall extends InstallAbstractModel
$this->setError($this->language->l('%s folder is not writable (check permissions)', dirname(self::SETTINGS_FILE)));
return false;
}
+ $key = \Defuse\Crypto\Key::createNewRandomKey();
// Generate settings content and write file
$settings_constants = array(
@@ -82,17 +85,12 @@ class InstallModelInstall extends InstallAbstractModel
'_PS_CACHE_ENABLED_' => '0',
'_COOKIE_KEY_' => Tools::passwdGen(56),
'_COOKIE_IV_' => Tools::passwdGen(8),
+ '_NEW_COOKIE_KEY_' => $key->saveToAsciiSafeString(),
'_PS_CREATION_DATE_' => date('Y-m-d'),
'_PS_VERSION_' => _PS_INSTALL_VERSION_,
'_QLOAPPS_VERSION_' => _QLO_INSTALL_VERSION_,
);
- // If mcrypt is activated, add Rijndael 128 configuration
- if (function_exists('mcrypt_encrypt')) {
- $settings_constants['_RIJNDAEL_KEY_'] = Tools::passwdGen(mcrypt_get_key_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
- $settings_constants['_RIJNDAEL_IV_'] = base64_encode(mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND));
- }
-
$settings_content = " $value) {
diff --git a/tools/defuse/index.php b/tools/defuse/index.php
new file mode 100644
index 000000000..d9925ba54
--- /dev/null
+++ b/tools/defuse/index.php
@@ -0,0 +1,29 @@
+
+* @copyright 2010-2021 Webkul IN
+* @license https://store.webkul.com/license.html
+*/
+
+header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
+header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
+
+header('Cache-Control: no-store, no-cache, must-revalidate');
+header('Cache-Control: post-check=0, pre-check=0', false);
+header('Pragma: no-cache');
+
+header('Location: ../');
+exit;
diff --git a/tools/defuse/php-encryption/LICENSE b/tools/defuse/php-encryption/LICENSE
new file mode 100644
index 000000000..f3e7c8e60
--- /dev/null
+++ b/tools/defuse/php-encryption/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Taylor Hornby and Paragon Initiative
+Enterprises .
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/tools/defuse/php-encryption/README.md b/tools/defuse/php-encryption/README.md
new file mode 100644
index 000000000..cd101a2cd
--- /dev/null
+++ b/tools/defuse/php-encryption/README.md
@@ -0,0 +1,87 @@
+php-encryption
+===============
+
+[](https://travis-ci.org/defuse/php-encryption)
+
+This is a library for encrypting data with a key or password in PHP. **It
+requires PHP 5.4 or newer.** The current version is v2.0.0, which is expected to
+remain stable and supported by its authors with security and bugfixes until at
+least January 1st, 2019.
+
+The library is a joint effort between [Taylor Hornby](https://defuse.ca/) and
+[Scott Arciszewski](https://paragonie.com/blog/author/scott-arcizewski) as well
+as numerous open-source contributors.
+
+What separates this library from other PHP encryption libraries is, firstly,
+that it is secure. The authors used to encounter insecure PHP encryption code on
+a daily basis, so they created this library to bring more security to the
+ecosystem. Secondly, this library is "difficult to misuse." Like
+[libsodium](https://github.com/jedisct1/libsodium), its API is designed to be
+easy to use in a secure way and hard to use in an insecure way.
+
+Dependencies
+------------
+
+This library requres no special dependencies except for PHP 5.4 or newer with
+the OpenSSL extensions enabled (this is the default). It uses
+[random\_compat](https://github.com/paragonie/random_compat), which is bundled
+in with this library so that your users will not need to follow any special
+installation steps.
+
+Getting Started
+----------------
+
+Start with the [**Tutorial**](docs/Tutorial.md). You can find instructions for
+obtaining this library's code securely in the [Installing and
+Verifying](docs/InstallingAndVerifying.md) documentation.
+
+After you've read the tutorial and got the code, refer to the formal
+documentation for each of the classes this library provides:
+
+- [Crypto](docs/classes/Crypto.md)
+- [File](docs/classes/File.md)
+- [Key](docs/classes/Key.md)
+- [KeyProtectedByPassword](docs/classes/KeyProtectedByPassword.md)
+
+If you encounter difficulties, see the [FAQ](docs/FAQ.md) answers. The fixes to
+the most commonly-reported problems are explained there.
+
+If you're a cryptographer and want to understand the nitty-gritty details of how
+this library works, look at the [Cryptography Details](docs/CryptoDetails.md)
+documentation.
+
+If you're interested in contributing to this library, see the [Internal
+Developer Documentation](docs/InternalDeveloperDocs.md).
+
+Examples
+---------
+
+If the documentation is not enough for you to understand how to use this
+library, then you can look at an example project that uses this library:
+
+- [encutil](https://github.com/defuse/encutil)
+
+Security Audit Status
+---------------------
+
+This code has not been subjected to a formal, paid, security audit. However, it
+has received lots of review from members of the PHP security community, and the
+authors are experienced with cryptography. In all likelihood, you are safer
+using this library than almost any other encryption library for PHP.
+
+If you use this library as a part of your business and would like to help fund
+a formal audit, please [contact Taylor Hornby](https://defuse.ca/contact.htm).
+
+Public Keys
+------------
+
+The GnuPG public key used to sign releases is available in
+[dist/signingkey.asc](https://github.com/defuse/php-encryption/raw/master/dist/signingkey.asc). Its fingerprint is:
+
+```
+2FA6 1D8D 99B9 2658 6BAC 3D53 385E E055 A129 1538
+```
+
+You can verify it against the Taylor Hornby's [contact
+page](https://defuse.ca/contact.htm) and
+[twitter](https://twitter.com/DefuseSec/status/723741424253059074).
diff --git a/tools/defuse/php-encryption/defuse-crypto.phar b/tools/defuse/php-encryption/defuse-crypto.phar
new file mode 100644
index 000000000..74c1b8492
Binary files /dev/null and b/tools/defuse/php-encryption/defuse-crypto.phar differ
diff --git a/tools/defuse/php-encryption/dist/Makefile b/tools/defuse/php-encryption/dist/Makefile
new file mode 100644
index 000000000..4b65c9ec4
--- /dev/null
+++ b/tools/defuse/php-encryption/dist/Makefile
@@ -0,0 +1,37 @@
+# This builds defuse-crypto.phar. To run this Makefile, `box` and `composer`
+# must be installed and in your $PATH. Run it from inside the dist/ directory.
+
+box := $(shell which box)
+composer := "composer"
+
+.PHONY: all
+all: build-phar
+
+.PHONY: sign-phar
+sign-phar:
+ gpg -u 7B4B2D98 --armor --output defuse-crypto.phar.sig --detach-sig defuse-crypto.phar
+
+# ensure we run in clean tree. export git tree and run there.
+.PHONY: build-phar
+build-phar:
+ @echo "Creating .phar from revision $(shell git rev-parse HEAD)."
+ rm -rf worktree
+ install -d worktree
+ (cd $(CURDIR)/..; git archive HEAD) | tar -x -C worktree
+ $(MAKE) -f $(CURDIR)/Makefile -C worktree defuse-crypto.phar
+ mv worktree/*.phar .
+ rm -rf worktree
+
+.PHONY: clean
+clean:
+ rm -vf defuse-crypto.phar defuse-crypto.phar.sig
+
+# Inside workdir/:
+
+defuse-crypto.phar: dist/box.json composer.lock
+ cp dist/box.json .
+ php -d phar.readonly=0 $(box) build -c box.json -v
+
+composer.lock:
+ $(composer) install --no-dev
+
diff --git a/tools/defuse/php-encryption/dist/box.json b/tools/defuse/php-encryption/dist/box.json
new file mode 100644
index 000000000..f225f781e
--- /dev/null
+++ b/tools/defuse/php-encryption/dist/box.json
@@ -0,0 +1,25 @@
+{
+ "chmod": "0755",
+ "finder": [
+ {
+ "in": "src",
+ "name": "*.php"
+ },
+ {
+ "in": "vendor/composer",
+ "name": "*.php"
+ },
+ {
+ "in": "vendor/paragonie",
+ "name": "*.php",
+ "exclude": "other"
+ }
+ ],
+ "compactors": [
+ "Herrera\\Box\\Compactor\\Php"
+ ],
+ "main": "vendor/autoload.php",
+ "output": "defuse-crypto.phar",
+ "shebang": false,
+ "stub": true
+}
diff --git a/tools/defuse/php-encryption/dist/index.php b/tools/defuse/php-encryption/dist/index.php
new file mode 100644
index 000000000..d9925ba54
--- /dev/null
+++ b/tools/defuse/php-encryption/dist/index.php
@@ -0,0 +1,29 @@
+
+* @copyright 2010-2021 Webkul IN
+* @license https://store.webkul.com/license.html
+*/
+
+header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
+header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
+
+header('Cache-Control: no-store, no-cache, must-revalidate');
+header('Cache-Control: post-check=0, pre-check=0', false);
+header('Pragma: no-cache');
+
+header('Location: ../');
+exit;
diff --git a/tools/defuse/php-encryption/dist/signingkey.asc b/tools/defuse/php-encryption/dist/signingkey.asc
new file mode 100644
index 000000000..63b6aa3dd
--- /dev/null
+++ b/tools/defuse/php-encryption/dist/signingkey.asc
@@ -0,0 +1,52 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v2
+
+mQINBFarvO4BEACdQBaLt6SUBx1cB5liUu1qo+YwVLh9bxTregQtmEREMdTVqXYt
+e5b79uL4pQp2GlKHcEyRURS+6rIIruM0oh9ZYGTJYPAkCDzJxaU2awZeFbfBvpCm
+iF66/O4ZJI4mlT8dFKmxBJxDhfeOR2UmmhDiEsJK9FxBKUzvo/dWrX2pBzf8Y122
+iIaVraSo+tymaf7vriaIf/NnSKhDw8dtQYGM4NMrxxsPTfbCF8XiboDgTkoD2A+6
+NpOJYxA4Veedsf2TP9YLhljH4m5yYlfjjqBzbBCPWuE6Hhy5Xze9mncgDr7LKenm
+Ctf2NxW6y4O3RCI+9eLlBfFWB+KuGV87/b5daetX7NNLbjID8z2rqEa+d6wu5xA5
+Ta2uiVkAOEovr3XnkayZ9zth+Za7w7Ai0ln0N/LVMkM+Gu4z/pJv6HjmTGDM2wJb
+fs+UOM0TFdg+N81Do67XT2M4o0MeHyUqsIiWpYa2Qf1PNmqTQNJnRk8uZZ9I96Nh
+eCgNuCbhsQiYBMicox+xmuWAlGAfA06y0kCtmqGhiBGArdJlWvUqPqGiZ4Hln9z0
+FJmXDOh0Q/FIPxcDg8mKRRbx+lOP389PLsPpj4b2B/4PEgfpCCOwuKpLotATZxC1
+9JwFk0Y/cvUUkq4a+nAJBNtBbtRJkEesuuUnRq6XexmnE3uUucDcV0XCSwARAQAB
+tCBUYXlsb3IgSG9ybmJ5IDx0YXlsb3JAZGVmdXNlLmNhPokCPQQTAQgAJwUCVqu8
+7gIbAwUJB4TOAAULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRA4XuBVoSkVOJbx
+EACG0F9blPMAsK05EWyNnnS4mw25zPfbaqqEvYbquAeM0nBpRDm7sRn2MNR0AL4g
+7XrtxE/4qYkdEl6f2wFCQeRhZgxE3w22llredzLme11Hic8hn4i7ysdIw0r9dMMR
+kjgR5UcWpv8iU847czyK09PkKW2EaLRbX2qbA7rNU5qCFKeD4Sy4bBTteISeVsHo
+Vr9o1/bRrMhgZ++ts8hYf0LmujIf5cxp+qcdKwCXSnS/gmmXaKRMCPv/Wdlq9bt6
+LX9jZB9lXBdGxcBJeFOsTG+QRDiVjg3d6i3o3TAKV87ALBI4v2ADEYtN8lviHo3/
+SovVKv6zrUsZHxhoGiLTiksNrYsKMmiMxdJCoOazmtUPnZ4UOtT8NdqMPoKvdoRz
+f4rhZ+f5jSVD9OuX2PDmfyq21Rdiym7Vcgr+uTIFJ3ShRHjWb/ytCwoB2FeGY6+G
+AKY58bTQvUIqEJvSov/+TAqZ4BfOuSdTLcHglV1OdUu2SFZvU2gmyVp0l5elGv5t
+FyUlBJUkQT9MtvsdLOR7vQi8QapV+9LWpqwvaj9hyEJz848DQ2sdYTphUytFHv7H
+k58DAtVhTrVjHyeefjiYtMl6vSAgTjy5LWAUpo5TfhdGrAi0Tdd/GD7amHoWoDy8
+EKXKq2xPLo3JOdkWYQUi5NErzEskfsSzpCOgyDJmGetWK7kCDQRWq7zuARAAu7/i
+cm8cjgLhHEX/bgfwOT2hLOLSjjve0O8YFSuJO9XqIHXqmfVOrqWtfG0Mh4bwlfqc
+MAvBfF5NSSPfAE4ftBAQ1e5jEv8hJeqICpq3IHTFX4etBs49NfNkyveQl/amVTu1
++/O5J4CuIcsEf3y0Xuu38n7EB3SfMQCWLcOR1NyZoX3bI+CGRpOVVoFse3ljSWL4
+LhLVl0WiEMXULsussEoN+c6x9KCyAi/jFOrxrTrFC//sZesKj6KucoqKGfwMWrrv
+IeRT9Ga8Wn5MJnQu0aWg+zVVYqTedXZLNLODgQIInFnXO0seBXy15yDok1y5bkx2
+sinKg4+mueYaGUpoUti0hM3J3yaC34i6Cwa8MQoLNw1JIS/oNtKjpMxyV10w8aoc
+PHRK3n7UYp10mJHx7aM+lldSKvVS1NTQmI4vloNLwMp324H5ANDFAlRUz7mysVnu
+DEEvigPSPxs5ZYENu/i7pCQC5qHfhrlBrQwTjhegr0pQPcumy2fO5SGC9l/5B7ev
+bqQSZmDeWWoTvh2w2wl5/RWAsgZKx6rDtkCqYx7sSBY17uorrxP24LP4zhq7NxRV
+nfdsLogbCFNVQ66u7qvq5zFccdFtg9h1HQWdS7wbnKSBGZoo5gl6js7GGtxfGbb0
+oQ9kp6eciF4U92r6POhVgbRe4CfPo50nqgZBddkAEQEAAYkCJQQYAQgADwUCVqu8
+7gIbDAUJB4TOAAAKCRA4XuBVoSkVOFJ8D/9J8IJ4XWUU3FYIaHJ3XeSoxDmTi7d5
+WmNdf1lmwz82MQjG4uw17oCbvQzmj4/a/CM1Ly4v0WwBhUf9aiNErD0ByHASFnuc
+tlQBLVJdk0vRyD0fZakGg64qCA76hiySjMhlGHkQFyP2mDORc2GNu/OqFGm79pXT
+ZUplXxd431E603/agM5xJrweutMMpP1nBFTSEMJvbMNzDVN8I1A1CH4zVmAVxOUk
+sQ5L5rXW+KeXWyiMF24+l2CMnkQ2CxfHpkcpfPJs1Cbt+TIBSSofIqK8QJXrb/2f
+Zpl/ftqW7Xe86rJFrB/Y/77LDWx10rqWEvfCqrBxrMj7ONAQfbKQF/IjAwDN17Wf
+1K74rqKnRu+KHCyNM89s1iDbQC9kzZfzYt4AEOvAH/ZQDMZffzPSbnfkBerExFpa
+93XMuiR66jiBsf9IXIQeydpJD4Ogl2sSUSxFEJxJ/bBSxPxC5w7/BVMA7Am1y8Zo
+3hrpqnX2PBzxG7L0FZ6fYkfR3p8JS7vI6nByBf2IDv8W32wn43olPf+u6uobHLvt
+ttapOjwPAhPDalRuxs9U6WSg06QJkT/0F8TFUPWpsFmKTl+G4Ty7PHWsjeeNHJCL
+7/5RQboFY3k8Jy3/sIofABO6Un9LJivDuu9PxqA0IgvaS6Mja8JdCCk9Nyk4vHB7
+IEgAL/CYqrk38w==
+=lmD7
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/tools/defuse/php-encryption/index.php b/tools/defuse/php-encryption/index.php
new file mode 100644
index 000000000..d9925ba54
--- /dev/null
+++ b/tools/defuse/php-encryption/index.php
@@ -0,0 +1,29 @@
+
+* @copyright 2010-2021 Webkul IN
+* @license https://store.webkul.com/license.html
+*/
+
+header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
+header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
+
+header('Cache-Control: no-store, no-cache, must-revalidate');
+header('Cache-Control: post-check=0, pre-check=0', false);
+header('Pragma: no-cache');
+
+header('Location: ../');
+exit;
diff --git a/tools/defuse/php-encryption/src/Core.php b/tools/defuse/php-encryption/src/Core.php
new file mode 100644
index 000000000..c489f3903
--- /dev/null
+++ b/tools/defuse/php-encryption/src/Core.php
@@ -0,0 +1,434 @@
+ PHP_INT_MAX - 255) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'Integer overflow may occur.'
+ );
+ }
+
+ /*
+ * We start at the rightmost byte (big-endian)
+ * So, too, does OpenSSL: http://stackoverflow.com/a/3146214/2224584
+ */
+ for ($i = Core::BLOCK_BYTE_SIZE - 1; $i >= 0; --$i) {
+ $sum = \ord($ctr[$i]) + $inc;
+
+ /* Detect integer overflow and fail. */
+ if (! \is_int($sum)) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'Integer overflow in CTR mode nonce increment.'
+ );
+ }
+
+ $ctr[$i] = \pack('C', $sum & 0xFF);
+ $inc = $sum >> 8;
+ }
+ return $ctr;
+ }
+
+ /**
+ * Returns a random byte string of the specified length.
+ *
+ * @param int $octets
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ *
+ * @return string
+ */
+ public static function secureRandom($octets)
+ {
+ self::ensureFunctionExists('random_bytes');
+ try {
+ return \random_bytes($octets);
+ } catch (\Exception $ex) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'Your system does not have a secure random number generator.'
+ );
+ }
+ }
+
+ /**
+ * Computes the HKDF key derivation function specified in
+ * http://tools.ietf.org/html/rfc5869.
+ *
+ * @param string $hash Hash Function
+ * @param string $ikm Initial Keying Material
+ * @param int $length How many bytes?
+ * @param string $info What sort of key are we deriving?
+ * @param string $salt
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ *
+ * @return string
+ */
+ public static function HKDF($hash, $ikm, $length, $info = '', $salt = null)
+ {
+ $digest_length = Core::ourStrlen(\hash_hmac($hash, '', '', true));
+
+ // Sanity-check the desired output length.
+ if (empty($length) || ! \is_int($length) ||
+ $length < 0 || $length > 255 * $digest_length) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'Bad output length requested of HKDF.'
+ );
+ }
+
+ // "if [salt] not provided, is set to a string of HashLen zeroes."
+ if (\is_null($salt)) {
+ $salt = \str_repeat("\x00", $digest_length);
+ }
+
+ // HKDF-Extract:
+ // PRK = HMAC-Hash(salt, IKM)
+ // The salt is the HMAC key.
+ $prk = \hash_hmac($hash, $ikm, $salt, true);
+
+ // HKDF-Expand:
+
+ // This check is useless, but it serves as a reminder to the spec.
+ if (Core::ourStrlen($prk) < $digest_length) {
+ throw new Ex\EnvironmentIsBrokenException();
+ }
+
+ // T(0) = ''
+ $t = '';
+ $last_block = '';
+ for ($block_index = 1; Core::ourStrlen($t) < $length; ++$block_index) {
+ // T(i) = HMAC-Hash(PRK, T(i-1) | info | 0x??)
+ $last_block = \hash_hmac(
+ $hash,
+ $last_block . $info . \chr($block_index),
+ $prk,
+ true
+ );
+ // T = T(1) | T(2) | T(3) | ... | T(N)
+ $t .= $last_block;
+ }
+
+ // ORM = first L octets of T
+ $orm = Core::ourSubstr($t, 0, $length);
+ if ($orm === false) {
+ throw new Ex\EnvironmentIsBrokenException();
+ }
+ return $orm;
+ }
+
+ /**
+ * Checks if two equal-length strings are the same without leaking
+ * information through side channels.
+ *
+ * @param string $expected
+ * @param string $given
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ *
+ * @return bool
+ */
+ public static function hashEquals($expected, $given)
+ {
+ static $native = null;
+ if ($native === null) {
+ $native = \function_exists('hash_equals');
+ }
+ if ($native) {
+ return \hash_equals($expected, $given);
+ }
+
+ // We can't just compare the strings with '==', since it would make
+ // timing attacks possible. We could use the XOR-OR constant-time
+ // comparison algorithm, but that may not be a reliable defense in an
+ // interpreted language. So we use the approach of HMACing both strings
+ // with a random key and comparing the HMACs.
+
+ // We're not attempting to make variable-length string comparison
+ // secure, as that's very difficult. Make sure the strings are the same
+ // length.
+ if (Core::ourStrlen($expected) !== Core::ourStrlen($given)) {
+ throw new Ex\EnvironmentIsBrokenException();
+ }
+
+ $blind = Core::secureRandom(32);
+ $message_compare = \hash_hmac(Core::HASH_FUNCTION_NAME, $given, $blind);
+ $correct_compare = \hash_hmac(Core::HASH_FUNCTION_NAME, $expected, $blind);
+ return $correct_compare === $message_compare;
+ }
+ /**
+ * Throws an exception if the constant doesn't exist.
+ *
+ * @param string $name
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ */
+ public static function ensureConstantExists($name)
+ {
+ if (! \defined($name)) {
+ throw new Ex\EnvironmentIsBrokenException();
+ }
+ }
+
+ /**
+ * Throws an exception if the function doesn't exist.
+ *
+ * @param string $name
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ */
+ public static function ensureFunctionExists($name)
+ {
+ if (! \function_exists($name)) {
+ throw new Ex\EnvironmentIsBrokenException();
+ }
+ }
+
+ /*
+ * We need these strlen() and substr() functions because when
+ * 'mbstring.func_overload' is set in php.ini, the standard strlen() and
+ * substr() are replaced by mb_strlen() and mb_substr().
+ */
+
+ /**
+ * Computes the length of a string in bytes.
+ *
+ * @param string $str
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ *
+ * @return int
+ */
+ public static function ourStrlen($str)
+ {
+ static $exists = null;
+ if ($exists === null) {
+ $exists = \function_exists('mb_strlen');
+ }
+ if ($exists) {
+ $length = \mb_strlen($str, '8bit');
+ if ($length === false) {
+ throw new Ex\EnvironmentIsBrokenException();
+ }
+ return $length;
+ } else {
+ return \strlen($str);
+ }
+ }
+
+ /**
+ * Behaves roughly like the function substr() in PHP 7 does.
+ *
+ * @param string $str
+ * @param int $start
+ * @param int $length
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ *
+ * @return string
+ */
+ public static function ourSubstr($str, $start, $length = null)
+ {
+ static $exists = null;
+ if ($exists === null) {
+ $exists = \function_exists('mb_substr');
+ }
+
+ if ($exists) {
+ // mb_substr($str, 0, NULL, '8bit') returns an empty string on PHP
+ // 5.3, so we have to find the length ourselves.
+ if (! isset($length)) {
+ if ($start >= 0) {
+ $length = Core::ourStrlen($str) - $start;
+ } else {
+ $length = -$start;
+ }
+ }
+
+ // This is required to make mb_substr behavior identical to substr.
+ // Without this, mb_substr() would return false, contra to what the
+ // PHP documentation says (it doesn't say it can return false.)
+ if ($start === Core::ourStrlen($str) && $length === 0) {
+ return '';
+ }
+
+ if ($start > Core::ourStrlen($str)) {
+ return false;
+ }
+
+ $substr = \mb_substr($str, $start, $length, '8bit');
+ if (Core::ourStrlen($substr) !== $length) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'Your version of PHP has bug #66797. Its implementation of
+ mb_substr() is incorrect. See the details here:
+ https://bugs.php.net/bug.php?id=66797'
+ );
+ }
+ return $substr;
+ }
+
+ // Unlike mb_substr(), substr() doesn't accept NULL for length
+ if (isset($length)) {
+ return \substr($str, $start, $length);
+ } else {
+ return \substr($str, $start);
+ }
+ }
+
+ /**
+ * Computes the PBKDF2 password-based key derivation function.
+ *
+ * The PBKDF2 function is defined in RFC 2898. Test vectors can be found in
+ * RFC 6070. This implementation of PBKDF2 was originally created by Taylor
+ * Hornby, with improvements from http://www.variations-of-shadow.com/.
+ *
+ * @param string $algorithm The hash algorithm to use. Recommended: SHA256
+ * @param string $password The password.
+ * @param string $salt A salt that is unique to the password.
+ * @param int $count Iteration count. Higher is better, but slower. Recommended: At least 1000.
+ * @param int $key_length The length of the derived key in bytes.
+ * @param bool $raw_output If true, the key is returned in raw binary format. Hex encoded otherwise.
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ *
+ * @return string A $key_length-byte key derived from the password and salt.
+ */
+ public static function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
+ {
+ // Type checks:
+ if (! \is_string($algorithm)) {
+ throw new \InvalidArgumentException(
+ 'pbkdf2(): algorithm must be a string'
+ );
+ }
+ if (! \is_string($password)) {
+ throw new \InvalidArgumentException(
+ 'pbkdf2(): password must be a string'
+ );
+ }
+ if (! \is_string($salt)) {
+ throw new \InvalidArgumentException(
+ 'pbkdf2(): salt must be a string'
+ );
+ }
+ // Coerce strings to integers with no information loss or overflow
+ $count += 0;
+ $key_length += 0;
+
+ $algorithm = \strtolower($algorithm);
+ if (! \in_array($algorithm, \hash_algos(), true)) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'Invalid or unsupported hash algorithm.'
+ );
+ }
+
+ // Whitelist, or we could end up with people using CRC32.
+ $ok_algorithms = [
+ 'sha1', 'sha224', 'sha256', 'sha384', 'sha512',
+ 'ripemd160', 'ripemd256', 'ripemd320', 'whirlpool',
+ ];
+ if (! \in_array($algorithm, $ok_algorithms, true)) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'Algorithm is not a secure cryptographic hash function.'
+ );
+ }
+
+ if ($count <= 0 || $key_length <= 0) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'Invalid PBKDF2 parameters.'
+ );
+ }
+
+ if (\function_exists('hash_pbkdf2')) {
+ // The output length is in NIBBLES (4-bits) if $raw_output is false!
+ if (! $raw_output) {
+ $key_length = $key_length * 2;
+ }
+ return \hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output);
+ }
+
+ $hash_length = Core::ourStrlen(\hash($algorithm, '', true));
+ $block_count = \ceil($key_length / $hash_length);
+
+ $output = '';
+ for ($i = 1; $i <= $block_count; $i++) {
+ // $i encoded as 4 bytes, big endian.
+ $last = $salt . \pack('N', $i);
+ // first iteration
+ $last = $xorsum = \hash_hmac($algorithm, $last, $password, true);
+ // perform the other $count - 1 iterations
+ for ($j = 1; $j < $count; $j++) {
+ $xorsum ^= ($last = \hash_hmac($algorithm, $last, $password, true));
+ }
+ $output .= $xorsum;
+ }
+
+ if ($raw_output) {
+ return Core::ourSubstr($output, 0, $key_length);
+ } else {
+ return Encoding::binToHex(Core::ourSubstr($output, 0, $key_length));
+ }
+ }
+}
diff --git a/tools/defuse/php-encryption/src/Crypto.php b/tools/defuse/php-encryption/src/Crypto.php
new file mode 100644
index 000000000..3acd6112a
--- /dev/null
+++ b/tools/defuse/php-encryption/src/Crypto.php
@@ -0,0 +1,372 @@
+deriveKeys($salt);
+ $ekey = $keys->getEncryptionKey();
+ $akey = $keys->getAuthenticationKey();
+ $iv = Core::secureRandom(Core::BLOCK_BYTE_SIZE);
+
+ $ciphertext = Core::CURRENT_VERSION . $salt . $iv . self::plainEncrypt($plaintext, $ekey, $iv);
+ $auth = \hash_hmac(Core::HASH_FUNCTION_NAME, $ciphertext, $akey, true);
+ $ciphertext = $ciphertext . $auth;
+
+ if ($raw_binary) {
+ return $ciphertext;
+ }
+ return Encoding::binToHex($ciphertext);
+ }
+
+ /**
+ * Decrypts a ciphertext to a string with either a key or a password.
+ *
+ * @param string $ciphertext
+ * @param KeyOrPassword $secret
+ * @param bool $raw_binary
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ * @throws Ex\WrongKeyOrModifiedCiphertextException
+ *
+ * @return string
+ */
+ private static function decryptInternal($ciphertext, KeyOrPassword $secret, $raw_binary)
+ {
+ RuntimeTests::runtimeTest();
+
+ if (! $raw_binary) {
+ try {
+ $ciphertext = Encoding::hexToBin($ciphertext);
+ } catch (Ex\BadFormatException $ex) {
+ throw new Ex\WrongKeyOrModifiedCiphertextException(
+ 'Ciphertext has invalid hex encoding.'
+ );
+ }
+ }
+
+ if (Core::ourStrlen($ciphertext) < Core::MINIMUM_CIPHERTEXT_SIZE) {
+ throw new Ex\WrongKeyOrModifiedCiphertextException(
+ 'Ciphertext is too short.'
+ );
+ }
+
+ // Get and check the version header.
+ $header = Core::ourSubstr($ciphertext, 0, Core::HEADER_VERSION_SIZE);
+ if ($header !== Core::CURRENT_VERSION) {
+ throw new Ex\WrongKeyOrModifiedCiphertextException(
+ 'Bad version header.'
+ );
+ }
+
+ // Get the salt.
+ $salt = Core::ourSubstr(
+ $ciphertext,
+ Core::HEADER_VERSION_SIZE,
+ Core::SALT_BYTE_SIZE
+ );
+ if ($salt === false) {
+ throw new Ex\EnvironmentIsBrokenException();
+ }
+
+ // Get the IV.
+ $iv = Core::ourSubstr(
+ $ciphertext,
+ Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE,
+ Core::BLOCK_BYTE_SIZE
+ );
+ if ($iv === false) {
+ throw new Ex\EnvironmentIsBrokenException();
+ }
+
+ // Get the HMAC.
+ $hmac = Core::ourSubstr(
+ $ciphertext,
+ Core::ourStrlen($ciphertext) - Core::MAC_BYTE_SIZE,
+ Core::MAC_BYTE_SIZE
+ );
+ if ($hmac === false) {
+ throw new Ex\EnvironmentIsBrokenException();
+ }
+
+ // Get the actual encrypted ciphertext.
+ $encrypted = Core::ourSubstr(
+ $ciphertext,
+ Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE +
+ Core::BLOCK_BYTE_SIZE,
+ Core::ourStrlen($ciphertext) - Core::MAC_BYTE_SIZE - Core::SALT_BYTE_SIZE -
+ Core::BLOCK_BYTE_SIZE - Core::HEADER_VERSION_SIZE
+ );
+ if ($encrypted === false) {
+ throw new Ex\EnvironmentIsBrokenException();
+ }
+
+ // Derive the separate encryption and authentication keys from the key
+ // or password, whichever it is.
+ $keys = $secret->deriveKeys($salt);
+
+ if (self::verifyHMAC($hmac, $header . $salt . $iv . $encrypted, $keys->getAuthenticationKey())) {
+ $plaintext = self::plainDecrypt($encrypted, $keys->getEncryptionKey(), $iv, Core::CIPHER_METHOD);
+ return $plaintext;
+ } else {
+ throw new Ex\WrongKeyOrModifiedCiphertextException(
+ 'Integrity check failed.'
+ );
+ }
+ }
+
+ /**
+ * Raw unauthenticated encryption (insecure on its own).
+ *
+ * @param string $plaintext
+ * @param string $key
+ * @param string $iv
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ *
+ * @return string
+ */
+ protected static function plainEncrypt($plaintext, $key, $iv)
+ {
+ Core::ensureConstantExists('OPENSSL_RAW_DATA');
+ Core::ensureFunctionExists('openssl_encrypt');
+ $ciphertext = \openssl_encrypt(
+ $plaintext,
+ Core::CIPHER_METHOD,
+ $key,
+ OPENSSL_RAW_DATA,
+ $iv
+ );
+
+ if ($ciphertext === false) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'openssl_encrypt() failed.'
+ );
+ }
+
+ return $ciphertext;
+ }
+
+ /**
+ * Raw unauthenticated decryption (insecure on its own).
+ *
+ * @param string $ciphertext
+ * @param string $key
+ * @param string $iv
+ * @param string $cipherMethod
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ *
+ * @return string
+ */
+ protected static function plainDecrypt($ciphertext, $key, $iv, $cipherMethod)
+ {
+ Core::ensureConstantExists('OPENSSL_RAW_DATA');
+ Core::ensureFunctionExists('openssl_decrypt');
+ $plaintext = \openssl_decrypt(
+ $ciphertext,
+ $cipherMethod,
+ $key,
+ OPENSSL_RAW_DATA,
+ $iv
+ );
+ if ($plaintext === false) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'openssl_decrypt() failed.'
+ );
+ }
+
+ return $plaintext;
+ }
+
+ /**
+ * Verifies an HMAC without leaking information through side-channels.
+ *
+ * @param string $correct_hmac
+ * @param string $message
+ * @param string $key
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ *
+ * @return bool
+ */
+ protected static function verifyHMAC($correct_hmac, $message, $key)
+ {
+ $message_hmac = \hash_hmac(Core::HASH_FUNCTION_NAME, $message, $key, true);
+ return Core::hashEquals($correct_hmac, $message_hmac);
+ }
+}
diff --git a/tools/defuse/php-encryption/src/DerivedKeys.php b/tools/defuse/php-encryption/src/DerivedKeys.php
new file mode 100644
index 000000000..fcfc04307
--- /dev/null
+++ b/tools/defuse/php-encryption/src/DerivedKeys.php
@@ -0,0 +1,37 @@
+akey;
+ }
+
+ /**
+ * Returns the encryption key.
+ */
+ public function getEncryptionKey()
+ {
+ return $this->ekey;
+ }
+
+ /**
+ * Constructor for DerivedKeys.
+ *
+ * @param string $akey
+ * @param string $ekey
+ */
+ public function __construct($akey, $ekey)
+ {
+ $this->akey = $akey;
+ $this->ekey = $ekey;
+ }
+}
diff --git a/tools/defuse/php-encryption/src/Encoding.php b/tools/defuse/php-encryption/src/Encoding.php
new file mode 100644
index 000000000..6e022d779
--- /dev/null
+++ b/tools/defuse/php-encryption/src/Encoding.php
@@ -0,0 +1,212 @@
+> 4;
+ $hex .= \pack(
+ 'CC',
+ 87 + $b + ((($b - 10) >> 8) & ~38),
+ 87 + $c + ((($c - 10) >> 8) & ~38)
+ );
+ }
+ return $hex;
+ }
+
+ /**
+ * Converts a hexadecimal string into a byte string without leaking
+ * information through side channels.
+ *
+ * @param string $hex_string
+ *
+ * @throws Ex\BadFormatException
+ * @throws Ex\EnvironmentIsBrokenException
+ *
+ * @return string
+ */
+ public static function hexToBin($hex_string)
+ {
+ $hex_pos = 0;
+ $bin = '';
+ $hex_len = Core::ourStrlen($hex_string);
+ $state = 0;
+ $c_acc = 0;
+
+ while ($hex_pos < $hex_len) {
+ $c = \ord($hex_string[$hex_pos]);
+ $c_num = $c ^ 48;
+ $c_num0 = ($c_num - 10) >> 8;
+ $c_alpha = ($c & ~32) - 55;
+ $c_alpha0 = (($c_alpha - 10) ^ ($c_alpha - 16)) >> 8;
+ if (($c_num0 | $c_alpha0) === 0) {
+ throw new Ex\BadFormatException(
+ 'Encoding::hexToBin() input is not a hex string.'
+ );
+ }
+ $c_val = ($c_num0 & $c_num) | ($c_alpha & $c_alpha0);
+ if ($state === 0) {
+ $c_acc = $c_val * 16;
+ } else {
+ $bin .= \pack('C', $c_acc | $c_val);
+ }
+ $state ^= 1;
+ ++$hex_pos;
+ }
+ return $bin;
+ }
+
+ /*
+ * SECURITY NOTE ON APPLYING CHECKSUMS TO SECRETS:
+ *
+ * The checksum introduces a potential security weakness. For example,
+ * suppose we apply a checksum to a key, and that an adversary has an
+ * exploit against the process containing the key, such that they can
+ * overwrite an arbitrary byte of memory and then cause the checksum to
+ * be verified and learn the result.
+ *
+ * In this scenario, the adversary can extract the key one byte at
+ * a time by overwriting it with their guess of its value and then
+ * asking if the checksum matches. If it does, their guess was right.
+ * This kind of attack may be more easy to implement and more reliable
+ * than a remote code execution attack.
+ *
+ * This attack also applies to authenticated encryption as a whole, in
+ * the situation where the adversary can overwrite a byte of the key
+ * and then cause a valid ciphertext to be decrypted, and then
+ * determine whether the MAC check passed or failed.
+ *
+ * By using the full SHA256 hash instead of truncating it, I'm ensuring
+ * that both ways of going about the attack are equivalently difficult.
+ * A shorter checksum of say 32 bits might be more useful to the
+ * adversary as an oracle in case their writes are coarser grained.
+ *
+ * Because the scenario assumes a serious vulnerability, we don't try
+ * to prevent attacks of this style.
+ */
+
+ /**
+ * INTERNAL USE ONLY: Applies a version header, applies a checksum, and
+ * then encodes a byte string into a range of printable ASCII characters.
+ *
+ * @param string $header
+ * @param string $bytes
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ *
+ * @return string
+ */
+ public static function saveBytesToChecksummedAsciiSafeString($header, $bytes)
+ {
+ // Headers must be a constant length to prevent one type's header from
+ // being a prefix of another type's header, leading to ambiguity.
+ if (Core::ourStrlen($header) !== self::SERIALIZE_HEADER_BYTES) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'Header must be ' . self::SERIALIZE_HEADER_BYTES . ' bytes.'
+ );
+ }
+
+ return Encoding::binToHex(
+ $header .
+ $bytes .
+ \hash(
+ self::CHECKSUM_HASH_ALGO,
+ $header . $bytes,
+ true
+ )
+ );
+ }
+
+ /**
+ * INTERNAL USE ONLY: Decodes, verifies the header and checksum, and returns
+ * the encoded byte string.
+ *
+ * @param string $expected_header
+ * @param string $string
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ * @throws Ex\BadFormatException
+ *
+ * @return string
+ */
+ public static function loadBytesFromChecksummedAsciiSafeString($expected_header, $string)
+ {
+ // Headers must be a constant length to prevent one type's header from
+ // being a prefix of another type's header, leading to ambiguity.
+ if (Core::ourStrlen($expected_header) !== self::SERIALIZE_HEADER_BYTES) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'Header must be 4 bytes.'
+ );
+ }
+
+ $bytes = Encoding::hexToBin($string);
+
+ /* Make sure we have enough bytes to get the version header and checksum. */
+ if (Core::ourStrlen($bytes) < self::SERIALIZE_HEADER_BYTES + self::CHECKSUM_BYTE_SIZE) {
+ throw new Ex\BadFormatException(
+ 'Encoded data is shorter than expected.'
+ );
+ }
+
+ /* Grab the version header. */
+ $actual_header = Core::ourSubstr($bytes, 0, self::SERIALIZE_HEADER_BYTES);
+
+ if ($actual_header !== $expected_header) {
+ throw new Ex\BadFormatException(
+ 'Invalid header.'
+ );
+ }
+
+ /* Grab the bytes that are part of the checksum. */
+ $checked_bytes = Core::ourSubstr(
+ $bytes,
+ 0,
+ Core::ourStrlen($bytes) - self::CHECKSUM_BYTE_SIZE
+ );
+
+ /* Grab the included checksum. */
+ $checksum_a = Core::ourSubstr(
+ $bytes,
+ Core::ourStrlen($bytes) - self::CHECKSUM_BYTE_SIZE,
+ self::CHECKSUM_BYTE_SIZE
+ );
+
+ /* Re-compute the checksum. */
+ $checksum_b = \hash(self::CHECKSUM_HASH_ALGO, $checked_bytes, true);
+
+ /* Check if the checksum matches. */
+ if (! Core::hashEquals($checksum_a, $checksum_b)) {
+ throw new Ex\BadFormatException(
+ "Data is corrupted, the checksum doesn't match"
+ );
+ }
+
+ return Core::ourSubstr(
+ $bytes,
+ self::SERIALIZE_HEADER_BYTES,
+ Core::ourStrlen($bytes) - self::SERIALIZE_HEADER_BYTES - self::CHECKSUM_BYTE_SIZE
+ );
+ }
+}
diff --git a/tools/defuse/php-encryption/src/Exception/BadFormatException.php b/tools/defuse/php-encryption/src/Exception/BadFormatException.php
new file mode 100644
index 000000000..804d9c17e
--- /dev/null
+++ b/tools/defuse/php-encryption/src/Exception/BadFormatException.php
@@ -0,0 +1,7 @@
+
+* @copyright 2010-2021 Webkul IN
+* @license https://store.webkul.com/license.html
+*/
+
+header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
+header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
+
+header('Cache-Control: no-store, no-cache, must-revalidate');
+header('Cache-Control: post-check=0, pre-check=0', false);
+header('Pragma: no-cache');
+
+header('Location: ../');
+exit;
diff --git a/tools/defuse/php-encryption/src/File.php b/tools/defuse/php-encryption/src/File.php
new file mode 100644
index 000000000..d73907775
--- /dev/null
+++ b/tools/defuse/php-encryption/src/File.php
@@ -0,0 +1,755 @@
+deriveKeys($file_salt);
+ $ekey = $keys->getEncryptionKey();
+ $akey = $keys->getAuthenticationKey();
+
+ $ivsize = Core::BLOCK_BYTE_SIZE;
+ $iv = Core::secureRandom($ivsize);
+
+ /* Initialize a streaming HMAC state. */
+ $hmac = \hash_init(Core::HASH_FUNCTION_NAME, HASH_HMAC, $akey);
+ if ($hmac === false) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'Cannot initialize a hash context'
+ );
+ }
+
+ /* Write the header, salt, and IV. */
+ self::writeBytes(
+ $outputHandle,
+ Core::CURRENT_VERSION . $file_salt . $iv,
+ Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE + $ivsize
+ );
+
+ /* Add the header, salt, and IV to the HMAC. */
+ \hash_update($hmac, Core::CURRENT_VERSION);
+ \hash_update($hmac, $file_salt);
+ \hash_update($hmac, $iv);
+
+ /* $thisIv will be incremented after each call to the encryption. */
+ $thisIv = $iv;
+
+ /* How many blocks do we encrypt at a time? We increment by this value. */
+ $inc = Core::BUFFER_BYTE_SIZE / Core::BLOCK_BYTE_SIZE;
+
+ /* Loop until we reach the end of the input file. */
+ $at_file_end = false;
+ while (! (\feof($inputHandle) || $at_file_end)) {
+ /* Find out if we can read a full buffer, or only a partial one. */
+ $pos = \ftell($inputHandle);
+ if ($pos === false) {
+ throw new Ex\IOException(
+ 'Could not get current position in input file during encryption'
+ );
+ }
+ if ($pos + Core::BUFFER_BYTE_SIZE >= $inputSize) {
+ /* We're at the end of the file, so we need to break out of the loop. */
+ $at_file_end = true;
+ $read = self::readBytes(
+ $inputHandle,
+ $inputSize - $pos
+ );
+ } else {
+ $read = self::readBytes(
+ $inputHandle,
+ Core::BUFFER_BYTE_SIZE
+ );
+ }
+
+ /* Encrypt this buffer. */
+ $encrypted = \openssl_encrypt(
+ $read,
+ Core::CIPHER_METHOD,
+ $ekey,
+ OPENSSL_RAW_DATA,
+ $thisIv
+ );
+
+ if ($encrypted === false) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'OpenSSL encryption error'
+ );
+ }
+
+ /* Write this buffer's ciphertext. */
+ self::writeBytes($outputHandle, $encrypted, Core::ourStrlen($encrypted));
+ /* Add this buffer's ciphertext to the HMAC. */
+ \hash_update($hmac, $encrypted);
+
+ /* Increment the counter by the number of blocks in a buffer. */
+ $thisIv = Core::incrementCounter($thisIv, $inc);
+ /* WARNING: Usually, unless the file is a multiple of the buffer
+ * size, $thisIv will contain an incorrect value here on the last
+ * iteration of this loop. */
+ }
+
+ /* Get the HMAC and append it to the ciphertext. */
+ $final_mac = \hash_final($hmac, true);
+ self::writeBytes($outputHandle, $final_mac, Core::MAC_BYTE_SIZE);
+ }
+
+ /**
+ * Decrypts a file-backed resource with either a key or a password.
+ *
+ * @param resource $inputHandle
+ * @param resource $outputHandle
+ * @param KeyOrPassword $secret
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ * @throws Ex\IOException
+ * @throws Ex\WrongKeyOrModifiedCiphertextException
+ */
+ public static function decryptResourceInternal($inputHandle, $outputHandle, KeyOrPassword $secret)
+ {
+ if (! \is_resource($inputHandle)) {
+ throw new Ex\IOException(
+ 'Input handle must be a resource!'
+ );
+ }
+ if (! \is_resource($outputHandle)) {
+ throw new Ex\IOException(
+ 'Output handle must be a resource!'
+ );
+ }
+
+ /* Make sure the file is big enough for all the reads we need to do. */
+ $stat = \fstat($inputHandle);
+ if ($stat['size'] < Core::MINIMUM_CIPHERTEXT_SIZE) {
+ throw new Ex\WrongKeyOrModifiedCiphertextException(
+ 'Input file is too small to have been created by this library.'
+ );
+ }
+
+ /* Check the version header. */
+ $header = self::readBytes($inputHandle, Core::HEADER_VERSION_SIZE);
+ if ($header !== Core::CURRENT_VERSION) {
+ throw new Ex\WrongKeyOrModifiedCiphertextException(
+ 'Bad version header.'
+ );
+ }
+
+ /* Get the salt. */
+ $file_salt = self::readBytes($inputHandle, Core::SALT_BYTE_SIZE);
+
+ /* Get the IV. */
+ $ivsize = Core::BLOCK_BYTE_SIZE;
+ $iv = self::readBytes($inputHandle, $ivsize);
+
+ /* Derive the authentication and encryption keys. */
+ $keys = $secret->deriveKeys($file_salt);
+ $ekey = $keys->getEncryptionKey();
+ $akey = $keys->getAuthenticationKey();
+
+ /* We'll store the MAC of each buffer-sized chunk as we verify the
+ * actual MAC, so that we can check them again when decrypting. */
+ $macs = [];
+
+ /* $thisIv will be incremented after each call to the decryption. */
+ $thisIv = $iv;
+
+ /* How many blocks do we encrypt at a time? We increment by this value. */
+ $inc = Core::BUFFER_BYTE_SIZE / Core::BLOCK_BYTE_SIZE;
+
+ /* Get the HMAC. */
+ if (\fseek($inputHandle, (-1 * Core::MAC_BYTE_SIZE), SEEK_END) === false) {
+ throw new Ex\IOException(
+ 'Cannot seek to beginning of MAC within input file'
+ );
+ }
+
+ /* Get the position of the last byte in the actual ciphertext. */
+ $cipher_end = \ftell($inputHandle);
+ if ($cipher_end === false) {
+ throw new Ex\IOException(
+ 'Cannot read input file'
+ );
+ }
+ /* We have the position of the first byte of the HMAC. Go back by one. */
+ --$cipher_end;
+
+ /* Read the HMAC. */
+ $stored_mac = self::readBytes($inputHandle, Core::MAC_BYTE_SIZE);
+
+ /* Initialize a streaming HMAC state. */
+ $hmac = \hash_init(Core::HASH_FUNCTION_NAME, HASH_HMAC, $akey);
+ if ($hmac === false) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'Cannot initialize a hash context'
+ );
+ }
+
+ /* Reset file pointer to the beginning of the file after the header */
+ if (\fseek($inputHandle, Core::HEADER_VERSION_SIZE, SEEK_SET) === false) {
+ throw new Ex\IOException(
+ 'Cannot read seek within input file'
+ );
+ }
+
+ /* Seek to the start of the actual ciphertext. */
+ if (\fseek($inputHandle, Core::SALT_BYTE_SIZE + $ivsize, SEEK_CUR) === false) {
+ throw new Ex\IOException(
+ 'Cannot seek input file to beginning of ciphertext'
+ );
+ }
+
+ /* PASS #1: Calculating the HMAC. */
+
+ \hash_update($hmac, $header);
+ \hash_update($hmac, $file_salt);
+ \hash_update($hmac, $iv);
+ $hmac2 = \hash_copy($hmac);
+
+ $break = false;
+ while (! $break) {
+ $pos = \ftell($inputHandle);
+ if ($pos === false) {
+ throw new Ex\IOException(
+ 'Could not get current position in input file during decryption'
+ );
+ }
+
+ /* Read the next buffer-sized chunk (or less). */
+ if ($pos + Core::BUFFER_BYTE_SIZE >= $cipher_end) {
+ $break = true;
+ $read = self::readBytes(
+ $inputHandle,
+ $cipher_end - $pos + 1
+ );
+ } else {
+ $read = self::readBytes(
+ $inputHandle,
+ Core::BUFFER_BYTE_SIZE
+ );
+ }
+
+ /* Update the HMAC. */
+ \hash_update($hmac, $read);
+
+ /* Remember this buffer-sized chunk's HMAC. */
+ $chunk_mac = \hash_copy($hmac);
+ if ($chunk_mac === false) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'Cannot duplicate a hash context'
+ );
+ }
+ $macs []= \hash_final($chunk_mac);
+ }
+
+ /* Get the final HMAC, which should match the stored one. */
+ $final_mac = \hash_final($hmac, true);
+
+ /* Verify the HMAC. */
+ if (! Core::hashEquals($final_mac, $stored_mac)) {
+ throw new Ex\WrongKeyOrModifiedCiphertextException(
+ 'Integrity check failed.'
+ );
+ }
+
+ /* PASS #2: Decrypt and write output. */
+
+ /* Rewind to the start of the actual ciphertext. */
+ if (\fseek($inputHandle, Core::SALT_BYTE_SIZE + $ivsize + Core::HEADER_VERSION_SIZE, SEEK_SET) === false) {
+ throw new Ex\IOException(
+ 'Could not move the input file pointer during decryption'
+ );
+ }
+
+ $at_file_end = false;
+ while (! $at_file_end) {
+ $pos = \ftell($inputHandle);
+ if ($pos === false) {
+ throw new Ex\IOException(
+ 'Could not get current position in input file during decryption'
+ );
+ }
+
+ /* Read the next buffer-sized chunk (or less). */
+ if ($pos + Core::BUFFER_BYTE_SIZE >= $cipher_end) {
+ $at_file_end = true;
+ $read = self::readBytes(
+ $inputHandle,
+ $cipher_end - $pos + 1
+ );
+ } else {
+ $read = self::readBytes(
+ $inputHandle,
+ Core::BUFFER_BYTE_SIZE
+ );
+ }
+
+ /* Recalculate the MAC (so far) and compare it with the one we
+ * remembered from pass #1 to ensure attackers didn't change the
+ * ciphertext after MAC verification. */
+ \hash_update($hmac2, $read);
+ $calc_mac = \hash_copy($hmac2);
+ if ($calc_mac === false) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'Cannot duplicate a hash context'
+ );
+ }
+ $calc = \hash_final($calc_mac);
+
+ if (empty($macs)) {
+ throw new Ex\WrongKeyOrModifiedCiphertextException(
+ 'File was modified after MAC verification'
+ );
+ } elseif (! Core::hashEquals(\array_shift($macs), $calc)) {
+ throw new Ex\WrongKeyOrModifiedCiphertextException(
+ 'File was modified after MAC verification'
+ );
+ }
+
+ /* Decrypt this buffer-sized chunk. */
+ $decrypted = \openssl_decrypt(
+ $read,
+ Core::CIPHER_METHOD,
+ $ekey,
+ OPENSSL_RAW_DATA,
+ $thisIv
+ );
+ if ($decrypted === false) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'OpenSSL decryption error'
+ );
+ }
+
+ /* Write the plaintext to the output file. */
+ self::writeBytes(
+ $outputHandle,
+ $decrypted,
+ Core::ourStrlen($decrypted)
+ );
+
+ /* Increment the IV by the amount of blocks in a buffer. */
+ $thisIv = Core::incrementCounter($thisIv, $inc);
+ /* WARNING: Usually, unless the file is a multiple of the buffer
+ * size, $thisIv will contain an incorrect value here on the last
+ * iteration of this loop. */
+ }
+ }
+
+ /**
+ * Read from a stream; prevent partial reads.
+ *
+ * @param resource $stream
+ * @param int $num_bytes
+ *
+ * @throws Ex\IOException
+ * @throws Ex\EnvironmentIsBrokenException
+ *
+ * @return string
+ */
+ public static function readBytes($stream, $num_bytes)
+ {
+ if ($num_bytes < 0) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'Tried to read less than 0 bytes'
+ );
+ } elseif ($num_bytes === 0) {
+ return '';
+ }
+ $buf = '';
+ $remaining = $num_bytes;
+ while ($remaining > 0 && ! \feof($stream)) {
+ $read = \fread($stream, $remaining);
+
+ if ($read === false) {
+ throw new Ex\IOException(
+ 'Could not read from the file'
+ );
+ }
+ $buf .= $read;
+ $remaining -= Core::ourStrlen($read);
+ }
+ if (Core::ourStrlen($buf) !== $num_bytes) {
+ throw new Ex\IOException(
+ 'Tried to read past the end of the file'
+ );
+ }
+ return $buf;
+ }
+
+ /**
+ * Write to a stream; prevents partial writes.
+ *
+ * @param resource $stream
+ * @param string $buf
+ * @param int $num_bytes
+ *
+ * @throws Ex\IOException
+ *
+ * @return string
+ */
+ public static function writeBytes($stream, $buf, $num_bytes = null)
+ {
+ $bufSize = Core::ourStrlen($buf);
+ if ($num_bytes === null) {
+ $num_bytes = $bufSize;
+ }
+ if ($num_bytes > $bufSize) {
+ throw new Ex\IOException(
+ 'Trying to write more bytes than the buffer contains.'
+ );
+ }
+ if ($num_bytes < 0) {
+ throw new Ex\IOException(
+ 'Tried to write less than 0 bytes'
+ );
+ }
+ $remaining = $num_bytes;
+ while ($remaining > 0) {
+ $written = \fwrite($stream, $buf, $remaining);
+ if ($written === false) {
+ throw new Ex\IOException(
+ 'Could not write to the file'
+ );
+ }
+ $buf = Core::ourSubstr($buf, $written, null);
+ $remaining -= $written;
+ }
+ return $num_bytes;
+ }
+
+ /**
+ * Returns the last PHP error's or warning's message string.
+ *
+ * @return string
+ */
+ private static function getLastErrorMessage()
+ {
+ $error = error_get_last();
+ if ($error === null) {
+ return '[no PHP error]';
+ } else {
+ return $error['message'];
+ }
+ }
+}
diff --git a/tools/defuse/php-encryption/src/Key.php b/tools/defuse/php-encryption/src/Key.php
new file mode 100644
index 000000000..ca7b9b2ee
--- /dev/null
+++ b/tools/defuse/php-encryption/src/Key.php
@@ -0,0 +1,84 @@
+key_bytes
+ );
+ }
+
+ /**
+ * Gets the raw bytes of the key.
+ *
+ * @return string
+ */
+ public function getRawBytes()
+ {
+ return $this->key_bytes;
+ }
+
+ /**
+ * Constructs a new Key object from a string of raw bytes.
+ *
+ * @param string $bytes
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ */
+ private function __construct($bytes)
+ {
+ if (Core::ourStrlen($bytes) !== self::KEY_BYTE_SIZE) {
+ throw new Ex\EnvironmentIsBrokenException(
+ 'Bad key length.'
+ );
+ }
+ $this->key_bytes = $bytes;
+ }
+
+}
diff --git a/tools/defuse/php-encryption/src/KeyOrPassword.php b/tools/defuse/php-encryption/src/KeyOrPassword.php
new file mode 100644
index 000000000..2a46b711a
--- /dev/null
+++ b/tools/defuse/php-encryption/src/KeyOrPassword.php
@@ -0,0 +1,119 @@
+secret_type === self::SECRET_TYPE_KEY) {
+ $akey = Core::HKDF(
+ Core::HASH_FUNCTION_NAME,
+ $this->secret->getRawBytes(),
+ Core::KEY_BYTE_SIZE,
+ Core::AUTHENTICATION_INFO_STRING,
+ $salt
+ );
+ $ekey = Core::HKDF(
+ Core::HASH_FUNCTION_NAME,
+ $this->secret->getRawBytes(),
+ Core::KEY_BYTE_SIZE,
+ Core::ENCRYPTION_INFO_STRING,
+ $salt
+ );
+ return new DerivedKeys($akey, $ekey);
+ } elseif ($this->secret_type === self::SECRET_TYPE_PASSWORD) {
+ /* Our PBKDF2 polyfill is vulnerable to a DoS attack documented in
+ * GitHub issue #230. The fix is to pre-hash the password to ensure
+ * it is short. We do the prehashing here instead of in pbkdf2() so
+ * that pbkdf2() still computes the function as defined by the
+ * standard. */
+ $prehash = \hash(Core::HASH_FUNCTION_NAME, $this->secret, true);
+ $prekey = Core::pbkdf2(
+ Core::HASH_FUNCTION_NAME,
+ $prehash,
+ $salt,
+ self::PBKDF2_ITERATIONS,
+ Core::KEY_BYTE_SIZE,
+ true
+ );
+ $akey = Core::HKDF(
+ Core::HASH_FUNCTION_NAME,
+ $prekey,
+ Core::KEY_BYTE_SIZE,
+ Core::AUTHENTICATION_INFO_STRING,
+ $salt
+ );
+ /* Note the cryptographic re-use of $salt here. */
+ $ekey = Core::HKDF(
+ Core::HASH_FUNCTION_NAME,
+ $prekey,
+ Core::KEY_BYTE_SIZE,
+ Core::ENCRYPTION_INFO_STRING,
+ $salt
+ );
+ return new DerivedKeys($akey, $ekey);
+ } else {
+ throw new Ex\EnvironmentIsBrokenException('Bad secret type.');
+ }
+ }
+
+ /**
+ * Constructor for KeyOrPassword.
+ *
+ * @param int $secret_type
+ * @param mixed $secret (either a Key or a password string)
+ */
+ private function __construct($secret_type, $secret)
+ {
+ $this->secret_type = $secret_type;
+ $this->secret = $secret;
+ }
+}
diff --git a/tools/defuse/php-encryption/src/KeyProtectedByPassword.php b/tools/defuse/php-encryption/src/KeyProtectedByPassword.php
new file mode 100644
index 000000000..b9a40f157
--- /dev/null
+++ b/tools/defuse/php-encryption/src/KeyProtectedByPassword.php
@@ -0,0 +1,112 @@
+saveToAsciiSafeString(),
+ \hash(Core::HASH_FUNCTION_NAME, $password, true),
+ true
+ );
+
+ return new KeyProtectedByPassword($encrypted_key);
+ }
+
+ /**
+ * Loads a KeyProtectedByPassword from its encoded form.
+ *
+ * @param string $saved_key_string
+ *
+ * @throws Ex\BadFormatException
+ *
+ * @return KeyProtectedByPassword
+ */
+ public static function loadFromAsciiSafeString($saved_key_string)
+ {
+ $encrypted_key = Encoding::loadBytesFromChecksummedAsciiSafeString(
+ self::PASSWORD_KEY_CURRENT_VERSION,
+ $saved_key_string
+ );
+ return new KeyProtectedByPassword($encrypted_key);
+ }
+
+ /**
+ * Encodes the KeyProtectedByPassword into a string of printable ASCII
+ * characters.
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ *
+ * @return string
+ */
+ public function saveToAsciiSafeString()
+ {
+ return Encoding::saveBytesToChecksummedAsciiSafeString(
+ self::PASSWORD_KEY_CURRENT_VERSION,
+ $this->encrypted_key
+ );
+ }
+
+ /**
+ * Decrypts the protected key, returning an unprotected Key object that can
+ * be used for encryption and decryption.
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ * @throws Ex\WrongKeyOrModifiedCiphertextException
+ *
+ * @return Key
+ */
+ public function unlockKey($password)
+ {
+ try {
+ $inner_key_encoded = Crypto::decryptWithPassword(
+ $this->encrypted_key,
+ \hash(Core::HASH_FUNCTION_NAME, $password, true),
+ true
+ );
+ return Key::loadFromAsciiSafeString($inner_key_encoded);
+ } catch (Ex\BadFormatException $ex) {
+ /* This should never happen unless an attacker replaced the
+ * encrypted key ciphertext with some other ciphertext that was
+ * encrypted with the same password. We transform the exception type
+ * here in order to make the API simpler, avoiding the need to
+ * document that this method might throw an Ex\BadFormatException. */
+ throw new Ex\WrongKeyOrModifiedCiphertextException(
+ "The decrypted key was found to be in an invalid format. " .
+ "This very likely indicates it was modified by an attacker."
+ );
+ }
+ }
+
+ /**
+ * Constructor for KeyProtectedByPassword.
+ *
+ * @param string $encrypted_key
+ */
+ private function __construct($encrypted_key)
+ {
+ $this->encrypted_key = $encrypted_key;
+ }
+}
diff --git a/tools/defuse/php-encryption/src/RuntimeTests.php b/tools/defuse/php-encryption/src/RuntimeTests.php
new file mode 100644
index 000000000..9c0ba339d
--- /dev/null
+++ b/tools/defuse/php-encryption/src/RuntimeTests.php
@@ -0,0 +1,242 @@
+getRawBytes()) != Core::KEY_BYTE_SIZE) {
+ throw new Ex\EnvironmentIsBrokenException();
+ }
+
+ if (Core::ENCRYPTION_INFO_STRING == Core::AUTHENTICATION_INFO_STRING) {
+ throw new Ex\EnvironmentIsBrokenException();
+ }
+ } catch (Ex\EnvironmentIsBrokenException $ex) {
+ // Do this, otherwise it will stay in the "tests are running" state.
+ $test_state = 3;
+ throw $ex;
+ }
+
+ // Change this to '0' make the tests always re-run (for benchmarking).
+ $test_state = 1;
+ }
+
+ /**
+ * High-level tests of Crypto operations.
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ */
+ private static function testEncryptDecrypt()
+ {
+ $key = Key::createNewRandomKey();
+ $data = "EnCrYpT EvErYThInG\x00\x00";
+
+ // Make sure encrypting then decrypting doesn't change the message.
+ $ciphertext = Crypto::encrypt($data, $key, true);
+ try {
+ $decrypted = Crypto::decrypt($ciphertext, $key, true);
+ } catch (Ex\WrongKeyOrModifiedCiphertextException $ex) {
+ // It's important to catch this and change it into a
+ // Ex\EnvironmentIsBrokenException, otherwise a test failure could trick
+ // the user into thinking it's just an invalid ciphertext!
+ throw new Ex\EnvironmentIsBrokenException();
+ }
+ if ($decrypted !== $data) {
+ throw new Ex\EnvironmentIsBrokenException();
+ }
+
+ // Modifying the ciphertext: Appending a string.
+ try {
+ Crypto::decrypt($ciphertext . 'a', $key, true);
+ throw new Ex\EnvironmentIsBrokenException();
+ } catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */
+ }
+
+ // Modifying the ciphertext: Changing an HMAC byte.
+ $indices_to_change = [
+ 0, // The header.
+ Core::HEADER_VERSION_SIZE + 1, // the salt
+ Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE + 1, // the IV
+ Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE + Core::BLOCK_BYTE_SIZE + 1, // the ciphertext
+ ];
+
+ foreach ($indices_to_change as $index) {
+ try {
+ $ciphertext[$index] = \chr((\ord($ciphertext[$index]) + 1) % 256);
+ Crypto::decrypt($ciphertext, $key, true);
+ throw new Ex\EnvironmentIsBrokenException();
+ } catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */
+ }
+ }
+
+ // Decrypting with the wrong key.
+ $key = Key::createNewRandomKey();
+ $data = 'abcdef';
+ $ciphertext = Crypto::encrypt($data, $key, true);
+ $wrong_key = Key::createNewRandomKey();
+ try {
+ Crypto::decrypt($ciphertext, $wrong_key, true);
+ throw new Ex\EnvironmentIsBrokenException();
+ } catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */
+ }
+
+ // Ciphertext too small.
+ $key = Key::createNewRandomKey();
+ $ciphertext = \str_repeat('A', Core::MINIMUM_CIPHERTEXT_SIZE - 1);
+ try {
+ Crypto::decrypt($ciphertext, $key, true);
+ throw new Ex\EnvironmentIsBrokenException();
+ } catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */
+ }
+ }
+
+ /**
+ * Test HKDF against test vectors.
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ */
+ private static function HKDFTestVector()
+ {
+ // HKDF test vectors from RFC 5869
+
+ // Test Case 1
+ $ikm = \str_repeat("\x0b", 22);
+ $salt = Encoding::hexToBin('000102030405060708090a0b0c');
+ $info = Encoding::hexToBin('f0f1f2f3f4f5f6f7f8f9');
+ $length = 42;
+ $okm = Encoding::hexToBin(
+ '3cb25f25faacd57a90434f64d0362f2a' .
+ '2d2d0a90cf1a5a4c5db02d56ecc4c5bf' .
+ '34007208d5b887185865'
+ );
+ $computed_okm = Core::HKDF('sha256', $ikm, $length, $info, $salt);
+ if ($computed_okm !== $okm) {
+ throw new Ex\EnvironmentIsBrokenException();
+ }
+
+ // Test Case 7
+ $ikm = \str_repeat("\x0c", 22);
+ $length = 42;
+ $okm = Encoding::hexToBin(
+ '2c91117204d745f3500d636a62f64f0a' .
+ 'b3bae548aa53d423b0d1f27ebba6f5e5' .
+ '673a081d70cce7acfc48'
+ );
+ $computed_okm = Core::HKDF('sha1', $ikm, $length, '', null);
+ if ($computed_okm !== $okm) {
+ throw new Ex\EnvironmentIsBrokenException();
+ }
+ }
+
+ /**
+ * Test HMAC against test vectors.
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ */
+ private static function HMACTestVector()
+ {
+ // HMAC test vector From RFC 4231 (Test Case 1)
+ $key = \str_repeat("\x0b", 20);
+ $data = 'Hi There';
+ $correct = 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7';
+ if (\hash_hmac(Core::HASH_FUNCTION_NAME, $data, $key) !== $correct) {
+ throw new Ex\EnvironmentIsBrokenException();
+ }
+ }
+
+ /**
+ * Test AES against test vectors.
+ *
+ * @throws Ex\EnvironmentIsBrokenException
+ */
+ private static function AESTestVector()
+ {
+ // AES CTR mode test vector from NIST SP 800-38A
+ $key = Encoding::hexToBin(
+ '603deb1015ca71be2b73aef0857d7781' .
+ '1f352c073b6108d72d9810a30914dff4'
+ );
+ $iv = Encoding::hexToBin('f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff');
+ $plaintext = Encoding::hexToBin(
+ '6bc1bee22e409f96e93d7e117393172a' .
+ 'ae2d8a571e03ac9c9eb76fac45af8e51' .
+ '30c81c46a35ce411e5fbc1191a0a52ef' .
+ 'f69f2445df4f9b17ad2b417be66c3710'
+ );
+ $ciphertext = Encoding::hexToBin(
+ '601ec313775789a5b7a7f504bbf3d228' .
+ 'f443e3ca4d62b59aca84e990cacaf5c5' .
+ '2b0930daa23de94ce87017ba2d84988d' .
+ 'dfc9c58db67aada613c2dd08457941a6'
+ );
+
+ $computed_ciphertext = Crypto::plainEncrypt($plaintext, $key, $iv);
+ if ($computed_ciphertext !== $ciphertext) {
+ echo \str_repeat("\n", 30);
+ echo \bin2hex($computed_ciphertext);
+ echo "\n---\n";
+ echo \bin2hex($ciphertext);
+ echo \str_repeat("\n", 30);
+ throw new Ex\EnvironmentIsBrokenException();
+ }
+
+ $computed_plaintext = Crypto::plainDecrypt($ciphertext, $key, $iv, Core::CIPHER_METHOD);
+ if ($computed_plaintext !== $plaintext) {
+ throw new Ex\EnvironmentIsBrokenException();
+ }
+ }
+}
diff --git a/tools/defuse/php-encryption/src/index.php b/tools/defuse/php-encryption/src/index.php
new file mode 100644
index 000000000..d9925ba54
--- /dev/null
+++ b/tools/defuse/php-encryption/src/index.php
@@ -0,0 +1,29 @@
+
+* @copyright 2010-2021 Webkul IN
+* @license https://store.webkul.com/license.html
+*/
+
+header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
+header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
+
+header('Cache-Control: no-store, no-cache, must-revalidate');
+header('Cache-Control: post-check=0, pre-check=0', false);
+header('Pragma: no-cache');
+
+header('Location: ../');
+exit;