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
- Re-encode the key with standard base64 (java.util.Base64.getEncoder()) on the client/producer side.
- Check for base64url vs standard base64 and convert ('-'→'+', '_'→'/', re-pad with '=').
- Strip whitespace/newlines from the string before sending.
- Verify the key was not truncated or altered by intermediate JSON tooling.
- 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
- Always produce the string via Base64.getEncoder().encodeToString(key.serialize())
- Standardize on standard base64 (not base64url) across clients and server
- Strip whitespace/newlines before encoding; never hand-copy keys into configs
- Add round-trip tests: encode -> decode -> reconstruct key
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
- Could not interpret bytes as a ZK credential public key
- 400 Bad Request (invalid ProfileKeyCommitment base64)
- Could not interpret identity key bytes as an EC public key
- return Response.status(422).build();
- access key length must be 16
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)