HMCL-dev/HMCL · error · JsonParseException
Failed to reveal protected JSON payload
Error message
Failed to reveal protected JSON payload
What it means
OBFUSCATED_V1.decryptPayload wraps GeneralSecurityException from Cipher decryption into JsonParseException with this message. ChaCha20-Poly1305 is an AEAD cipher, so doFinal throws AEADBadTagException when the ciphertext or authentication tag does not verify — typically corrupt or tampered envelope data. It also covers missing cipher/provider at decrypt time.
Solutions
- Discard or restore the corrupted envelope from backup and let HMCL rewrite it (re-save the setting).
- Verify the 'nonce' decodes to 12 bytes and the payload lanes were not edited; do not modify the null-padded array.
- If it happens on old JREs, upgrade to JDK 11+ where ChaCha20-Poly1305 exists.
- Re-generate the setting instead of repairing the ciphertext — AEAD failures are not recoverable.
Example fix
// before: hand-merged envelope with altered lanes
{"protection":"hmcl-obfuscated-v1","payload":[null,"abc",...],"nonce":"..."}
// after: restored original envelope or delete and re-save
Delete the settings file; HMCL rewrites it on next launch. Defensive patterns
Strategy: try-catch
Validate before calling
if (envelope.has("nonce")) {
byte[] nonce = java.util.Base64.getDecoder().decode(envelope.get("nonce").getAsString());
if (nonce.length != 12) throw new IllegalStateException("Nonce must be 12 bytes");
} Try / catch
try {
JsonElement payload = ProtectedPayload.read(envelope, JsonObject.class);
} catch (com.google.gson.JsonParseException e) {
logger.warning("Protected payload failed AEAD verification; resetting setting", e);
settings.reset(key); // re-save so a fresh envelope is written
} Prevention
- Never modify the null-padded 256-element payload array or the nonce member.
- Keep config files out of lossy merge tools (git merges, sync conflict resolution).
- Keep backups before manual config edits; AEAD failures are unrecoverable.
- Prefer deleting the corrupt file and re-saving rather than repairing ciphertext.
When it happens
Trigger: Calling ProtectedPayload.read on an OBFUSCATED_V1 envelope whose Base64 payload or nonce was corrupted (bad decode is caught separately), truncated, modified after writing, or decoded with wrong byte ordering; or a JVM lacking ChaCha20-Poly1305.
Common situations: Hand-editing or diff-merging the obfuscated (256-element, null-padded) payload array and destroying lane contents; file corruption from crashes/sync tools; copying the nonce/payload fields inconsistently between envelopes; running on a JRE without the cipher.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- Failed to protect JSON payload
- Protected payload nonce has invalid length
- Missing protected payload member: nonce
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/56edea3788425575.
Report an issue: GitHub.
Appendix: source
Thrown at HMCL/src/main/java/org/jackhuang/hmcl/setting/ProtectedPayload.java:146
return cipher.doFinal(payload);
} catch (GeneralSecurityException e) {
throw new JsonParseException("Failed to protect JSON payload", e);
}
}
/// Decrypts the protected payload bytes.
///
/// @param payload the encrypted payload bytes with the authentication tag appended
/// @param nonce the encryption nonce
/// @return the plain payload bytes
/// @throws JsonParseException if the payload cannot be decrypted
private byte[] decryptPayload(byte[] payload, byte[] nonce) {
try {
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, PROTECTION_KEY, new IvParameterSpec(nonce));
return cipher.doFinal(payload);
} catch (GeneralSecurityException e) {
throw new JsonParseException("Failed to reveal protected JSON payload", e);
}
}
/// Returns the payload array index storing one lane in an effective payload window.
///
/// @param laneIndex the lane index
/// @param payloadSize the effective payload window size
/// @return the payload array index
private static int lanePayloadIndex(int laneIndex, int payloadSize) {
int segmentSize = payloadSize / OBFUSCATED_LANE_COUNT;
return (laneIndex + 1) * segmentSize - 1;
}
/// Splits a Base64 payload into padded lanes.
///
/// @param payload the Base64 payload to split
/// @return the padded payload lanes
private static JsonArray splitObfuscatedPayload(String payload) {View on GitHub (pinned to 24702dc5a0)