getCode($secret, $timeSlice + $i, $digits); if (hash_equals($calc, $code)) { return true; } } return false; } public function getOtpAuthUrl($issuer, $account, $secret) { $label = rawurlencode($issuer . ':' . $account); $issuerParam = rawurlencode($issuer); return "otpauth://totp/{$label}?secret={$secret}&issuer={$issuerParam}&algorithm=SHA1&digits=6&period=30"; } public function getQrCodeUrl($otpAuthUrl, $size = 200) { $encoded = rawurlencode($otpAuthUrl); return "https://api.qrserver.com/v1/create-qr-code/?size={$size}x{$size}&data={$encoded}"; } protected function getCode($secret, $timeSlice, $digits) { $secretKey = $this->base32Decode($secret); if ($secretKey === '') { return ''; } $time = pack('N*', 0) . pack('N*', $timeSlice); $hash = hash_hmac('sha1', $time, $secretKey, true); $offset = ord(substr($hash, -1)) & 0x0F; $truncatedHash = substr($hash, $offset, 4); $value = unpack('N', $truncatedHash)[1] & 0x7FFFFFFF; $modulo = pow(10, $digits); return str_pad((string) ($value % $modulo), $digits, '0', STR_PAD_LEFT); } protected function base32Decode($secret) { $secret = strtoupper($secret); $secret = preg_replace('/[^A-Z2-7]/', '', $secret); if ($secret === '') { return ''; } $alphabet = array_flip(str_split(self::BASE32_ALPHABET)); $binary = ''; $buffer = 0; $bitsLeft = 0; foreach (str_split($secret) as $char) { if (!isset($alphabet[$char])) { continue; } $buffer = ($buffer << 5) | $alphabet[$char]; $bitsLeft += 5; while ($bitsLeft >= 8) { $bitsLeft -= 8; $binary .= chr(($buffer >> $bitsLeft) & 0xFF); } } return $binary; } }