public function getIdentity()
    {
        if ($this->identity) {
            return $this->identity;
        }

        if (!isset($_COOKIE[self::COOKIE_VERIFY_A], $_COOKIE[self::COOKIE_VERIFY_B], $_COOKIE[self::COOKIE_USER])) {
            return null;
        }

        $systemKey = new EncryptionKey($this->systemEncryptionKey);
        $verificationCookie = $_COOKIE[self::COOKIE_VERIFY_A];
        $hashPass = hash_equals(
            hash_hmac('sha256', $_COOKIE[self::COOKIE_USER], $systemKey),
            $verificationCookie
        );

        //
        // 1. Is the verify cookie still equivalent to the user cookie, if so, do not decrypt
        //
        if (!$hashPass) {
            return null;
        }

        //
        // 2. If the user cookie was not tampered with, decrypt its contents with the system key
        //
        try {

            $userTuple = Crypto::decrypt(base64_decode($_COOKIE[self::COOKIE_USER]), $systemKey);

            if (strpos($userTuple, ':') === false) {
                throw new \Exception();
            }

            // paranoid, make sure we have everything we need
            @list($cookieUserId, $hashCookieSuffix) = @explode(":", $userTuple, 2);
            if (!isset($cookieUserId, $hashCookieSuffix) || !is_numeric($cookieUserId) || !trim($hashCookieSuffix)) {
                throw new \Exception();
            }

            /** @var AuthenticationRecordInterface $auth */
            if (!($auth = $this->authenticationProvider->findByUserId($cookieUserId))) {
                throw new \Exception();
            }

            $hashCookieName = self::COOKIE_HASH_PREFIX . $hashCookieSuffix;

            //
            // 2. Check the hashCookie for corroborating data
            //
            if (!isset($_COOKIE[$hashCookieName])) {
                throw new \Exception();
            }

            $userKey = new EncryptionKey(new HiddenString($auth->getSessionKey()));
            $hashPass = hash_equals(
                hash_hmac('sha256', $_COOKIE[$hashCookieName], $userKey),
                $_COOKIE[self::COOKIE_VERIFY_B]
            );

            if (!$hashPass) {
                throw new \Exception();
            }

            //
            // 3. Decrypt the hash cookie with the user key
            //
            $hashedCookieContents = Crypto::decrypt(base64_decode($_COOKIE[$hashCookieName]), $userKey);
            if (!substr_count($hashedCookieContents, ':') === 2) {
                throw new \Exception();
            }

            list(, $hashedUserId, $hashedUsername) = explode(':', $hashedCookieContents);
            if ($hashedUserId !== $cookieUserId) {
                throw new \Exception();
            }

            if ($hashedUsername !== $auth->getUsername()) {
                throw new \Exception();
            }

            $this->purgeHashCookies($hashCookieName);

            //
            // 4. Cookies check out - it's up to the user provider now
            //
            $user = $this->userProvider->getUser($auth->getUserId());
            if ($user) {
                $this->setIdentity($user);

                return $this->identity;
            }

        } catch (\Exception $x) {
            $this->purgeHashCookies();
        }

        return null;
    }