beemdevelopment/Aegis · error · DatabaseImporterException

Unexpected master key KDF

Error message

Unexpected master key KDF: %s

What it means

The FreeOTP importer reads the master key object's "mAlgorithm" field from the FreeOTP backup JSON and only supports PBKDF2withHmacSHA1 and PBKDF2withHmacSHA512. Any other KDF string means Aegis cannot derive the master key, so it throws DatabaseImporterException.

Solutions

  1. Update Aegis to the latest version for newer FreeOTP KDF support
  2. Check the JSON's mAlgorithm value; re-export from stock FreeOTP if it's nonstandard
  3. If feasible, convert the backup by re-deriving tokens in the original app and re-exporting
  4. Use the FreeOTP app's key migration path (or key exchange feature) instead of direct import

Example fix

// before
if (!_mkAlgo.equals("PBKDF2withHmacSHA1") && !_mkAlgo.equals("PBKDF2withHmacSHA512")) {
    throw new DatabaseImporterException(String.format("Unexpected master key KDF: %s", _mkAlgo));
}
// after
Set<String> SUPPORTED_KDFS = Set.of("PBKDF2withHmacSHA1", "PBKDF2withHmacSHA512");
if (!SUPPORTED_KDFS.contains(_mkAlgo)) {
    throw new DatabaseImporterException(String.format(
        "Unexpected master key KDF: %s (supported: PBKDF2withHmacSHA1, PBKDF2withHmacSHA512)", _mkAlgo));
}
Defensive patterns

Strategy: validation

Validate before calling

JSONObject mk = backupJson.getJSONObject("mMasterKey");
String algo = mk.getString("mAlgorithm");
if (!algo.equals("PBKDF2withHmacSHA1") && !algo.equals("PBKDF2withHmacSHA512")) {
    throw new IllegalArgumentException("Unsupported FreeOTP KDF: " + algo);
}

Try / catch

try {
    importer.read(stream);
} catch (DatabaseImporterException e) {
    if (e.getMessage().startsWith("Unexpected master key KDF")) {
        showUserError("FreeOTP backup uses an unsupported KDF — update Aegis or re-export");
    }
}

Prevention

When it happens

Trigger: Importing a FreeOTP backup (freeotp-backup.json) whose master key entry uses an unrecognized mAlgorithm value — produced by a different/patched FreeOTP version or a hand-edited file.

Common situations: FreeOTP fork or newer release switching KDF; JSON edited by hand; attempting to import a non-FreeOTP JSON that happens to have a master key section.

Related errors


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

Appendix: source

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

    public static class EncryptedState extends State {
        private static final int MASTER_KEY_SIZE = 32 * 8;

        private final String _mkAlgo;
        private final String _mkCipher;
        private final byte[] _mkCipherText;
        private final byte[] _mkParameters;
        private final byte[] _mkToken;
        private final byte[] _mkSalt;
        private final int _mkIterations;
        private final Map<String, String> _entries;

        private EncryptedState(JSONObject mkObj, Map<String, String> entries)
                throws DatabaseImporterException, JSONException {
            super(true);

            _mkAlgo = mkObj.getString("mAlgorithm");
            if (!_mkAlgo.equals("PBKDF2withHmacSHA1") && !_mkAlgo.equals("PBKDF2withHmacSHA512")) {
                throw new DatabaseImporterException(String.format("Unexpected master key KDF: %s", _mkAlgo));
            }
            JSONObject keyObj = mkObj.getJSONObject("mEncryptedKey");
            _mkCipher = keyObj.getString("mCipher");
            if (!_mkCipher.equals("AES/GCM/NoPadding")) {
                throw new DatabaseImporterException(String.format("Unexpected master key cipher: %s", _mkCipher));
            }
            _mkCipherText = toBytes(keyObj.getJSONArray("mCipherText"));
            _mkParameters = toBytes(keyObj.getJSONArray("mParameters"));
            _mkToken = keyObj.getString("mToken").getBytes(StandardCharsets.UTF_8);
            _mkSalt = toBytes(mkObj.getJSONArray("mSalt"));
            _mkIterations = mkObj.getInt("mIterations");
            _entries = entries;
        }

        public State decrypt(char[] password) throws DatabaseImporterException {
            PBKDFTask.Params params = new PBKDFTask.Params(_mkAlgo, MASTER_KEY_SIZE, password, _mkSalt, _mkIterations);
            SecretKey passKey = PBKDFTask.deriveKey(params);
            return decrypt(passKey);

View on GitHub (pinned to d6f4e5925a)