123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- <?php
- namespace Aws\Crypto\Polyfill;
- /**
- * Class Gmac
- */
- class Gmac
- {
- use NeedsTrait;
- const BLOCK_SIZE = 16;
- /** @var ByteArray $buf */
- protected $buf;
- /** @var int $bufLength */
- protected $bufLength = 0;
- /** @var ByteArray $h */
- protected $h;
- /** @var ByteArray $hf */
- protected $hf;
- /** @var Key $key */
- protected $key;
- /** @var ByteArray $x */
- protected $x;
- /**
- * Gmac constructor.
- *
- * @param Key $aesKey
- * @param string $nonce
- * @param int $keySize
- */
- public function __construct(Key $aesKey, $nonce, $keySize = 256)
- {
- $this->buf = new ByteArray(16);
- $this->h = new ByteArray(
- \openssl_encrypt(
- \str_repeat("\0", 16),
- "aes-{$keySize}-ecb",
- $aesKey->get(),
- OPENSSL_RAW_DATA | OPENSSL_NO_PADDING
- )
- );
- $this->key = $aesKey;
- $this->x = new ByteArray(16);
- $this->hf = new ByteArray(
- \openssl_encrypt(
- $nonce,
- "aes-{$keySize}-ecb",
- $aesKey->get(),
- OPENSSL_RAW_DATA | OPENSSL_NO_PADDING
- )
- );
- }
- /**
- * Update the object with some data.
- *
- * This method mutates this Gmac object.
- *
- * @param ByteArray $blocks
- * @return self
- */
- public function update(ByteArray $blocks)
- {
- if (($blocks->count() + $this->bufLength) < self::BLOCK_SIZE) {
- // Write to internal buffer until we reach enough to write.
- $this->buf->set($blocks, $this->bufLength);
- $this->bufLength += $blocks->count();
- return $this;
- }
- // Process internal buffer first.
- if ($this->bufLength > 0) {
- // 0 <= state.buf_len < BLOCK_SIZE is an invariant
- $tmp = new ByteArray(self::BLOCK_SIZE);
- $tmp->set($this->buf->slice(0, $this->bufLength));
- $remainingBlockLength = self::BLOCK_SIZE - $this->bufLength;
- $tmp->set($blocks->slice(0, $remainingBlockLength), $this->bufLength);
- $blocks = $blocks->slice($remainingBlockLength);
- $this->bufLength = 0;
- $this->x = $this->blockMultiply($this->x->exclusiveOr($tmp), $this->h);
- }
- // Process full blocks.
- $numBlocks = $blocks->count() >> 4;
- for ($i = 0; $i < $numBlocks; ++$i) {
- $tmp = $blocks->slice($i << 4, self::BLOCK_SIZE);
- $this->x = $this->blockMultiply($this->x->exclusiveOr($tmp), $this->h);
- }
- $last = $numBlocks << 4;
- // Zero-fill buffer
- for ($i = 0; $i < 16; ++$i) {
- $this->buf[$i] = 0;
- }
- // Feed leftover into buffer.
- if ($last < $blocks->count()) {
- $tmp = $blocks->slice($last);
- $this->buf->set($tmp);
- $this->bufLength += ($blocks->count() - $last);
- }
- return $this;
- }
- /**
- * Finish processing the authentication tag.
- *
- * This method mutates this Gmac object (effectively resetting it).
- *
- * @param int $aadLength
- * @param int $ciphertextLength
- * @return ByteArray
- */
- public function finish($aadLength, $ciphertextLength)
- {
- $lengthBlock = new ByteArray(16);
- $state = $this->flush();
- // AES-GCM expects bit lengths, not byte lengths.
- $lengthBlock->set(ByteArray::enc32be($aadLength >> 29), 0);
- $lengthBlock->set(ByteArray::enc32be($aadLength << 3), 4);
- $lengthBlock->set(ByteArray::enc32be($ciphertextLength >> 29), 8);
- $lengthBlock->set(ByteArray::enc32be($ciphertextLength << 3), 12);
- $state->update($lengthBlock);
- $output = $state->x->exclusiveOr($state->hf);
- // Zeroize the internal values as a best-effort.
- $state->buf->zeroize();
- $state->x->zeroize();
- $state->h->zeroize();
- $state->hf->zeroize();
- return $output;
- }
- /**
- * Get a specific bit from the provided array, at the given index.
- *
- * [01234567], 8+[01234567], 16+[01234567], ...
- *
- * @param ByteArray $x
- * @param int $i
- * @return int
- */
- protected function bit(ByteArray $x, $i)
- {
- $byte = $i >> 3;
- return ($x[$byte] >> ((7 - $i) & 7)) & 1;
- }
- /**
- * Galois Field Multiplication
- *
- * This function is the critical path that must be constant-time in order to
- * avoid timing side-channels against AES-GCM.
- *
- * The contents of each are always calculated, regardless of the branching
- * condition, to prevent another kind of timing leak.
- *
- * @param ByteArray $x
- * @param ByteArray $y
- * @return ByteArray
- */
- protected function blockMultiply(ByteArray $x, ByteArray $y)
- {
- static $fieldPolynomial = null;
- if (!$fieldPolynomial) {
- $fieldPolynomial = new ByteArray([
- 0xe1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- ]);
- }
- self::needs($x->count() === 16, 'Argument 1 must be a ByteArray of exactly 16 bytes');
- self::needs($y->count() === 16, 'Argument 2 must be a ByteArray of exactly 16 bytes');
- $v = clone $y;
- $z = new ByteArray(16);
- for ($i = 0; $i < 128; ++$i) {
- // if ($b) $z = $z->exclusiveOr($v);
- $b = $this->bit($x, $i);
- $z = ByteArray::select(
- $b,
- $z->exclusiveOr($v),
- $z
- );
- // if ($b) $v = $v->exclusiveOr($fieldPolynomial);
- $b = $v[15] & 1;
- $v = $v->rshift();
- $v = ByteArray::select(
- $b,
- $v->exclusiveOr($fieldPolynomial),
- $v
- );
- }
- return $z;
- }
- /**
- * Finish processing any leftover bytes in the internal buffer.
- *
- * @return self
- */
- public function flush()
- {
- if ($this->bufLength !== 0) {
- $this->x = $this->blockMultiply(
- $this->x->exclusiveOr($this->buf),
- $this->h
- );
- $this->bufLength = 0;
- }
- return $this;
- }
- }
|