ellite/Wallos · error · InvalidArgumentException
Invalid data.
Error message
Invalid data.
What it means
OTP::generateOTP calls unpack('C*', $hash) to convert the HMAC bytes to a byte array and throws InvalidArgumentException if unpack returns false — which for a valid hash string essentially cannot happen, so this is a defensive internal invariant check. It fires during at()/code generation.
Solutions
- Verify the digest is one of sha1, sha256, sha512: $otp->setDigest('sha256').
- Ensure the secret is a valid non-empty base32 string so getDecodedSecret() yields bytes.
- Check the PHP hash extension is enabled (php -m | grep hash).
- If it persists, report as a library bug — unpack on a valid hash_hmac result should never fail.
Example fix
// before
$totp->setDigest('sha512-256'); // unsupported
// after
$totp->setDigest('sha512'); Defensive patterns
Strategy: validation
Validate before calling
if (!in_array($otp->getDigest(), ['sha1', 'sha256', 'sha512'], true)) {
throw new DomainException('Unsupported digest for OTP generation');
} Try / catch
try {
$code = $totp->now();
} catch (InvalidArgumentException | RuntimeException $e) {
log_critical('OTP generation failed', ['exception' => $e->getMessage()]);
throw new OtpGenerationException($e);
} Prevention
- Restrict digest values to sha1/sha256/sha512 via allow-list
- Ensure secrets are valid base32 so the HMAC input is well-formed
- Keep the PHP hash extension enabled and tested in CI
- Treat this exception as a bug signal, not user error — alert on it
When it happens
Trigger: unpack() failing on the HMAC hash — only realistically possible if hash_hmac returned an empty/invalid string, e.g. an unknown digest algorithm name or an empty decoded secret slipping through.
Common situations: Setting an unsupported digest via setDigest (typo like 'sha257'), or a PHP environment where hash_hmac is disabled/behaving unexpectedly.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Unsupported " " OTP type
- Invalid "counter" parameter.
- The counter must be at least 0.
- Counter must be at least 0.
- The label is not set.
AI-assisted analysis of ellite/Wallos@52820e87ca (2026-09-13).
Data as JSON: /api/errors/7c068fac0bbdefd0.
Report an issue: GitHub.
Appendix: source
Thrown at libs/OTPHP/OTP.php:65
* @return non-empty-string
*/
final protected static function generateSecret(): string
{
return Base32::encodeUpper(random_bytes(self::DEFAULT_SECRET_SIZE));
}
/**
* The OTP at the specified input.
*
* @param 0|positive-int $input
*
* @return non-empty-string
*/
protected function generateOTP(int $input): string
{
$hash = hash_hmac($this->getDigest(), $this->intToByteString($input), $this->getDecodedSecret(), true);
$unpacked = unpack('C*', $hash);
$unpacked !== false || throw new InvalidArgumentException('Invalid data.');
$hmac = array_values($unpacked);
$offset = ($hmac[count($hmac) - 1] & 0xF);
$code = ($hmac[$offset] & 0x7F) << 24 | ($hmac[$offset + 1] & 0xFF) << 16 | ($hmac[$offset + 2] & 0xFF) << 8 | ($hmac[$offset + 3] & 0xFF);
$otp = $code % (10 ** $this->getDigits());
return str_pad((string) $otp, $this->getDigits(), '0', STR_PAD_LEFT);
}
/**
* @param array<non-empty-string, mixed> $options
*/
protected function filterOptions(array &$options): void
{
foreach ([
'algorithm' => 'sha1',
'period' => 30,
'digits' => 6,View on GitHub (pinned to 52820e87ca)