HMCL-dev/HMCL · error · JsonParseException
Protected payload nonce has invalid length
Error message
Protected payload nonce has invalid length
What it means
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.
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.
Example fix
// before byte[] nonce = sha256(secret).cloneFirst16(); // 16 bytes mode.writePayload(envelope, payload, nonce); // after byte[] nonce = new byte[12]; new SecureRandom().nextBytes(nonce); mode.writePayload(envelope, payload, nonce);
Defensive patterns
Strategy: validation
Validate before calling
static void requireChaCha20Nonce(byte[] nonce) {
if (nonce == null || nonce.length != 12) {
throw new IllegalArgumentException("ChaCha20-Poly1305 nonce must be exactly 12 bytes");
}
} Try / catch
try {
mode.writePayload(envelope, payload, nonce);
} catch (JsonParseException e) {
// regenerate with a secure random nonce and retry once
mode.writePayload(envelope, payload);
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Missing protected payload member: nonce
- Failed to protect JSON payload
- Failed to reveal protected JSON payload
- Protected payload is not a
- Missing game manifest
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/d4989f4271b78399.
Report an issue: GitHub.
Appendix: source
Thrown at HMCL/src/main/java/org/jackhuang/hmcl/setting/ProtectedPayload.java:222
for (String laneText : laneTexts) {
result.append(laneText);
}
return result.toString();
}
/// Writes the payload into the given envelope.
@Override
protected void writePayload(JsonObject envelope, JsonElement payload) {
byte[] nonce = new byte[NONCE_SIZE];
SECURE_RANDOM.nextBytes(nonce);
writePayload(envelope, payload, nonce);
}
/// Writes the payload into the given envelope with a caller-provided nonce.
@Override
protected void writePayload(JsonObject envelope, JsonElement payload, byte[] nonce) {
if (nonce.length != NONCE_SIZE) {
throw new JsonParseException("Protected payload nonce has invalid length");
}
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) {View on GitHub (pinned to 24702dc5a0)