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

  1. Discard or restore the corrupted envelope from backup and let HMCL rewrite it (re-save the setting).
  2. Verify the 'nonce' decodes to 12 bytes and the payload lanes were not edited; do not modify the null-padded array.
  3. If it happens on old JREs, upgrade to JDK 11+ where ChaCha20-Poly1305 exists.
  4. 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

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


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)