beemdevelopment/Aegis · error · DatabaseImporterException

Unexpected cipher

Error message

Unexpected cipher: %s

What it means

Each FreeOTP token entry embeds its own encrypted key JSON with an "mCipher" field. The importer only supports AES/GCM/NoPadding for per-entry keys; any other value aborts conversion of that entry with DatabaseImporterException (wrapped into a DatabaseImporterEntryException by convertEntry's caller vaultEntry).

Solutions

  1. Update Aegis; if unsupported, re-export from stock FreeOTP
  2. Inspect the entry's key JSON mCipher value to diagnose
  3. Re-save the token in stock FreeOTP so it is encrypted with GCM and re-export
  4. Delete/re-add the affected entry in FreeOTP if only some entries fail

Example fix

// before
if (!cipherName.equals("AES/GCM/NoPadding")) {
    throw new DatabaseImporterException(String.format("Unexpected cipher: %s", cipherName));
}
// after
if (!cipherName.equals("AES/GCM/NoPadding")) {
    throw new DatabaseImporterException(String.format(
        "Unexpected cipher: %s for token key (only AES/GCM/NoPadding supported)", cipherName));
}
Defensive patterns

Strategy: try-catch

Validate before calling

JSONObject keyObj = new JSONObject(encObj.getString("key"));
if (!keyObj.getString("mCipher").equals("AES/GCM/NoPadding")) {
    // skip or warn about this entry
}

Try / catch

try {
    entry = importer.convertEntry(encObj, tokenObj);
} catch (DatabaseImporterEntryException e) {
    log.warn("Entry skipped: " + e.getMessage());
}

Prevention

When it happens

Trigger: Importing a FreeOTP backup where a token's key object has mCipher different from "AES/GCM/NoPadding" — modified FreeOTP build, schema drift, or hand-edited backup JSON.

Common situations: FreeOTP fork using CBC or another mode; partial manual edits to a backup; importing backups generated by tooling that reimplemented FreeOTP encryption differently.

Related errors


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

Appendix: source

Thrown at app/src/main/java/com/beemdevelopment/aegis/importers/FreeOtpImporter.java:245

                    VaultEntry vaultEntry = convertEntry(encObj, tokenObj);
                    result.addEntry(vaultEntry);
                } catch (DatabaseImporterEntryException e) {
                    result.addError(e);
                } catch (JSONException ignored) {
                }
            }

            return result;
        }

        private VaultEntry convertEntry(JSONObject encObj, JSONObject tokenObj)
                throws DatabaseImporterEntryException {
            try {
                JSONObject keyObj = new JSONObject(encObj.getString("key"));
                String cipherName = keyObj.getString("mCipher");
                if (!cipherName.equals("AES/GCM/NoPadding")) {
                    throw new DatabaseImporterException(String.format("Unexpected cipher: %s", cipherName));
                }
                byte[] cipherText = toBytes(keyObj.getJSONArray("mCipherText"));
                byte[] parameters = toBytes(keyObj.getJSONArray("mParameters"));
                byte[] token = keyObj.getString("mToken").getBytes(StandardCharsets.UTF_8);

                byte[] nonce = parseNonce(parameters);
                IvParameterSpec spec = new IvParameterSpec(nonce);
                Cipher cipher = Cipher.getInstance(cipherName);
                cipher.init(Cipher.DECRYPT_MODE, _masterKey, spec);
                cipher.updateAAD(token);
                byte[] secretBytes = cipher.doFinal(cipherText);

                JSONArray secretArray = new JSONArray();
                for (byte b : secretBytes) {
                    secretArray.put(b);
                }
                tokenObj.put("secret", secretArray);

View on GitHub (pinned to d6f4e5925a)