icingaweb2/library/Icinga/Crypt/AesCrypt.php

280 lines
6.0 KiB
PHP
Raw Normal View History

<?php
/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
namespace Icinga\Crypt;
use UnexpectedValueException;
use RuntimeException;
/**
* Data encryption and decryption using symmetric algorithm
*
* # Example Usage
*
* ```php
*
* // Encryption
* $encryptedData = new AesCrypt()->encrypt($data); // Accepts a string
*
*
* // Encrypt and encode to Base64
* $encryptedData = (new AesCrypt())->encryptToBase64($data); // Accepts a string
*
*
* // Decryption
* $aesCrypt = (new AesCrypt())
* ->setTag($tag)
* ->setIV($iv)
* ->setKey($key);
*
* $decryptedData = $aesCrypt->decrypt($data);
*
* // Decode from Base64 and decrypt
* $aesCrypt = (new AesCrypt())
* ->setTag($tag)
* ->setIV($iv)
* ->setKey($key);
*
* $decryptedData = $aesCrypt->->decryptFromBase64($data);
* ```
*
*/
class AesCrypt
{
/** @var string The encryption key */
private $key;
/** @var string The initialization vector which is not NULL */
private $iv;
/** @var string The authentication tag which is passed by reference when using AEAD cipher mode */
private $tag;
/** @var string The cipher method */
private $method = 'AES-128-GCM';
const GCM_SUPPORT_VERSION = '7.1';
public function __construct($random_bytes_len = 128)
{
if (version_compare(PHP_VERSION, self::GCM_SUPPORT_VERSION, '<')) {
$this->method = 'AES-128-CBC';
}
$this->key = random_bytes($random_bytes_len);
}
/**
* Force set the method
*
* @return $this
*/
public function setMethod($method)
{
$this->method = $method;
return $this;
}
/**
* Set the key
*
* @return $this
*/
public function setKey($key)
{
$this->key = $key;
return $this;
}
/**
* Get the key
*
* @return string
*
* @throws RuntimeException If the key is not set
*/
public function getKey()
{
if (empty($this->key)) {
throw new RuntimeException('No key set');
}
return $this->key;
}
/**
* Set the IV
*
* @return $this
*/
public function setIV($iv)
{
$this->iv = $iv;
return $this;
}
/**
* Get the IV
*
* @return string
*
* @throws RuntimeException If the IV is not set
*/
public function getIV()
{
if (empty($this->iv)) {
$len = openssl_cipher_iv_length($this->method);
$this->iv = random_bytes($len);
}
return $this->iv;
}
/**
* Set the Tag
*
* @return $this
*/
public function setTag($tag)
{
if (strlen($tag) !== 16) {
throw new UnexpectedValueException(sprintf(
'expects tag length to be 16, got instead %s',
strlen($tag)
));
}
$this->tag = $tag;
return $this;
}
/**
* Get the Tag
*
* @return string
*
* @throws RuntimeException If the Tag is not set
*/
public function getTag()
{
if (empty($this->tag)) {
throw new RuntimeException('No tag set');
}
return $this->tag;
}
/**
* Decrypt the given data using the key, iv and tag
*
* @param string $data
*
* @return string
*
* @throws RuntimeException If decryption fails
*/
public function decrypt($data)
{
if ($this->method === 'AES-128-CBC') {
return $this->decryptCBC($data);
}
$decrypt = openssl_decrypt($data, $this->method, $this->getKey(), 0, $this->getIV(), $this->getTag());
if ($decrypt === false) {
throw new RuntimeException('Decryption failed');
}
return $decrypt;
}
/**
* Decode from Base64 and decrypt the given data using the key, iv and tag
*
* @param string $data
*
* @return string decrypted data
*
* @throws RuntimeException If decryption fails
*/
public function decryptFromBase64($data)
{
return $this->decrypt(base64_decode($data));
}
/**
* Encrypt the given data using the key, iv and tag
*
* @param string $data
*
* @return string encrypted data
*
* @throws RuntimeException If decryption fails
*/
public function encrypt($data)
{
if ($this->method === 'AES-128-CBC') {
return $this->encryptCBC($data);
}
$encrypt = openssl_encrypt($data, $this->method, $this->getkey(), 0, $this->getIV(), $this->tag);
if ($encrypt === false) {
throw new RuntimeException('Encryption failed');
}
return $encrypt;
}
/**
* Encrypt the given string using the the key, iv, tag and encode to Base64
*
* @param string $data
*
* @return string encrypted and encoded to Base64 data
*
* @throws RuntimeException If encryption fails
*/
public function encryptToBase64($data)
{
return base64_encode($this->encrypt($data));
}
private function decryptCBC($data)
{
if (strlen($this->getIV()) !== 16) {
throw new RuntimeException('Decryption failed');
}
$c = base64_decode($data);
$hmac = substr($c, 0, 32);
$data = substr($c, 32);
$decrypt = openssl_decrypt($data, $this->method, $this->getKey(), 0, $this->getIV());
$calcHmac = hash_hmac('sha256', $data, $this->getKey(), true);
if ($decrypt === false || ! hash_equals($hmac, $calcHmac)) {
throw new RuntimeException('Decryption failed');
}
return $decrypt;
}
private function encryptCBC($data)
{
$encrypt = openssl_encrypt($data, $this->method, $this->getkey(), 0, $this->getIV());
if ($encrypt === false) {
throw new RuntimeException('Encryption failed');
}
$hmac = hash_hmac('sha256', $encrypt, $this->getkey(), true);
return base64_encode($hmac . $encrypt);
}
}