Theme switcher

Data Security and Decryption

You may opt to receive your export encrypted at rest. Along with your API key, we’ll issue a base64-encoded 32-byte AES key (“Customer Encryption Key”).

Important: Store your key in a secrets manager. Never share it with us.

Encrypted delivery format

To request encryption:

{ "compression": "zip", "encrypted": true }

You’ll receive:

  1. A ZIP from a presigned S3 URL
  2. Inside, a single .ndjson file
  3. Each line contains:

{"encrypted_data":"key_id:iv_b64:ciphertext_plus_tag_b64"}

Part

Meaning

key_id

Identifier of the key used (informational)

iv_b64

12-byte IV (nonce), base64

ciphertext_plus_tag_b64

Base64 of `ciphertext

Example (shortened):

{"encrypted_data":"3e9c..e2f1:0R0y8kS3g8m2s6v8:AAABBBCCC...zzz"}

Cryptography profile

  • Cipher: AES-256-GCM
  • Key: your 32-byte key (base64-decode first)
  • IV: 12 bytes (from iv_b64)
  • Auth tag: 16 bytes (trailing bytes of the decoded blob)
  • AAD: stream:{customer_id}:{row_index} row_index is zero-based for each NDJSON line.

Decryption steps

  1. Unzip to get job_id.ndjson.
  2. Read file line by line.
  3. Parse JSON; get encrypted_data.
  4. split(":") → [key_id, iv_b64, ctext_tag_b64].
  5. iv = base64(iv_b64).
  6. raw = base64(ctext_tag_b64); then:
  7. ciphertext = raw[0 : len(raw) - 16]
  8. tag = raw[len(raw) - 16 : ]
  9. aad = "stream:{customer_id}:{row_index}".
  10. Decrypt with AES-256-GCM (key, iv, aad, ciphertext, tag).
  11. Parse the UTF-8 JSON result.

Reference implementations

Replace {{customer_key_b64}} and {{customer_id}} with your values (or Theneo variables).

Python

Python
from cryptography.hazmat.primitives.ciphers.aead import AESGCM import base64 import json # Your pre-shared values derived_key_base64 = "your-pre-shared-key" customer_id = "your-customer-id" # Decode your key encryption_key = base64.b64decode(derived_key_base64) # For each encrypted line (row_index starts at 0) def decrypt_line(encrypted_data, row_index): # Split the encrypted data parts = encrypted_data.split(":") iv = base64.b64decode(parts[1]) ciphertext = base64.b64decode(parts[2]) # Create AAD aad = f"stream:{customer_id}:{row_index}".encode('utf-8') # Decrypt aesgcm = AESGCM(encryption_key) decrypted_bytes = aesgcm.decrypt(iv, ciphertext, aad) # Parse JSON return json.loads(decrypted_bytes.decode('utf-8'))

Node.js

JavaScript
const crypto = require("node:crypto"); const CUSTOMER_KEY_B64 = "{{customer_key_b64}}"; const CUSTOMER_ID = "{{customer_id}}"; const KEY = Buffer.from(CUSTOMER_KEY_B64, "base64"); function decryptLine(encryptedData, rowIndex) { const [keyId, iv_b64, ctext_tag_b64] = encryptedData.split(":"); const iv = Buffer.from(iv_b64, "base64"); const raw = Buffer.from(ctext_tag_b64, "base64"); const ciphertext = raw.slice(0, -16); const tag = raw.slice(-16); const aad = Buffer.from(`stream:${CUSTOMER_ID}:${rowIndex}`, "utf8"); const decipher = crypto.createDecipheriv("aes-256-gcm", KEY, iv); decipher.setAAD(aad); decipher.setAuthTag(tag); const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]); return JSON.parse(decrypted.toString("utf8")); }

Java

Plain text

C# (.NET)

C#
using System; using System.Text; using System.Text.Json; using System.Security.Cryptography; public static class Decrypter { private static readonly string CustomerKeyB64 = "{{customer_key_b64}}"; private static readonly string CustomerId = "{{customer_id}}"; private static readonly byte[] Key = Convert.FromBase64String(CustomerKeyB64); public static string DecryptLine(string encryptedData, int rowIndex) { var parts = encryptedData.Split(':'); var iv = Convert.FromBase64String(parts[1]); var raw = Convert.FromBase64String(parts[2]); var ciphertext = raw[..^16]; var tag = raw[^16..]; var aad = Encoding.UTF8.GetBytes($"stream:{CustomerId}:{rowIndex}"); var plaintext = new byte[ciphertext.Length]; using var aes = new AesGcm(Key); aes.Decrypt(iv, ciphertext, tag, plaintext, aad); return Encoding.UTF8.GetString(plaintext); } }

Validation & troubleshooting

  • Key decodes to exactly 32 bytes
  • AAD exactly stream:{customer_id}:{row_index} (row index is zero-based)
  • IV is 12 bytes; tag is the last 16 bytes of the decoded blob
  • Don’t reuse IV/AAD on your side when testing

Was this section helpful?

What made this section unhelpful for you?

On this page
  • Data Security and Decryption