secretKey = $secretKey; $this->identity = $identity; $this->tolerance = $tolerance; } private static function time(int $time = -1): int { return ($time < 0 ? time() : $time) - self::EPOCH; } private function createHash(int $time, string $nonce): string { return hash_hmac(self::HASH_ALGO, "{$this->identity}!{$time}!{$nonce}", $this->secretKey, true); } /** * Creates a token string. * * @param int $time Timestamp to generate the token for, -1 (default) for now. * @return string Token string. */ public function createToken(int $time = -1): string { $time = self::time($time); $nonce = random_bytes(self::NONCE_LENGTH); $hash = $this->createHash($time, $nonce); return UriBase64::encode( pack('V', $time) . $nonce . $hash ); } /** * Verifies a token string. * * @param string $token Token to test. * @param int $tolerance Amount of seconds for which the token can remain valid, < 0 for whatever the default value is. * @param int $time Point in time for which to check validity for this time. * @return bool true if the token is valid, false if not. */ public function verifyToken(string $token, int $tolerance = -1, int $time = -1): bool { if($tolerance === 0 || empty($token)) return false; if($tolerance < 0) $tolerance = $this->tolerance; $token = UriBase64::decode($token); if(empty($token)) return false; $unpacked = unpack('Vts', $token); $uTime = (int)$unpacked['ts']; if($uTime < 0) return false; $uNonce = substr($token, self::TIMESTAMP_LENGTH, self::NONCE_LENGTH); if(empty($uNonce)) return false; $uHash = substr($token, self::TIMESTAMP_LENGTH + self::NONCE_LENGTH, self::HASH_LENGTH); if(empty($uHash)) return false; $rTime = self::time($time); return ($rTime - ($uTime + $tolerance)) < 0 && hash_equals($this->createHash($uTime, $uNonce), $uHash); } }