HMCL-dev/HMCL · error · JsonParseException

Missing protected payload member: nonce

Error message

Missing protected payload member: nonce

What it means

Thrown by OBFUSCATED_V1.readPayload when the JSON envelope declares protection mode hmcl-obfuscated-v1 but has no "nonce" member (JsonUtils.getString returns null). Without the nonce stored alongside the ciphertext, the ChaCha20-Poly1305 payload cannot be decrypted, so the library fails fast with a descriptive JsonParseException.

Solutions

  1. Regenerate the envelope through ProtectionMode.writePayload so the nonce, payload lanes, and protection marker are all written consistently.
  2. If the data is recoverable, restore the missing "nonce" member (Base64, 16 chars) from a backup of the original file.
  3. Check the writer path: ensure no serializer/migration step strips the nonce member between write and read.

Example fix

// before (hand-built envelope)
JsonObject envelope = new JsonObject();
envelope.addProperty("protection", "hmcl-obfuscated-v1");
envelope.add("payload", lanes);
// after
mode.writePayload(envelope, payload); // writes protection, payload lanes, and nonce
Defensive patterns

Strategy: validation

Validate before calling

static boolean hasNonce(JsonObject envelope) {
    return envelope.has("nonce") && envelope.get("nonce").isJsonPrimitive()
        && envelope.get("nonce").getAsString() != null;
}

Type guard

static boolean isObfuscatedEnvelopeWithNonce(JsonObject envelope) {
    return envelope.has("protection") && "hmcl-obfuscated-v1".equals(envelope.get("protection").getAsString())
        && envelope.has("nonce");
}

Try / catch

try {
    return ProtectedPayload.read(envelope, JsonObject.class);
} catch (JsonParseException e) {
    LOG.warning("Envelope missing/corrupt members; rebuilding", e);
    return rebuildEnvelope();
}

Prevention

When it happens

Trigger: Calling ProtectedPayload.read(envelope, type) (or ProtectionMode.fromEnvelope(...).readPayload) on an envelope where envelope.get("nonce") is absent or JSON null — e.g. a hand-written envelope, one produced by an older/other writer, or one where the nonce member was renamed or stripped by a sanitizer.

Common situations: Manually editing or partial-copying a settings JSON; a schema migration dropping unknown members; another tool round-tripping the JSON and omitting fields it does not understand; constructing the envelope in code and forgetting envelope.addProperty("nonce", ...).

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10). Data as JSON: /api/errors/512e37f699ca7fff. Report an issue: GitHub.

Appendix: source

Thrown at HMCL/src/main/java/org/jackhuang/hmcl/setting/ProtectedPayload.java:241

                }

                String payloadText = JsonUtils.UGLY_GSON.toJson(payload);
                byte[] payloadBytes = payloadText.getBytes(StandardCharsets.UTF_8);
                byte[] encryptedPayload = encryptPayload(payloadBytes, nonce);
                String actualPayload = Base64.getEncoder().encodeToString(encryptedPayload);

                envelope.addProperty(PROPERTY_PROTECTION, id());
                envelope.add(PROPERTY_PAYLOAD, splitObfuscatedPayload(actualPayload));
                envelope.addProperty(PROPERTY_NONCE, Base64.getEncoder().encodeToString(nonce));
            }

            /// Reads the payload from the given envelope.
            @Override
            protected JsonElement readPayload(JsonObject envelope) {
                try {
                    String encodedNonce = JsonUtils.getString(envelope, PROPERTY_NONCE);
                    if (encodedNonce == null) {
                        throw new JsonParseException("Missing protected payload member: nonce");
                    }

                    byte[] nonce = Base64.getDecoder().decode(encodedNonce);
                    if (nonce.length != NONCE_SIZE) {
                        throw new JsonParseException("Protected payload nonce has invalid length");
                    }

                    String encodedPayload = joinObfuscatedPayload(envelope);
                    byte[] encryptedPayload = Base64.getDecoder().decode(encodedPayload);
                    byte[] payloadBytes = decryptPayload(encryptedPayload, nonce);
                    return JsonParser.parseString(new String(payloadBytes, StandardCharsets.UTF_8));
                } catch (IllegalArgumentException e) {
                    throw new JsonParseException("Failed to reveal protected JSON payload", e);
                }
            }
        };

        /// The serialized protection marker.

View on GitHub (pinned to 24702dc5a0)