beemdevelopment/Aegis · error · DatabaseImporterException

Unexpectedly high number of iterations

Error message

Unexpectedly high number of iterations: %d

What it means

The andOTP importer caps the PBKDF2 iteration count at 10,000,000. A larger value almost certainly means the file is not an andOTP backup, so the importer aborts early to avoid a multi-minute key derivation that would only fail at decryption time.

Solutions

  1. Confirm the file is an andOTP encrypted export and re-select it
  2. Re-export the backup from andOTP
  3. Check the first 4 bytes in a hex editor for a realistic iteration count (typically 10,000–1,000,000)
  4. Import via the correct importer for the file's actual source app

Example fix

// before
if (iterations > 10_000_000L) {
    throw new DatabaseImporterException(String.format("Unexpectedly high number of iterations: %d", iterations));
}
// after
long MAX_ITERATIONS = 10_000_000L;
if (iterations > MAX_ITERATIONS) {
    throw new DatabaseImporterException(String.format(
        "Not an andOTP backup (iterations=%d > %d); pick the correct file/app",
        iterations, MAX_ITERATIONS));
}
Defensive patterns

Strategy: validation

Validate before calling

int iterations = ByteBuffer.wrap(Arrays.copyOfRange(fileBytes, 0, 4)).getInt();
if (iterations <= 0 || iterations > 10_000_000) {
    throw new IllegalArgumentException("Un plausible andOTP iteration count: " + iterations);
}

Try / catch

try {
    importer.read(stream, password);
} catch (DatabaseImporterException e) {
    if (e.getMessage().startsWith("Unexpectedly high")) {
        promptUserToPickCorrectFile();
    }
}

Prevention

When it happens

Trigger: Importing a file whose first 4 bytes parse to an iteration count above 10,000,000 — typically a non-andOTP file or one from a different app whose header places other data where andOTP stores iterations.

Common situations: Wrong file selected during import; andOTP backup created with an absurd iteration count via manual config edit; corrupted/truncated file where high-order bytes shift the value.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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

Appendix: source

Thrown at app/src/main/java/com/beemdevelopment/aegis/importers/AndOtpImporter.java:127

                    | InvalidAlgorithmParameterException
                    | InvalidKeyException
                    | NoSuchPaddingException
                    | IllegalBlockSizeException e) {
                throw new RuntimeException(e);
            }
        }

        private PBKDFTask.Params getKeyDerivationParams(char[] password) throws DatabaseImporterException {
            byte[] iterBytes = Arrays.copyOfRange(_data, 0, INT_SIZE);
            int iterations = ByteBuffer.wrap(iterBytes).getInt();
            if (iterations < 1) {
                throw new DatabaseImporterException(String.format("Invalid number of iterations for PBKDF: %d", iterations));
            }
            // If number of iterations is this high, it's probably not an andOTP file, so
            // abort early in order to prevent having to wait for an extremely long key derivation
            // process, only to find out that the user picked the wrong file
            if (iterations > 10_000_000L) {
                throw new DatabaseImporterException(String.format("Unexpectedly high number of iterations: %d", iterations));
            }

            byte[] salt = Arrays.copyOfRange(_data, INT_SIZE, INT_SIZE + SALT_SIZE);
            return new PBKDFTask.Params("PBKDF2WithHmacSHA1", KEY_SIZE, password, salt, iterations);
        }

        protected DecryptedState decryptOldFormat(char[] password) throws DatabaseImporterException {
            // WARNING: DON'T DO THIS IN YOUR OWN CODE
            // this exists solely to support the old andOTP backup format
            // it is not a secure way to derive a key from a password
            MessageDigest hash;
            try {
                hash = MessageDigest.getInstance("SHA-256");
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            }
            byte[] keyBytes = hash.digest(CryptoUtils.toBytes(password));
            SecretKey key = new SecretKeySpec(keyBytes, "AES");

View on GitHub (pinned to d6f4e5925a)