{"record":{"id":"baef64c93ab7110f","repo":"juspay/hyperswitch","slug":"failed-utf-8-decode-of-gcp-kms-decrypted-output","errorCode":null,"errorMessage":"Failed UTF-8 decode of GCP KMS decrypted output","messagePattern":"Failed UTF-8 decode of GCP KMS decrypted output","errorType":"exception","errorClass":"GcpKmsError","httpStatus":null,"severity":"error","filePath":"crates/external_services/src/gcp_kms/core.rs","lineNumber":164,"sourceCode":"}\n\n/// Errors that could occur during GCP KMS operations.\n#[derive(Debug, thiserror::Error)]\npub enum GcpKmsError {\n    /// An error occurred when base64 decoding the input data.\n    #[error(\"Failed to base64 decode input data\")]\n    Base64DecodingFailed,\n\n    /// An error occurred when GCP KMS decrypting the input data.\n    #[error(\"Failed to GCP KMS decrypt input data\")]\n    DecryptionFailed,\n\n    /// An error occurred when GCP KMS encrypting the input data.\n    #[error(\"Failed to GCP KMS encrypt input data\")]\n    EncryptionFailed,\n\n    /// An error occurred UTF-8 decoding the GCP KMS decrypted output.\n    #[error(\"Failed UTF-8 decode of GCP KMS decrypted output\")]\n    Utf8DecodingFailed,\n\n    /// An error occurred when creating the GCP KMS client.\n    #[error(\"Failed to create GCP KMS client\")]\n    ClientCreationFailed,\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn validate_fails_when_project_id_is_empty() {\n        let config = GcpKmsConfig {\n            project_id: String::new(),\n            location_id: \"global\".to_string(),\n            key_ring_id: \"key-ring\".to_string(),\n            key_id: \"key\".to_string(),","sourceCodeStart":146,"sourceCodeEnd":182,"githubUrl":"https://github.com/juspay/hyperswitch/blob/806ec7dcc036d895b93313d5e8eaa2374e25bcdf/crates/external_services/src/gcp_kms/core.rs#L146-L182","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: encrypting binary data, then decrypt() must return String\nclient.encrypt(&binary_dek).await?;\nlet dek = client.decrypt(&ct).await?; // Utf8DecodingFailed: DEK is not UTF-8\n\n// after: base64-encode binary payloads before encrypting with this client\nlet b64 = base64::engine::general_purpose::STANDARD.encode(&binary_dek);\nlet ct = client.encrypt(b64).await?;\nlet dek_b64 = client.decrypt(&ct).await?;","handlingStrategy":"validation","validationCode":"// If you control encryption, force text payloads so decrypt() can return String:\nfn prepare_for_kms(plaintext: &[u8]) -> String {\n    if std::str::from_utf8(plaintext).is_ok() {\n        String::from_utf8_lossy(plaintext).into_owned()\n    } else {\n        use base64::Engine;\n        base64::engine::general_purpose::STANDARD.encode(plaintext)\n    }\n}\nlet ct = client.encrypt(prepare_for_kms(payload)).await?;","typeGuard":null,"tryCatchPattern":"match client.decrypt(ct).await {\n    Ok(s) => s,\n    Err(e) if matches!(e.current_context(), GcpKmsError::Utf8DecodingFailed) => {\n        // decrypted bytes are binary; this API cannot return them — re-encrypt as\n        // base64/hex text or use a raw KMS client for binary payloads\n        return Err(e);\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["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"],"tags":["gcp-kms","utf-8","rust","decoding","secrets"],"backgroundTag":"invalid-utf8-decode","analyzedSha":"806ec7dcc036d895b93313d5e8eaa2374e25bcdf","analyzedAt":"2026-08-28T16:43:48.683Z","contentChangedAt":"2026-08-28T16:43:48.683Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}