| Current Path : /srv/web/sites/trentinoplant.it/httpdocs/vendor1/laminas/laminas-crypt/src/ |
| Current File : /srv/web/sites/trentinoplant.it/httpdocs/vendor1/laminas/laminas-crypt/src/BlockCipher.php |
<?php
namespace Laminas\Crypt;
use Laminas\Crypt\Key\Derivation\Pbkdf2;
use Laminas\Crypt\Symmetric\SymmetricInterface;
use Laminas\Math\Rand;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface as NotFoundException;
use function base64_decode;
use function base64_encode;
use function class_exists;
use function get_class;
use function gettype;
use function in_array;
use function is_array;
use function is_object;
use function is_string;
use function is_subclass_of;
use function mb_substr;
use function sprintf;
/**
* Encrypt using a symmetric cipher then authenticate using HMAC (SHA-256)
*/
class BlockCipher
{
/**
* Hash algorithm for Pbkdf2
*
* @var string
*/
protected $pbkdf2Hash = 'sha256';
/**
* Symmetric cipher
*
* @var SymmetricInterface
*/
protected $cipher;
/**
* Symmetric cipher plugin manager
*
* @var SymmetricPluginManager
*/
protected static $symmetricPlugins;
/**
* Hash algorithm for HMAC
*
* @var string
*/
protected $hash = 'sha256';
/**
* Check if the salt has been set
*
* @var bool
*/
protected $saltSetted = false;
/**
* The output is binary?
*
* @var bool
*/
protected $binaryOutput = false;
/**
* Number of iterations for Pbkdf2
*
* @var string
*/
protected $keyIteration = 5000;
/**
* Key
*
* @var string
*/
protected $key;
/**
* Constructor
*/
public function __construct(SymmetricInterface $cipher)
{
$this->cipher = $cipher;
}
/**
* Factory
*
* @param string $adapter
* @param array $options
* @return BlockCipher
*/
public static function factory($adapter, $options = [])
{
$plugins = static::getSymmetricPluginManager();
try {
$cipher = $plugins->get($adapter);
} catch (NotFoundException $e) {
throw new Exception\RuntimeException(sprintf(
'The symmetric adapter %s does not exist',
$adapter
));
}
$cipher->setOptions($options);
return new static($cipher);
}
/**
* Returns the symmetric cipher plugin manager. If it doesn't exist it's created.
*
* @return ContainerInterface
*/
public static function getSymmetricPluginManager()
{
if (static::$symmetricPlugins === null) {
static::setSymmetricPluginManager(new SymmetricPluginManager());
}
return static::$symmetricPlugins;
}
/**
* Set the symmetric cipher plugin manager
*
* @param string|SymmetricPluginManager $plugins
* @throws Exception\InvalidArgumentException
*/
public static function setSymmetricPluginManager($plugins)
{
if (is_string($plugins)) {
if (! class_exists($plugins) || ! is_subclass_of($plugins, ContainerInterface::class)) {
throw new Exception\InvalidArgumentException(sprintf(
'Unable to locate symmetric cipher plugins using class "%s"; '
. 'class does not exist or does not implement ContainerInterface',
$plugins
));
}
$plugins = new $plugins();
}
if (! $plugins instanceof ContainerInterface) {
throw new Exception\InvalidArgumentException(sprintf(
'Symmetric plugin must implements Interop\Container\ContainerInterface;; received "%s"',
is_object($plugins) ? get_class($plugins) : gettype($plugins)
));
}
static::$symmetricPlugins = $plugins;
}
/**
* Set the symmetric cipher
*
* @return BlockCipher Provides a fluent interface
*/
public function setCipher(SymmetricInterface $cipher)
{
$this->cipher = $cipher;
return $this;
}
/**
* Get symmetric cipher
*
* @return SymmetricInterface
*/
public function getCipher()
{
return $this->cipher;
}
/**
* Set the number of iterations for Pbkdf2
*
* @param int $num
* @return BlockCipher Provides a fluent interface
*/
public function setKeyIteration($num)
{
$this->keyIteration = (int) $num;
return $this;
}
/**
* Get the number of iterations for Pbkdf2
*
* @return int
*/
public function getKeyIteration()
{
return $this->keyIteration;
}
/**
* Set the salt (IV)
*
* @param string $salt
* @return BlockCipher Provides a fluent interface
* @throws Exception\InvalidArgumentException
*/
public function setSalt($salt)
{
try {
$this->cipher->setSalt($salt);
} catch (Symmetric\Exception\InvalidArgumentException $e) {
throw new Exception\InvalidArgumentException("The salt is not valid: " . $e->getMessage());
}
$this->saltSetted = true;
return $this;
}
/**
* Get the salt (IV) according to the size requested by the algorithm
*
* @return string
*/
public function getSalt()
{
return $this->cipher->getSalt();
}
/**
* Get the original salt value
*
* @return string
*/
public function getOriginalSalt()
{
return $this->cipher->getOriginalSalt();
}
/**
* Enable/disable the binary output
*
* @param bool $value
* @return BlockCipher Provides a fluent interface
*/
public function setBinaryOutput($value)
{
$this->binaryOutput = (bool) $value;
return $this;
}
/**
* Get the value of binary output
*
* @return bool
*/
public function getBinaryOutput()
{
return $this->binaryOutput;
}
/**
* Set the encryption/decryption key
*
* @param string $key
* @return BlockCipher Provides a fluent interface
* @throws Exception\InvalidArgumentException
*/
public function setKey($key)
{
if (empty($key)) {
throw new Exception\InvalidArgumentException('The key cannot be empty');
}
$this->key = $key;
return $this;
}
/**
* Get the key
*
* @return string
*/
public function getKey()
{
return $this->key;
}
/**
* Set algorithm of the symmetric cipher
*
* @param string $algo
* @return BlockCipher Provides a fluent interface
* @throws Exception\InvalidArgumentException
*/
public function setCipherAlgorithm($algo)
{
try {
$this->cipher->setAlgorithm($algo);
} catch (Symmetric\Exception\InvalidArgumentException $e) {
throw new Exception\InvalidArgumentException($e->getMessage());
}
return $this;
}
/**
* Get the cipher algorithm
*
* @return string|bool
*/
public function getCipherAlgorithm()
{
return $this->cipher->getAlgorithm();
}
/**
* Get the supported algorithms of the symmetric cipher
*
* @return array
*/
public function getCipherSupportedAlgorithms()
{
return $this->cipher->getSupportedAlgorithms();
}
/**
* Set the hash algorithm for HMAC authentication
*
* @param string $hash
* @return BlockCipher Provides a fluent interface
* @throws Exception\InvalidArgumentException
*/
public function setHashAlgorithm($hash)
{
if (! Hash::isSupported($hash)) {
throw new Exception\InvalidArgumentException(
"The specified hash algorithm '{$hash}' is not supported by Laminas\Crypt\Hash"
);
}
$this->hash = $hash;
return $this;
}
/**
* Get the hash algorithm for HMAC authentication
*
* @return string
*/
public function getHashAlgorithm()
{
return $this->hash;
}
/**
* Set the hash algorithm for the Pbkdf2
*
* @param string $hash
* @return BlockCipher Provides a fluent interface
* @throws Exception\InvalidArgumentException
*/
public function setPbkdf2HashAlgorithm($hash)
{
if (! Hash::isSupported($hash)) {
throw new Exception\InvalidArgumentException(
"The specified hash algorithm '{$hash}' is not supported by Laminas\Crypt\Hash"
);
}
$this->pbkdf2Hash = $hash;
return $this;
}
/**
* Get the Pbkdf2 hash algorithm
*
* @return string
*/
public function getPbkdf2HashAlgorithm()
{
return $this->pbkdf2Hash;
}
/**
* Encrypt then authenticate using HMAC
*
* @param string $data
* @return string
* @throws Exception\InvalidArgumentException
*/
public function encrypt($data)
{
// 0 (as integer), 0.0 (as float) & '0' (as string) will return false, though these should be allowed
// Must be a string, integer, or float in order to encrypt
if (
(is_string($data) && $data === '')
|| is_array($data)
|| is_object($data)
) {
throw new Exception\InvalidArgumentException('The data to encrypt cannot be empty');
}
// Cast to string prior to encrypting
if (! is_string($data)) {
$data = (string) $data;
}
if (empty($this->key)) {
throw new Exception\InvalidArgumentException('No key specified for the encryption');
}
$keySize = $this->cipher->getKeySize();
// generate a random salt (IV) if the salt has not been set
if (! $this->saltSetted) {
$this->cipher->setSalt(Rand::getBytes($this->cipher->getSaltSize()));
}
if (in_array($this->cipher->getMode(), ['ccm', 'gcm'], true)) {
return $this->encryptViaCcmOrGcm($data, $keySize);
}
// generate the encryption key and the HMAC key for the authentication
$hash = Pbkdf2::calc(
$this->getPbkdf2HashAlgorithm(),
$this->getKey(),
$this->getSalt(),
$this->keyIteration,
$keySize * 2
);
// set the encryption key
$this->cipher->setKey(mb_substr($hash, 0, $keySize, '8bit'));
// set the key for HMAC
$keyHmac = mb_substr($hash, $keySize, null, '8bit');
// encryption
$ciphertext = $this->cipher->encrypt($data);
// HMAC
$hmac = Hmac::compute($keyHmac, $this->hash, $this->cipher->getAlgorithm() . $ciphertext);
return $this->binaryOutput ? $hmac . $ciphertext : $hmac . base64_encode($ciphertext);
}
/**
* Decrypt
*
* @param string $data
* @return string|bool
* @throws Exception\InvalidArgumentException
*/
public function decrypt($data)
{
if (! is_string($data)) {
throw new Exception\InvalidArgumentException('The data to decrypt must be a string');
}
if ('' === $data) {
throw new Exception\InvalidArgumentException('The data to decrypt cannot be empty');
}
if (empty($this->key)) {
throw new Exception\InvalidArgumentException('No key specified for the decryption');
}
$keySize = $this->cipher->getKeySize();
if (in_array($this->cipher->getMode(), ['ccm', 'gcm'], true)) {
return $this->decryptViaCcmOrGcm($data, $keySize);
}
$hmacSize = Hmac::getOutputSize($this->hash);
$hmac = mb_substr($data, 0, $hmacSize, '8bit');
$ciphertext = mb_substr($data, $hmacSize, null, '8bit') ?: '';
if (! $this->binaryOutput) {
$ciphertext = base64_decode($ciphertext);
}
$iv = mb_substr($ciphertext, 0, $this->cipher->getSaltSize(), '8bit');
// generate the encryption key and the HMAC key for the authentication
$hash = Pbkdf2::calc(
$this->getPbkdf2HashAlgorithm(),
$this->getKey(),
$iv,
$this->keyIteration,
$keySize * 2
);
// set the decryption key
$this->cipher->setKey(mb_substr($hash, 0, $keySize, '8bit'));
// set the key for HMAC
$keyHmac = mb_substr($hash, $keySize, null, '8bit');
$hmacNew = Hmac::compute($keyHmac, $this->hash, $this->cipher->getAlgorithm() . $ciphertext);
if (! Utils::compareStrings($hmacNew, $hmac)) {
return false;
}
return $this->cipher->decrypt($ciphertext);
}
/**
* Note: CCM and GCM modes do not need HMAC
*
* @param string $data
* @param int $keySize
* @return string
* @throws Exception\InvalidArgumentException
*/
private function encryptViaCcmOrGcm($data, $keySize)
{
$this->cipher->setKey(Pbkdf2::calc(
$this->getPbkdf2HashAlgorithm(),
$this->getKey(),
$this->getSalt(),
$this->keyIteration,
$keySize
));
$cipherText = $this->cipher->encrypt($data);
return $this->binaryOutput ? $cipherText : base64_encode($cipherText);
}
/**
* Note: CCM and GCM modes do not need HMAC
*
* @param string $data
* @param int $keySize
* @return string
* @throws Exception\InvalidArgumentException
*/
private function decryptViaCcmOrGcm($data, $keySize)
{
$cipherText = $this->binaryOutput ? $data : base64_decode($data);
$iv = mb_substr($cipherText, $this->cipher->getTagSize(), $this->cipher->getSaltSize(), '8bit');
$this->cipher->setKey(Pbkdf2::calc(
$this->getPbkdf2HashAlgorithm(),
$this->getKey(),
$iv,
$this->keyIteration,
$keySize
));
return $this->cipher->decrypt($cipherText);
}
}