encrypt($data); // Accepts a string * * * // Encrypt and encode to Base64 * $encryptedData = (new AesCrypt())->encryptToBase64($data); // Accepts a string * * * // Decryption * $aesCrypt = (new AesCrypt()) * ->setTag($tag) // if exists * ->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 array The list of cipher methods */ const METHODS = [ 'aes-256-gcm', 'aes-256-cbc', 'aes-256-ctr' ]; /** @var string The encryption key */ private $key; /** @var int The length of the key */ private $keyLength; /** @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; public function __construct($keyLength = 128) { $this->keyLength = $keyLength; } /** * Set the method * * @return $this */ public function setMethod($method) { $this->method = $method; return $this; } /** * Get the method * * @return string */ public function getMethod() { if ($this->method === null) { $this->method = $this->getSupportedMethod(); } return $this->method; } /** * Get supported method * * @return string * * @throws RuntimeException If none of the methods listed in the METHODS array is available */ protected function getSupportedMethod() { $availableMethods = openssl_get_cipher_methods(); $methods = self::METHODS; if (! $this->isAuthenticatedEncryptionSupported()) { unset($methods[0]); } foreach ($methods as $method) { if (in_array($method, $availableMethods)) { return $method; } } throw new RuntimeException('No supported method found'); } /** * Set the key * * @return $this */ public function setKey($key) { $this->key = $key; return $this; } /** * Get the key * * @return string * */ public function getKey() { if (empty($this->key)) { $this->key = random_bytes($this->keyLength); } return $this->key; } /** * Set the IV * * @return $this */ public function setIV($iv) { $this->iv = $iv; return $this; } /** * Get the IV * * @return string * */ public function getIV() { if (empty($this->iv)) { $len = openssl_cipher_iv_length($this->getMethod()); $this->iv = random_bytes($len); } return $this->iv; } /** * Set the Tag * * @return $this * * @throws RuntimeException If a tag is available but authenticated encryption (AE) is not supported. * * @throws UnexpectedValueException If tag length is less then 16 */ public function setTag($tag) { if (! $this->isAuthenticatedEncryptionSupported()) { throw new RuntimeException(sprintf( "The given decryption method is not supported in php version '%s'", PHP_VERSION )); } 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 string * * @param string $data * * @return string * * @throws RuntimeException If decryption fails */ public function decrypt($data) { if (! $this->isAuthenticatedEncryptionRequired()) { return $this->nonAEDecrypt($data); } $decrypt = openssl_decrypt($data, $this->getMethod(), $this->getKey(), 0, $this->getIV(), $this->getTag()); if ($decrypt === false) { throw new RuntimeException('Decryption failed'); } return $decrypt; } /** * Decode from Base64 and decrypt the given string * * @param string $data * * @return string The base64 decoded and decrypted string * * @deprecated Use decrypt() instead as it also returns a base64 decoded string * * @throws RuntimeException If decryption fails */ public function decryptFromBase64($data) { return $this->decrypt(base64_decode($data)); } /** * Encrypt the given string * * @param string $data * * @return string encrypted string * * @throws RuntimeException If decryption fails */ public function encrypt($data) { if (! $this->isAuthenticatedEncryptionRequired()) { return $this->nonAEEncrypt($data); } $encrypt = openssl_encrypt($data, $this->getMethod(), $this->getKey(), 0, $this->getIV(), $this->tag); if ($encrypt === false) { throw new RuntimeException('Encryption failed'); } return $encrypt; } /** * Encrypt the given string and encode to Base64 * * @param string $data * * @return string encrypted and base64 encoded string * * @deprecated Use encrypt() instead as it also returns a base64 encoded string * * @throws RuntimeException If encryption fails */ public function encryptToBase64($data) { return base64_encode($this->encrypt($data)); } /** * Decrypt the given string with non Authenticated encryption (AE) cipher method * * @param string $data * * @return string decrypted string * * @throws RuntimeException If decryption fails */ private function nonAEDecrypt($data) { $c = base64_decode($data); $hmac = substr($c, 0, 32); $data = substr($c, 32); $decrypt = openssl_decrypt($data, $this->getMethod(), $this->getKey(), 0, $this->getIV()); $calcHmac = hash_hmac('sha256', $this->getIV() . $data, $this->getKey(), true); if ($decrypt === false || ! hash_equals($hmac, $calcHmac)) { throw new RuntimeException('Decryption failed'); } return $decrypt; } /** * Encrypt the given string with non Authenticated encryption (AE) cipher method * * @param string $data * * @return string encrypted string * * @throws RuntimeException If encryption fails */ private function nonAEEncrypt($data) { $encrypt = openssl_encrypt($data, $this->getMethod(), $this->getKey(), 0, $this->getIV()); if ($encrypt === false) { throw new RuntimeException('Encryption failed'); } $hmac = hash_hmac('sha256', $this->getIV() . $encrypt, $this->getKey(), true); return base64_encode($hmac . $encrypt); } /** * Whether the Authenticated encryption (a tag) is required * * @return bool True if required false otherwise */ public function isAuthenticatedEncryptionRequired() { return $this->getMethod() === 'aes-256-gcm'; } /** * Whether the php version supports Authenticated encryption (AE) or not * * @return bool True if supported false otherwise */ public function isAuthenticatedEncryptionSupported() { return PHP_VERSION_ID >= 70100; } }