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

  1. 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.
  2. Prefer the 2-arg writePayload(envelope, payload) overload, which generates a fresh secure 12-byte nonce automatically.
  3. 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

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


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)