Last active
May 30, 2025 23:22
-
-
Save smuuf/987c28a89f06db85d42c4551a46193b5 to your computer and use it in GitHub Desktop.
AES-256-GCM encryption/decryption class in PHP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
class AesGcmCrypto { | |
private const CIPHER = 'aes-256-gcm'; | |
/** 96-bit IV is standard for GCM. */ | |
private const IV_LENGTH = 12; | |
/** Tag with 128-bits. */ | |
private const TAG_LENGTH = 16; | |
/** | |
* @param string $key 32-byte (256-bit) secret key. | |
*/ | |
public function __construct( | |
private string $key, | |
) { | |
if (strlen($this->key) !== 32) { | |
throw new \InvalidArgumentException( | |
'Key must be 32 bytes (256 bits)', | |
); | |
} | |
} | |
/** | |
* Encrypts plaintext with optional AAD. | |
* | |
* @param ?string $aad Optional additional authenticated data. | |
* @return string Base64-encoded string containing IV + ciphertext + tag. | |
*/ | |
public function encrypt(string $plaintext, string $aad = ''): string { | |
$iv = random_bytes(self::IV_LENGTH); | |
$tag = ''; | |
$ciphertext = openssl_encrypt( | |
$plaintext, | |
self::CIPHER, | |
$this->key, | |
OPENSSL_RAW_DATA, | |
$iv, | |
$tag, | |
$aad, | |
self::TAG_LENGTH, | |
); | |
if ($ciphertext === false) { | |
throw new \RuntimeException('Encryption failed'); | |
} | |
// Combine IV + ciphertext + tag for transmission. | |
return base64_encode($iv . $ciphertext . $tag); | |
} | |
/** | |
* Decrypts base64-encoded encrypted message with optional AAD. | |
* | |
* @param string $encoded Base64-encoded IV + ciphertext + tag. | |
* @param ?string $aad Optional additional authenticated data. | |
* @return string Decrypted plaintext. | |
*/ | |
public function decrypt(string $encoded, string $aad = ''): string { | |
$data = base64_decode($encoded, true); | |
if ( | |
$data === false | |
|| strlen($data) < (self::IV_LENGTH + self::TAG_LENGTH) | |
) { | |
throw new \InvalidArgumentException('Invalid encrypted input'); | |
} | |
$iv = substr($data, 0, self::IV_LENGTH); | |
$tag = substr($data, -self::TAG_LENGTH); | |
$ciphertext = substr($data, self::IV_LENGTH, -self::TAG_LENGTH); | |
$plaintext = openssl_decrypt( | |
$ciphertext, | |
self::CIPHER, | |
$this->key, | |
OPENSSL_RAW_DATA, | |
$iv, | |
$tag, | |
$aad, | |
); | |
if ($plaintext === false) { | |
throw new \RuntimeException( | |
'Decryption failed or authentication failed', | |
); | |
} | |
return $plaintext; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment