<?php
    # Time-based One-Time Password algorithm (RFC 6238)
    
function calc_totp(
        
string $secretkey,
        
int $codelen = 6,
        
int $epoch = 0,
        
int $timestep = 30,
        ?
int $timestamp = null,
        
$hashfunc = "sha1"
    
): string {
        if (
$timestamp === null) {
            
$timestamp = time();
        }

        
assert(is_int($epoch));
        
assert(is_int($timestep));
        
assert(is_int($timestamp));

        return 
calc_hotp(
            
$secretkey,
            
pack("N*", 0, $timestamp - $epoch - ($timestamp - $epoch) % $timestep),
            
$codelen,
            
$hashfunc
        
);
    }

    
# HMAC-based One-Time Password algorithm (RFC 4226)
    
function calc_hotp(
        
string $secretkey,
        
string $counter,
        
int $codelen = 6,
        
$hashfunc = "sha1"
    
): string {
        
assert(is_string($secretkey));
        
assert(is_string($counter));
        
assert(is_int($codelen) && 1 <= $codelen && $codelen <= 9);

        
$hash = hash_hmac($hashfunc, $counter, $secretkey, true);

        
$offset = ord(substr($hash, -1)) & 0xf;
        
$val = unpack("N*", substr($hash, $offset, 4))[1] & 0x7fffffff;

        return 
str_pad((string)($val % (10 ** $codelen)), $codelen, "0", STR_PAD_LEFT);
    }