| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 | <?phpnamespace 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;    }}
 |