How should I structure AES‑GCM‑SIV ciphertext so Palantir Foundry (Cipher) can decrypt it?

Hello.

I encrypt specific data outside Palantir Foundry using AES‑GCM‑SIV, and then try to decrypt it inside Foundry with Cipher.
However, decryption fails with the following error. I suspect the ciphertext is shorter than Foundry expects. What is the expected ciphertext layout when using AES‑GCM‑SIV so Cipher can decrypt it successfully?

Error (excerpt):

Caused by: com.palantir.conjure.java.api.errors.ServiceException: ServiceException: INVALID_ARGUMENT (Bellaso:UndecryptableValue)
	at com.palantir.bellaso.api.BellasoErrors.undecryptableValue(BellasoErrors.java:112)
	at com.palantir.bellaso.encryption.bulk.Suppliers.silently(Suppliers.java:26)
	at com.palantir.bellaso.encryption.bulk.AesGcmSivCipherLibrary.decryptString(AesGcmSivCipherLibrary.java:89)
	at com.palantir.bellaso.encryption.bulk.EncryptionCipherLibrary.lambda$getDecryptUdf$c1a5cb6e$1(EncryptionCipherLibrary.java:161)
	at org.apache.spark.sql.functions$.$anonfun$udf$91(functions.scala:8150)
	... 20 more
Caused by: javax.crypto.AEADBadTagException: Tag mismatch
	at java.base/com.sun.crypto.provider.GaloisCounterMode$GCMDecrypt.doFinal(GaloisCounterMode.java:1545)
	at java.base/com.sun.crypto.provider.GaloisCounterMode.engineDoFinal(GaloisCounterMode.java:417)
	at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2303)
	at com.palantir.bellaso.encryption.bulk.AesGcmSivCipherLibrary.lambda$decryptString$1(AesGcmSivCipherLibrary.java:98)
	at com.palantir.bellaso.encryption.bulk.Suppliers.silently(Suppliers.java:22)
	... 23 more

My AES‑GCM‑SIV code (outside Foundry):

def aes_gcm_encrypt(b64_key: str, plaintext: str, aad: bytes = b""):
    raw_key = b64decode(b64_key)
    
    # GCM uses a 12‑byte nonce
    nonce = os.urandom(12)    

    # Encrypt using AES‑GCM‑SIV
    aesgcmsiv = AESGCMSIV(raw_key)
    ciphertext = aesgcmsiv.encrypt(nonce, plaintext.encode("utf-8"), aad)
    encrypted = nonce + ciphertext

    return {
        "plain_text": plaintext,
        "nonce": nonce,
        "aad": aad,
        "ciphertext_b64": b64encode(encrypted).decode("utf-8"),
    }

raw_key = AESGCMSIV.generate_key(256)  # 32‑byte random key for AES‑GCM‑SIV (AES‑256)
b64_key = b64encode(raw_key)  # Base64 encode the raw key to register it in Palantir Cipher
data = ["apple", "banana", "cherry", "cherry"]  # Plaintext samples

for plain_text in data:
    encrypted = aes_gcm_encrypt(b64_key, plain_text)
    print(encrypted)

Question:
What exact ciphertext composition does Foundry’s Cipher expect for AES‑GCM‑SIV?
(e.g., is it nonce(12) || ciphertext || tag(16) as a single byte sequence, or a different structure?)
Are there any additional bytes/headers that must be present in the encrypted payload before decryption in Cipher?
Any do’s/don’ts for nonce length, tag placement, AAD handling, or Base64 encoding that are required for Cipher to accept and decrypt it?

Thanks in advance!

Hey @wisteria !

Your ciphertext construction is indeed correct, but Cipher actually uses a non-standard IV/nonce length of 32B (rather than 12B) for AES-GCM-SIV! Tag length stays the same (16B).

Assuming that you’re using cryptography for the python code above, you should be able to just swap 12 for 32 and you’ll be good to go. Everything else you’re doing in your script looks correct to me, on the do’s/don’ts side. If you’re still encountering issues, may be worth trying None instead of empty bytes for your AAD.

Let me know if you run into any issues or have more questions! We’ll be adding this quirk to our public docs for the future, too.

1 Like

Hi @kat ,

Thank you for the clarification! The 32B nonce requirement is a crucial detail.

However, I have encountered a technical issue while implementing this in Python. Standard libraries like cryptography strictly enforce a 12-byte nonce for AES-GCM-SIV, as per RFC 8452. Attempting to pass a 32-byte nonce results in a ValueError.

Could you please advise on which Python library I should use to support this non-standard 32B nonce?

If there isn’t a specific library, do you have any Python code snippets or a recommended approach to bypass the 12-byte constraint so that the output remains compatible with Cipher?

Best regards.

Ah, good catch!

Could you try using standard AESGCM with the 32B IV and see if it decrypts? Keep everything else the same (including the library), just swap out the algorithm. I have a hunch…

1 Like

You were absolutely correct — your suggestion resolved the issue.

I swapped the algorithm from AESGCMSIV to the standard AESGCM and used a 32-byte IV (nonce). With this configuration, Palantir Cipher was able to decrypt the ciphertext successfully.

Thank you so much for the quick and precise support. This detail about the 32B IV requirement was exactly what I was missing. I’m glad to hear this will be added to the public docs!

import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from base64 import b64encode, b64decode

def aes_gcm_encrypt(b64_key: str, plaintext: str, aad: bytes = b""):
    raw_key = b64decode(b64_key)
    
    # Cipher(Foundry) uses a 32‑byte nonce
    nonce = os.urandom(32)

    # Encrypt using AES‑GCM‑SIV
    aesgcmsiv = AESGCM(raw_key)
    ciphertext = aesgcmsiv.encrypt(nonce, plaintext.encode("utf-8"), aad)
    encrypted = nonce + ciphertext

    return {
        "plain_text": plaintext,
        "nonce": nonce,
        "aad": aad,
        "ciphertext_b64": b64encode(encrypted).decode("utf-8"),
    }

raw_key = AESGCM.generate_key(256)  # 32‑byte random key for AES‑GCM‑SIV (AES‑256)
b64_key = b64encode(raw_key)  # Base64 encode the raw key to register it in Palantir Cipher
data = ["apple", "banana", "cherry", "cherry"]  # Plaintext samples
print("Value to register as raw key in Palantir Cipher:{0}".format(b64_key))
for plain_text in data:
    encrypted = aes_gcm_encrypt(b64_key, plain_text)
    print(encrypted)
1 Like