{"record":{"id":"56edea3788425575","repo":"HMCL-dev/HMCL","slug":"failed-to-reveal-protected-json-payload","errorCode":null,"errorMessage":"Failed to reveal protected JSON payload","messagePattern":"Failed to reveal protected JSON payload","errorType":"validation","errorClass":"JsonParseException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/setting/ProtectedPayload.java","lineNumber":146,"sourceCode":"                    return cipher.doFinal(payload);\n                } catch (GeneralSecurityException e) {\n                    throw new JsonParseException(\"Failed to protect JSON payload\", e);\n                }\n            }\n\n            /// Decrypts the protected payload bytes.\n            ///\n            /// @param payload the encrypted payload bytes with the authentication tag appended\n            /// @param nonce the encryption nonce\n            /// @return the plain payload bytes\n            /// @throws JsonParseException if the payload cannot be decrypted\n            private byte[] decryptPayload(byte[] payload, byte[] nonce) {\n                try {\n                    Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);\n                    cipher.init(Cipher.DECRYPT_MODE, PROTECTION_KEY, new IvParameterSpec(nonce));\n                    return cipher.doFinal(payload);\n                } catch (GeneralSecurityException e) {\n                    throw new JsonParseException(\"Failed to reveal protected JSON payload\", e);\n                }\n            }\n\n            /// Returns the payload array index storing one lane in an effective payload window.\n            ///\n            /// @param laneIndex the lane index\n            /// @param payloadSize the effective payload window size\n            /// @return the payload array index\n            private static int lanePayloadIndex(int laneIndex, int payloadSize) {\n                int segmentSize = payloadSize / OBFUSCATED_LANE_COUNT;\n                return (laneIndex + 1) * segmentSize - 1;\n            }\n\n            /// Splits a Base64 payload into padded lanes.\n            ///\n            /// @param payload the Base64 payload to split\n            /// @return the padded payload lanes\n            private static JsonArray splitObfuscatedPayload(String payload) {","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/setting/ProtectedPayload.java#L128-L164","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: hand-merged envelope with altered lanes\n{\"protection\":\"hmcl-obfuscated-v1\",\"payload\":[null,\"abc\",...],\"nonce\":\"...\"}\n// after: restored original envelope or delete and re-save\nDelete the settings file; HMCL rewrites it on next launch.","handlingStrategy":"try-catch","validationCode":"if (envelope.has(\"nonce\")) {\n    byte[] nonce = java.util.Base64.getDecoder().decode(envelope.get(\"nonce\").getAsString());\n    if (nonce.length != 12) throw new IllegalStateException(\"Nonce must be 12 bytes\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    JsonElement payload = ProtectedPayload.read(envelope, JsonObject.class);\n} catch (com.google.gson.JsonParseException e) {\n    logger.warning(\"Protected payload failed AEAD verification; resetting setting\", e);\n    settings.reset(key); // re-save so a fresh envelope is written\n}","preventionTips":["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."],"tags":["crypto","aead","integrity-check"],"backgroundTag":"checksum-mismatch","analyzedSha":"24702dc5a0214034f4c27166d5fd30cad08cec19","analyzedAt":"2026-09-10T12:36:46.680Z","contentChangedAt":"2026-09-10T12:36:46.680Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}