beemdevelopment/Aegis · error · OtpInfoException

Invalid Yandex secret length

Error message

Invalid Yandex secret length: %d bytes

What it means

YandexInfo.validateSecret requires the Yandex OTP secret to be exactly SECRET_LENGTH or SECRET_FULL_LENGTH bytes. QR-code secrets omit the checksum; the full form includes it. Any other length indicates corrupt or malformed secret data, so OtpInfoException is thrown.

Solutions

  1. Re-scan or regenerate the Yandex QR code so the secret is complete
  2. Verify the secret's encoding (base32 vs raw bytes) and decode it correctly before passing to parseSecret/validateSecret
  3. Strip whitespace/padding from the encoded secret and confirm the decoded byte length equals SECRET_LENGTH or SECRET_FULL_LENGTH
  4. Catch OtpInfoException around parseSecret and reject the entry with a clear message

Example fix

// before
byte[] secret = Base32.decode(secretString); // length may be wrong
YandexInfo.validateSecret(secret);
// after
byte[] secret = Base32.decode(secretString.replaceAll("\\s", ""));
if (secret.length != YandexInfo.SECRET_LENGTH && secret.length != YandexInfo.SECRET_FULL_LENGTH) {
    throw new IllegalArgumentException("Expected " + YandexInfo.SECRET_LENGTH + " or "
        + YandexInfo.SECRET_FULL_LENGTH + " bytes, got " + secret.length);
}
YandexInfo.validateSecret(secret);
Defensive patterns

Strategy: validation

Validate before calling

byte[] secret = Base32.decode(secretString.trim());
if (secret.length != YandexInfo.SECRET_LENGTH && secret.length != YandexInfo.SECRET_FULL_LENGTH) {
    throw new IllegalArgumentException("Yandex secret must be "
        + YandexInfo.SECRET_LENGTH + " or " + YandexInfo.SECRET_FULL_LENGTH + " bytes");
}

Try / catch

try {
    YandexInfo.validateSecret(secret);
} catch (OtpInfoException e) {
    Toast.makeText(ctx, "Corrupt Yandex secret: re-scan the QR code", Toast.LENGTH_LONG).show();
}

Prevention

When it happens

Trigger: Calling YandexInfo.validateSecret(byte[]) (via parseSecret) with a secret byte array whose length matches neither constant — e.g. base32-decoding errors, truncated QR payload, wrong key parameter in a yandex:// or otpauth URI.

Common situations: Importing Yandex Key entries from QR codes that were cropped or re-encoded, base32 strings containing whitespace/padding mishandled, exporting secrets from other tools with different encodings (hex vs base32 confusion).

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of beemdevelopment/Aegis@d6f4e5925a (2026-09-08). Data as JSON: /api/errors/9ed06cbd37864333. Report an issue: GitHub.

Appendix: source

Thrown at app/src/main/java/com/beemdevelopment/aegis/otp/YandexInfo.java:112

    public static byte[] parseSecret(byte[] secret) throws OtpInfoException {
        validateSecret(secret);

        if (secret.length != SECRET_LENGTH) {
            return Arrays.copyOfRange(secret, 0, SECRET_LENGTH);
        }

        return secret;
    }

    /**
     * Java implementation of ChecksumIsValid
     * From: https://github.com/norblik/KeeYaOtp/blob/188a1a99f13f82e4ef8df8a1b9b9351ba236e2a1/KeeYaOtp/Core/Secret.cs
     * License: GPLv3+
     */
    public static void validateSecret(byte[] secret) throws OtpInfoException {
        if (secret.length != SECRET_LENGTH && secret.length != SECRET_FULL_LENGTH) {
            throw new OtpInfoException(String.format("Invalid Yandex secret length: %d bytes", secret.length));
        }

        // Secrets originating from a QR code do not have a checksum, so we assume those are valid
        if (secret.length == SECRET_LENGTH) {
            return;
        }

        char originalChecksum = (char) ((secret[secret.length - 2] & 0x0F) << 8 | secret[secret.length - 1] & 0xff);

        char accum = 0;
        int accumBits = 0;

        int inputTotalBitsAvailable = secret.length * 8 - 12;
        int inputIndex = 0;
        int inputBitsAvailable = 8;

        while (inputTotalBitsAvailable > 0) {
            int requiredBits = 13 - accumBits;

View on GitHub (pinned to d6f4e5925a)