Files
agtuser 16a3fd1e1b init
2024-11-11 17:56:00 +08:00

162 lines
4.3 KiB
PHP
Executable File

<?php
namespace PhpMyAdmin\Crypto;
use Exception;
use phpseclib\Crypt\AES;
use phpseclib\Crypt\Random;
final class Crypto
{
/** @var bool */
private $hasRandomBytesSupport;
/** @var bool */
private $hasSodiumSupport;
/**
* @param bool $forceFallback Force the usage of the fallback functions.
*/
public function __construct($forceFallback = false)
{
$this->hasRandomBytesSupport = ! $forceFallback && is_callable('random_bytes');
$this->hasSodiumSupport = ! $forceFallback
&& $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 function encrypt($plaintext)
{
if ($this->hasSodiumSupport) {
return $this->encryptWithSodium($plaintext);
}
return $this->encryptWithPhpseclib($plaintext);
}
/**
* @param string $ciphertext
*
* @return string
*/
public function decrypt($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;
}
/**
* @param string $plaintext
*
* @return string
*/
private function encryptWithPhpseclib($plaintext)
{
$key = $this->getEncryptionKey();
$cipher = new AES(AES::MODE_CBC);
$iv = $this->hasRandomBytesSupport ? random_bytes(16) : Random::string(16);
$cipher->setIV($iv);
$cipher->setKey($key);
$ciphertext = $cipher->encrypt($plaintext);
$hmac = hash_hmac('sha256', $iv . $ciphertext, $key, true);
return $hmac . $iv . $ciphertext;
}
/**
* @param string $encrypted
*
* @return string|null
*/
private function decryptWithPhpseclib($encrypted)
{
$key = $this->getEncryptionKey();
$hmac = mb_substr($encrypted, 0, 32, '8bit');
$iv = mb_substr($encrypted, 32, 16, '8bit');
$ciphertext = mb_substr($encrypted, 48, null, '8bit');
$calculatedHmac = hash_hmac('sha256', $iv . $ciphertext, $key, true);
if (! hash_equals($hmac, $calculatedHmac)) {
return null;
}
$cipher = new AES(AES::MODE_CBC);
$cipher->setIV($iv);
$cipher->setKey($key);
return $cipher->decrypt($ciphertext);
}
/**
* @param string $plaintext
*
* @return string
*/
private function encryptWithSodium($plaintext)
{
$key = $this->getEncryptionKey();
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = sodium_crypto_secretbox($plaintext, $nonce, $key);
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');
try {
$decrypted = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
} catch (Exception $e) {
return null;
}
if ($decrypted === false) {
return null;
}
return $decrypted;
}
}