Add support for Sodium encryption

Signed-off-by: Maurício Meneghini Fauth <mauricio@fauth.dev>
This commit is contained in:
Maurício Meneghini Fauth 2021-10-16 14:23:02 -03:00
parent 28ad63c969
commit 3c11d62b31
No known key found for this signature in database
GPG Key ID: 6A16FD38AFC89CC8
5 changed files with 111 additions and 16 deletions

View File

@ -76,7 +76,8 @@
"tecnickcom/tcpdf": "For PDF support",
"pragmarx/google2fa": "For 2FA authentication",
"bacon/bacon-qr-code": "For 2FA authentication",
"samyoul/u2f-php-server": "For FIDO U2F authentication"
"samyoul/u2f-php-server": "For FIDO U2F authentication",
"paragonie/sodium_compat": "For modern encryption support"
},
"require-dev": {
"phpunit/phpunit": "^4.8.36 || ^5.7",
@ -87,7 +88,9 @@
"pragmarx/google2fa": "^3.0",
"bacon/bacon-qr-code": "^1.0",
"samyoul/u2f-php-server": "^1.1",
"phpmyadmin/coding-standard": "^0.3"
"phpmyadmin/coding-standard": "^0.3",
"paragonie/random_compat": ">=2",
"paragonie/sodium_compat": "^1.17"
},
"extra": {
"branch-alias": {

View File

@ -1747,6 +1747,16 @@ Generic settings
and table name) from the URL query string. Default is to encrypt the URL
query string.
.. config:option:: $cfg['URLQueryEncryptionSecretKey']
:type: string
:default: ``''``
.. versionadded:: 4.9.8
A secret key used to encrypt/decrypt the URL query string.
Should be 32 bytes long.
Cookie authentication options
-----------------------------

View File

@ -7,14 +7,34 @@ use phpseclib\Crypt\Random;
final class Crypto
{
/** @var bool */
private $hasRandomBytesSupport;
/** @var bool */
private $hasSodiumSupport;
public function __construct()
{
$this->hasRandomBytesSupport = is_callable('random_bytes');
$this->hasSodiumSupport = $this->hasRandomBytesSupport
&& is_callable('sodium_crypto_secretbox')
&& is_callable('sodium_crypto_secretbox_open')
&& defined('SODIUM_CRYPTO_SECRETBOX_NONCEBYTES')
&& defined('SODIUM_CRYPTO_SECRETBOX_KEYBYTES');
}
/**
* @param string $plaintext
*
* @return string
*/
public static function encrypt($plaintext)
public function encrypt($plaintext)
{
return self::encryptWithPhpseclib($plaintext);
if ($this->hasSodiumSupport) {
return $this->encryptWithSodium($plaintext);
}
return $this->encryptWithPhpseclib($plaintext);
}
/**
@ -22,9 +42,38 @@ final class Crypto
*
* @return string
*/
public static function decrypt($ciphertext)
public function decrypt($ciphertext)
{
return self::decryptWithPhpseclib($ciphertext);
if ($this->hasSodiumSupport) {
return $this->decryptWithSodium($ciphertext);
}
return $this->decryptWithPhpseclib($ciphertext);
}
/**
* @return string
*/
private function getEncryptionKey()
{
global $PMA_Config;
$keyLength = $this->hasSodiumSupport ? SODIUM_CRYPTO_SECRETBOX_KEYBYTES : 32;
$key = $PMA_Config->get('URLQueryEncryptionSecretKey');
if (is_string($key) && mb_strlen($key, '8bit') === $keyLength) {
return $key;
}
$key = isset($_SESSION['URLQueryEncryptionSecretKey']) ? $_SESSION['URLQueryEncryptionSecretKey'] : null;
if (is_string($key) && mb_strlen($key, '8bit') === $keyLength) {
return $key;
}
$key = $this->hasRandomBytesSupport ? random_bytes($keyLength) : Random::string($keyLength);
$_SESSION['URLQueryEncryptionSecretKey'] = $key;
return $key;
}
/**
@ -32,11 +81,11 @@ final class Crypto
*
* @return string
*/
private static function encryptWithPhpseclib($plaintext)
private function encryptWithPhpseclib($plaintext)
{
$key = self::getEncryptionKey();
$key = $this->getEncryptionKey();
$cipher = new AES(AES::MODE_CBC);
$iv = Random::string(16);
$iv = $this->hasRandomBytesSupport ? random_bytes(16) : Random::string(16);
$cipher->setIV($iv);
$cipher->setKey($key);
$ciphertext = $cipher->encrypt($plaintext);
@ -50,9 +99,9 @@ final class Crypto
*
* @return string|null
*/
private static function decryptWithPhpseclib($encrypted)
private function decryptWithPhpseclib($encrypted)
{
$key = self::getEncryptionKey();
$key = $this->getEncryptionKey();
$hmac = mb_substr($encrypted, 0, 32, '8bit');
$iv = mb_substr($encrypted, 32, 16, '8bit');
$ciphertext = mb_substr($encrypted, 48, null, '8bit');
@ -69,12 +118,34 @@ final class Crypto
}
/**
* @param string $plaintext
*
* @return string
*/
private static function getEncryptionKey()
private function encryptWithSodium($plaintext)
{
global $PMA_Config;
$key = $this->getEncryptionKey();
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = sodium_crypto_secretbox($plaintext, $nonce, $key);
return $_SESSION[' HMAC_secret '] . $PMA_Config->get('blowfish_secret');
return $nonce . $ciphertext;
}
/**
* @param string $encrypted
*
* @return string|null
*/
private function decryptWithSodium($encrypted)
{
$key = $this->getEncryptionKey();
$nonce = mb_substr($encrypted, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
$ciphertext = mb_substr($encrypted, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
$decrypted = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
if ($decrypted === false) {
return null;
}
return $decrypted;
}
}

View File

@ -240,7 +240,9 @@ class Url
*/
public static function encryptQuery($query)
{
return strtr(base64_encode(Crypto::encrypt($query)), '+/', '-_');
$crypto = new Crypto();
return strtr(base64_encode($crypto->encrypt($query)), '+/', '-_');
}
/**
@ -249,7 +251,9 @@ class Url
*/
public static function decryptQuery($query)
{
return Crypto::decrypt(base64_decode(strtr($query, '-_', '+/')));
$crypto = new Crypto();
return $crypto->decrypt(base64_decode(strtr($query, '-_', '+/')));
}
/**

View File

@ -824,6 +824,13 @@ $cfg['IgnoreMultiSubmitErrors'] = false;
*/
$cfg['URLQueryEncryption'] = true;
/**
* A secret key used to encrypt/decrypt the URL query string. Should be 32 bytes long.
*
* @global string $cfg['URLQueryEncryptionSecretKey']
*/
$cfg['URLQueryEncryptionSecretKey'] = '';
/**
* allow login to any user entered server in cookie based authentication
*