| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 | <?phpnamespace Aws\Crypto\Polyfill;use Aws\Exception\CryptoPolyfillException;use InvalidArgumentException;use RangeException;/** * Class AesGcm * * This provides a polyfill for AES-GCM encryption/decryption, with caveats: * * 1. Only 96-bit nonces are supported. * 2. Only 128-bit authentication tags are supported. (i.e. non-truncated) * * Supports AES key sizes of 128-bit, 192-bit, and 256-bit. */class AesGcm{    use NeedsTrait;    /** @var Key $aesKey */    private $aesKey;    /** @var int $keySize */    private $keySize;    /** @var int $blockSize */    protected $blockSize = 8192;    /**     * AesGcm constructor.     *     * @param Key $aesKey     * @param int $keySize     * @param int $blockSize     *     * @throws CryptoPolyfillException     * @throws InvalidArgumentException     * @throws RangeException     */    public function __construct(Key $aesKey, $keySize = 256, $blockSize = 8192)    {        /* Preconditions: */        self::needs(            \in_array($keySize, [128, 192, 256], true),            "Key size must be 128, 192, or 256 bits; {$keySize} given",            InvalidArgumentException::class        );        self::needs(            \is_int($blockSize) && $blockSize > 0 && $blockSize <= PHP_INT_MAX,            'Block size must be a positive integer.',            RangeException::class        );        self::needs(            $aesKey->length() << 3 === $keySize,            'Incorrect key size; expected ' . $keySize . ' bits, got ' . ($aesKey->length() << 3) . ' bits.'        );        $this->aesKey = $aesKey;        $this->keySize = $keySize;    }    /**     * Encryption interface for AES-GCM     *     * @param string $plaintext  Message to be encrypted     * @param string $nonce      Number to be used ONCE     * @param Key $key           AES Key     * @param string $aad        Additional authenticated data     * @param string &$tag       Reference to variable to hold tag     * @param int $keySize       Key size (bits)     * @param int $blockSize     Block size (bytes) -- How much memory to buffer     * @return string     * @throws InvalidArgumentException     */    public static function encrypt(        $plaintext,        $nonce,        Key $key,        $aad,        &$tag,        $keySize = 256,        $blockSize = 8192    ) {        self::needs(            self::strlen($nonce) === 12,            'Nonce must be exactly 12 bytes',            InvalidArgumentException::class        );        $encryptor = new AesGcm($key, $keySize, $blockSize);        list($aadLength, $gmac) = $encryptor->gmacInit($nonce, $aad);        $ciphertext = \openssl_encrypt(            $plaintext,            "aes-{$encryptor->keySize}-ctr",            $key->get(),            OPENSSL_NO_PADDING | OPENSSL_RAW_DATA,            $nonce . "\x00\x00\x00\x02"        );        /* Calculate auth tag in a streaming fashion to minimize memory usage: */        $ciphertextLength = self::strlen($ciphertext);        for ($i = 0; $i < $ciphertextLength; $i += $encryptor->blockSize) {            $cBlock = new ByteArray(self::substr($ciphertext, $i, $encryptor->blockSize));            $gmac->update($cBlock);        }        $tag = $gmac->finish($aadLength, $ciphertextLength)->toString();        return $ciphertext;    }    /**     * Decryption interface for AES-GCM     *     * @param string $ciphertext Ciphertext to decrypt     * @param string $nonce      Number to be used ONCE     * @param Key $key           AES key     * @param string $aad        Additional authenticated data     * @param string $tag        Authentication tag     * @param int $keySize       Key size (bits)     * @param int $blockSize     Block size (bytes) -- How much memory to buffer     * @return string            Plaintext     *     * @throws CryptoPolyfillException     * @throws InvalidArgumentException     */    public static function decrypt(        $ciphertext,        $nonce,        Key $key,        $aad,        &$tag,        $keySize = 256,        $blockSize = 8192    ) {        /* Precondition: */        self::needs(            self::strlen($nonce) === 12,            'Nonce must be exactly 12 bytes',            InvalidArgumentException::class        );        $encryptor = new AesGcm($key, $keySize, $blockSize);        list($aadLength, $gmac) = $encryptor->gmacInit($nonce, $aad);        /* Calculate auth tag in a streaming fashion to minimize memory usage: */        $ciphertextLength = self::strlen($ciphertext);        for ($i = 0; $i < $ciphertextLength; $i += $encryptor->blockSize) {            $cBlock = new ByteArray(self::substr($ciphertext, $i, $encryptor->blockSize));            $gmac->update($cBlock);        }        /* Validate auth tag in constant-time: */        $calc = $gmac->finish($aadLength, $ciphertextLength);        $expected = new ByteArray($tag);        self::needs($calc->equals($expected), 'Invalid authentication tag');        /* Return plaintext if auth tag check succeeded: */        return \openssl_decrypt(            $ciphertext,            "aes-{$encryptor->keySize}-ctr",            $key->get(),            OPENSSL_NO_PADDING | OPENSSL_RAW_DATA,            $nonce . "\x00\x00\x00\x02"        );    }    /**     * Initialize a Gmac object with the nonce and this object's key.     *     * @param string $nonce     Must be exactly 12 bytes long.     * @param string|null $aad     * @return array     */    protected function gmacInit($nonce, $aad = null)    {        $gmac = new Gmac(            $this->aesKey,            $nonce . "\x00\x00\x00\x01",            $this->keySize        );        $aadBlock = new ByteArray($aad);        $aadLength = $aadBlock->count();        $gmac->update($aadBlock);        $gmac->flush();        return [$aadLength, $gmac];    }    /**     * Calculate the length of a string.     *     * Uses the appropriate PHP function without being brittle to     * mbstring.func_overload.     *     * @param string $string     * @return int     */    protected static function strlen($string)    {        if (\is_callable('\\mb_strlen')) {            return (int) \mb_strlen($string, '8bit');        }        return (int) \strlen($string);    }    /**     * Return a substring of the provided string.     *     * Uses the appropriate PHP function without being brittle to     * mbstring.func_overload.     *     * @param string $string     * @param int $offset     * @param int|null $length     * @return string     */    protected static function substr($string, $offset = 0, $length = null)    {        if (\is_callable('\\mb_substr')) {            return \mb_substr($string, $offset, $length, '8bit');        } elseif (!\is_null($length)) {            return \substr($string, $offset, $length);        }        return \substr($string, $offset);    }}
 |