juspay/hyperswitch · error · GcpKmsError
Failed UTF-8 decode of GCP KMS decrypted output
Error message
Failed UTF-8 decode of GCP KMS decrypted output
What it means
Thrown by GcpKmsClient::decrypt when the KMS decryption succeeded but the returned plaintext bytes are not valid UTF-8. The library's decrypt returns a String, so after String::from_utf8(response.plaintext) fails, this error is applied. It means the ciphertext decrypts fine but the original secret was not UTF-8 text (e.g. it was raw binary data encrypted by a different client).
Source
Thrown at crates/external_services/src/gcp_kms/core.rs:164
}
/// Errors that could occur during GCP KMS operations.
#[derive(Debug, thiserror::Error)]
pub enum GcpKmsError {
/// An error occurred when base64 decoding the input data.
#[error("Failed to base64 decode input data")]
Base64DecodingFailed,
/// An error occurred when GCP KMS decrypting the input data.
#[error("Failed to GCP KMS decrypt input data")]
DecryptionFailed,
/// An error occurred when GCP KMS encrypting the input data.
#[error("Failed to GCP KMS encrypt input data")]
EncryptionFailed,
/// An error occurred UTF-8 decoding the GCP KMS decrypted output.
#[error("Failed UTF-8 decode of GCP KMS decrypted output")]
Utf8DecodingFailed,
/// An error occurred when creating the GCP KMS client.
#[error("Failed to create GCP KMS client")]
ClientCreationFailed,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validate_fails_when_project_id_is_empty() {
let config = GcpKmsConfig {
project_id: String::new(),
location_id: "global".to_string(),
key_ring_id: "key-ring".to_string(),
key_id: "key".to_string(),View on GitHub (pinned to 806ec7dcc0)
Solutions
- Confirm the original secret is actually UTF-8 text; if it's binary, this decrypt API is not suitable — use the raw google_cloud_kms client for those payloads
- If a DEK or random key was encrypted, switch to envelope encryption and keep the decrypted bytes as Vec<u8>, never passing them through decrypt()
- Re-encrypt the secret as UTF-8 (e.g. hex or base64-encode binary data before encrypting with this client)
- Check for corrupted/truncated ciphertext that happens to decode to garbage bytes
Example fix
// before: encrypting binary data, then decrypt() must return String client.encrypt(&binary_dek).await?; let dek = client.decrypt(&ct).await?; // Utf8DecodingFailed: DEK is not UTF-8 // after: base64-encode binary payloads before encrypting with this client let b64 = base64::engine::general_purpose::STANDARD.encode(&binary_dek); let ct = client.encrypt(b64).await?; let dek_b64 = client.decrypt(&ct).await?;
Defensive patterns
Strategy: validation
Validate before calling
// If you control encryption, force text payloads so decrypt() can return String:
fn prepare_for_kms(plaintext: &[u8]) -> String {
if std::str::from_utf8(plaintext).is_ok() {
String::from_utf8_lossy(plaintext).into_owned()
} else {
use base64::Engine;
base64::engine::general_purpose::STANDARD.encode(plaintext)
}
}
let ct = client.encrypt(prepare_for_kms(payload)).await?; Try / catch
match client.decrypt(ct).await {
Ok(s) => s,
Err(e) if matches!(e.current_context(), GcpKmsError::Utf8DecodingFailed) => {
// decrypted bytes are binary; this API cannot return them — re-encrypt as
// base64/hex text or use a raw KMS client for binary payloads
return Err(e);
}
Err(e) => return Err(e),
} Prevention
- Only encrypt UTF-8 text secrets through this client; base64-encode binary data first
- Document per-key whether payloads are text or binary to prevent cross-system mismatches
- Add a startup round-trip test: encrypt then decrypt a sample secret
When it happens
Trigger: Calling GcpKmsClient::decrypt where the ciphertext decrypts to binary (non-UTF-8) plaintext — e.g. the data was encrypted with gcloud/kms API directly or by another service using GcpKmsClient::encrypt on binary bytes, since encrypt accepts arbitrary bytes (data: impl AsRef<[u8]>) but decrypt assumes UTF-8 output. Raised at String::from_utf8 at crates/external_services/src/gcp_kms/core.rs:108-109.
Common situations: Encrypting binary secrets (DER keys, serialized protobufs, random DEKs) via encrypt() then decrypting with this client, whose API only returns String; interop with other systems that encrypt raw bytes under the same KMS key; secrets containing legacy encodings (latin-1) rather than UTF-8.
Related errors
- Failed to base64 decode input data
- Failed to GCP KMS decrypt input data
- Failed to GCP KMS encrypt input data
- Failed to create GCP KMS client
AI-assisted analysis of juspay/hyperswitch@806ec7dcc0 (2026-08-28).
Data as JSON: /api/errors/baef64c93ab7110f.
Report an issue: GitHub.