{"record":{"id":"d4989f4271b78399","repo":"HMCL-dev/HMCL","slug":"protected-payload-nonce-has-invalid-length","errorCode":null,"errorMessage":"Protected payload nonce has invalid length","messagePattern":"Protected payload nonce has invalid length","errorType":"validation","errorClass":"JsonParseException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/setting/ProtectedPayload.java","lineNumber":222,"sourceCode":"                for (String laneText : laneTexts) {\n                    result.append(laneText);\n                }\n                return result.toString();\n            }\n\n            /// Writes the payload into the given envelope.\n            @Override\n            protected void writePayload(JsonObject envelope, JsonElement payload) {\n                byte[] nonce = new byte[NONCE_SIZE];\n                SECURE_RANDOM.nextBytes(nonce);\n                writePayload(envelope, payload, nonce);\n            }\n\n            /// Writes the payload into the given envelope with a caller-provided nonce.\n            @Override\n            protected void writePayload(JsonObject envelope, JsonElement payload, byte[] nonce) {\n                if (nonce.length != NONCE_SIZE) {\n                    throw new JsonParseException(\"Protected payload nonce has invalid length\");\n                }\n\n                String payloadText = JsonUtils.UGLY_GSON.toJson(payload);\n                byte[] payloadBytes = payloadText.getBytes(StandardCharsets.UTF_8);\n                byte[] encryptedPayload = encryptPayload(payloadBytes, nonce);\n                String actualPayload = Base64.getEncoder().encodeToString(encryptedPayload);\n\n                envelope.addProperty(PROPERTY_PROTECTION, id());\n                envelope.add(PROPERTY_PAYLOAD, splitObfuscatedPayload(actualPayload));\n                envelope.addProperty(PROPERTY_NONCE, Base64.getEncoder().encodeToString(nonce));\n            }\n\n            /// Reads the payload from the given envelope.\n            @Override\n            protected JsonElement readPayload(JsonObject envelope) {\n                try {\n                    String encodedNonce = JsonUtils.getString(envelope, PROPERTY_NONCE);\n                    if (encodedNonce == null) {","sourceCodeStart":204,"sourceCodeEnd":240,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/setting/ProtectedPayload.java#L204-L240","documentation":"Thrown by OBFUSCATED_V1.writePayload when the caller-supplied nonce byte array is not exactly 12 bytes (ChaCha20-Poly1305 nonce size). ChaCha20-Poly1305 requires a fixed 12-byte nonce, so the library rejects any other length before encrypting rather than letting the JCE fail later. The random-nonce overload always supplies a valid nonce; only the explicit writePayload(envelope, payload, nonce) overload can trigger this.","triggerScenarios":"Calling ProtectionMode.OBFUSCATED_V1.writePayload(envelope, payload, nonce) (the 3-arg protected overload) with a nonce byte[] whose length != 12 — e.g. a 16-byte AES-style IV, an empty array, or a nonce decoded from a truncated Base64 string.","commonSituations":"Tests or tools that inject a deterministic nonce for reproducible envelopes; code ported from AES-GCM (12 or 16 byte confusion); hand-crafted nonces read back from a corrupted or hand-edited config file; using a password digest or hash prefix as a nonce.","solutions":["Supply exactly 12 bytes: Arrays.copyOf(nonce, 12) only after confirming the source data is a real ChaCha20 nonce, or generate one with new byte[12] + SecureRandom.nextBytes.","Prefer the 2-arg writePayload(envelope, payload) overload, which generates a fresh secure 12-byte nonce automatically.","If the nonce came from Base64 decoding, verify the encoded string is 16 Base64 chars (12 bytes) before decoding."],"exampleFix":"// before\nbyte[] nonce = sha256(secret).cloneFirst16(); // 16 bytes\nmode.writePayload(envelope, payload, nonce);\n// after\nbyte[] nonce = new byte[12];\nnew SecureRandom().nextBytes(nonce);\nmode.writePayload(envelope, payload, nonce);","handlingStrategy":"validation","validationCode":"static void requireChaCha20Nonce(byte[] nonce) {\n    if (nonce == null || nonce.length != 12) {\n        throw new IllegalArgumentException(\"ChaCha20-Poly1305 nonce must be exactly 12 bytes\");\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    mode.writePayload(envelope, payload, nonce);\n} catch (JsonParseException e) {\n    // regenerate with a secure random nonce and retry once\n    mode.writePayload(envelope, payload);\n}","preventionTips":["Use the 2-arg writePayload overload unless you have a strong reason to control the nonce.","Centralize nonce generation in one utility that always allocates new byte[12] filled by SecureRandom.","Never reuse AES IVs or hash prefixes as ChaCha20 nonces."],"tags":["crypto","nonce","chacha20-poly1305","encryption","hmcl"],"backgroundTag":"invalid-argument-value","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"}