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
- Update Aegis to the latest version for newer FreeOTP KDF support
- Check the JSON's mAlgorithm value; re-export from stock FreeOTP if it's nonstandard
- If feasible, convert the backup by re-deriving tokens in the original app and re-exporting
- 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
- Use stock FreeOTP, not forks, for exports
- Update Aegis before importing
- Don't hand-edit the backup JSON
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
- Invalid number of iterations for PBKDF
- Unexpectedly high number of iterations
- Unexpected master key cipher
- Unexpected cipher
- unsupported otp type:
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)