PHP script to calculate Google Authenticator HOTP

<?php

$key = base_32::to_string('HELLOWORLDSECERT');
$time = time();
$window = intdiv($time, 30);
$data = '';
for ($i = 0; $i < 8; $i++) {
	$data = chr($window & 0xff) . $data;
	$window = $window >> 8;
}
$hash_hmac = hash_hmac('sha1', $data, $key, true);
$offset = ord($hash_hmac[19]) & 0xf;
$code =
	((ord($hash_hmac[$offset++]) & 0x7f) << 24) |
	(ord($hash_hmac[$offset++]) << 16) |
	(ord($hash_hmac[$offset++]) << 8) |
	ord($hash_hmac[$offset]);
$code = $code % 1000000;
while (strlen($code) < 6) {
	$code = '0' . $code;
}
echo $code . "\n";

class base_32 {

	public static function to_string(string $p_base_32): string {
		$string = '';
		$base_32_length = strlen($p_base_32);
		if ($base_32_length % 8) {
			throw new Execption('Invalid base 32 length ' . $base_32_length . '.');
		}
		$i = 0;
		while ($i < $base_32_length) {
			$byte = [];
			for ($k = 0; $k < 8; $k++) {
				$c = ord($p_base_32[$i++]);
				if ($c === 61) { // = pad -> 255 as a not used flag
					$c = 255;
				} else if ($c > 49 && $c < 56) { // 2 to 7 -> 26 to 31
					$c -=24;
				} else if ($c > 64 && $c < 91) { // A to Z -> 0 to 25
					$c -= 65;
				} else if ($c > 96 && $c < 123) { // a to z -> 0 to 25
					$c -= 97;
				} else {
					throw new Exception('Invalid base 32 byte ' . $c . '.');
				}
				$byte[] = $c;
			}
			// Each block of eight incoming bytes decodes to five outgoing bytes.
			// Only the five least significant bits in each incoming byte are used.
			// The first outgoing byte uses bits 3, 4, 5, 6 and 7 from the first incoming byte and bits 3, 4 and 5 from the second incoming byte.
			$string .= chr($byte[0] << 3 | $byte[1] >> 2);
			// If the third incoming byte is the not used flag, we're done.
			if ($byte[2] !== 0xff) {
				// The second outgoing byte uses bits 6 and 7 from the second incoming byte, bits 3, 4, 5, 6 and 7 from the third incoming byte and bit 3 from the
				// fourth incoming byte.
				$string .= chr($byte[1] << 6 | $byte[2] << 1 | $byte[3] >> 4);
				// If the fifth incoming byte is the not used flag, we're done.
				if ($byte[4] !== 0xff) {
					// The third outgoing byte uses bits 4, 5, 6 and 7 from the fourth incoming byte and bits 3, 4, 5 and 6 from the fifth incoming byte.
					$string .= chr($byte[3] << 4 | $byte[4] >> 1);
					// If the sixth incoming byte is the not used flag, we're done.
					if ($byte[5] !== 0xff) {
						// The fourth outgoing byte uses bit 7 from the fifth incoming byte, bits 3, 4, 5, 6 and 7 from the sixth incoming byte and bits 3 and 4 from the
						// seventh incoming byte.
						$string .= chr($byte[4] << 7 | $byte[5] << 2 | $byte[6] >> 3);
						// If the eighth incoming byte is the not used flag, we're done.
						if ($byte[7] !== 0xff) {
							// The fifth outgoing byte uses bits 5, 6 and 7 from the seventh incoming byte and bits 3, 4, 5, 6 and 7 from the eighth incoming byte.
							$string .= chr($byte[6] << 5 | $byte[7]);
						}
					}
				}
			}
		}
		return $string;
	}

}