signalapp/Signal-Server · error · JsonParseException

Could not parse key as a base64-encoded value

Error message

Could not parse key as a base64-encoded value

What it means

Thrown by ZkCredentialPublicKeyAdapter.deserialize when the JSON string value cannot be base64-decoded. The adapter expects the ZK credential public key serialized as a base64 string; invalid base64 (bad characters, wrong padding, empty-ish malformed input) is surfaced as a JsonParseException. This validates encoding before the key bytes are ever interpreted.

Solutions

  1. Re-encode the key with standard base64 (java.util.Base64.getEncoder()) on the client/producer side.
  2. Check for base64url vs standard base64 and convert ('-'→'+', '_'→'/', re-pad with '=').
  3. Strip whitespace/newlines from the string before sending.
  4. Verify the key was not truncated or altered by intermediate JSON tooling.
  5. Catch JsonParseException on deserialize and return a 400 identifying the malformed field.

Example fix

// before (base64url, rejected by Base64.getDecoder())
String key = "aBcD-eFgH_iJkL";
// after (standard base64 with correct alphabet/padding)
String key = Base64.getEncoder().encodeToString(zkCredentialPublicKey.serialize());
Defensive patterns

Strategy: validation

Validate before calling

// before sending / deserializing
String s = rawKeyString.replaceAll("\\s", "");
if (!s.matches("[A-Za-z0-9+/]*={0,2}")) {
    throw new IllegalArgumentException("Key is not valid standard base64: " + s);
}
byte[] bytes = Base64.getDecoder().decode(s);

Try / catch

try {
    ZkCredentialPublicKeys keys = objectMapper.readValue(json, ZkCredentialPublicKeys.class);
} catch (JsonParseException e) {
    if (e.getMessage() != null && e.getMessage().contains("base64")) {
        throw new BadRequestException("zk credential public key must be standard base64");
    }
    throw e;
}

Prevention

When it happens

Trigger: Deserializing JSON where the zk credential public key field is a string containing non-base64 characters (spaces, URL-safe '-'/'_' instead of standard '+'/'/'), missing padding, or other characters rejected by java.util.Base64.getDecoder().

Common situations: Clients using base64url encoding while the server expects standard base64; manually copy-pasted keys with whitespace or quotes mangled; JSON produced by a different serializer that escapes or wraps the key differently; config files with hand-typed keys.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09). Data as JSON: /api/errors/b27fd56f49853021. Report an issue: GitHub.

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/util/ZkCredentialPublicKeyAdapter.java:41

    @Override
    public void serialize(final ZkCredentialPublicKey zkCredentialPublicKey,
        final JsonGenerator jsonGenerator,
        final SerializerProvider serializers) throws IOException {

      jsonGenerator.writeString(Base64.getEncoder().encodeToString(zkCredentialPublicKey.serialize()));
    }
  }

  public static class Deserializer extends JsonDeserializer<ZkCredentialPublicKey> {

    @Override
    public ZkCredentialPublicKey deserialize(final JsonParser parser, final DeserializationContext context) throws IOException {
      final byte[] zkCredentialPublicKeyBytes;

      try {
        zkCredentialPublicKeyBytes = Base64.getDecoder().decode(parser.getValueAsString());
      } catch (final IllegalArgumentException e) {
        throw new JsonParseException(parser, "Could not parse key as a base64-encoded value", e);
      }

      if (zkCredentialPublicKeyBytes.length == 0) {
        return null;
      }

      try {
        return new ZkCredentialPublicKey(zkCredentialPublicKeyBytes);
      } catch (final InvalidInputException e) {
        // this should really never happen, as ZkCredentialPublicKey simply extends ByteArray
        throw new JsonParseException(parser, "Could not interpret bytes as a ZK credential public key", e);
      }
    }
  }
}

View on GitHub (pinned to 100ab61c82)