TaxStreem Logo

Encryption Filing

๐Ÿ” In-Flight Encryption

How to encrypt credentials before sending them to the TaxStreem API.

Why in-flight encryption?

All Flux filing requests carry sensitive government-portal credentials (email + password). While every API call already travels over TLS (HTTPS), TaxStreem adds a second layer of encryption at the application level so that credentials are never visible in plaintext โ€” not in logs, proxies, or intermediate infrastructure. This is called in-flight encryption.

The encryptedPayload field in every Flux request must contain credentials encrypted with AES-256-GCM using your Shared Secret.

Encryption Specification

PropertyValue
AlgorithmAES-256-GCM
Key DerivationSHA-256(sharedSecret)
IV (Nonce)12 bytes (96-bit), random per request
Auth Tag16 bytes (128-bit), appended by GCM
AADsharedSecret (UTF-8 bytes)
EncodingBase64 (standard)

The #1 integration mistake โ€” missing AAD

AES-256-GCM supports Additional Authenticated Data (AAD) โ€” extra data that is authenticated but not encrypted. TaxStreem uses your sharedSecret as the AAD. You must pass it when encrypting, or the server will reject the payload with:

Error: Unsupported state or unable to authenticate data

This error means the auth tag could not be verified โ€” almost always because AAD was missing or incorrect.

Payload Format

The final transmitted value must be:

Base64( IV[12 bytes] โ€– Ciphertext โ€– AuthTag[16 bytes] )
  • IV โ€” randomly generated fresh for every request. Never reuse.
  • Ciphertext โ€” AES-GCM encryption of the JSON payload.
  • AuthTag โ€” 16-byte GCM authentication tag, appended automatically by all standard libraries.

Plaintext Before Encryption

Encrypt a JSON object with exactly these two fields:

{
  "email": "your_rev360_email@example.com",
  "password": "your_rev360_password"
}

Implementation Examples

NODE.JSTypeScript / JavaScript
import crypto from "crypto";

const IV_LENGTH = 12;

export function encryptCredentials(
  email: string,
  password: string,
  sharedSecret: string,
): string {
  // 1. Derive 32-byte AES key from sharedSecret
  const key = crypto.createHash("sha256").update(sharedSecret).digest();

  // 2. Generate fresh random IV (never reuse)
  const iv = crypto.randomBytes(IV_LENGTH);

  const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);

  // 3. Set sharedSecret as AAD โ€” required, must match server
  cipher.setAAD(Buffer.from(sharedSecret, "utf8"));

  const plaintext = JSON.stringify({ email, password });
  const ciphertext = Buffer.concat([
    cipher.update(plaintext, "utf8"),
    cipher.final(),
  ]);

  const tag = cipher.getAuthTag(); // 16 bytes

  // 4. Pack: IV || Ciphertext || AuthTag, then Base64
  return Buffer.concat([iv, ciphertext, tag]).toString("base64");
}
PYTHONcryptography library
import os
import json
import base64
import hashlib
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

def encrypt_credentials(email: str, password: str, shared_secret: str) -> str:
    # 1. Derive 32-byte AES key from sharedSecret
    key = hashlib.sha256(shared_secret.encode()).digest()

    # 2. Generate fresh random IV (never reuse)
    iv = os.urandom(12)

    aesgcm = AESGCM(key)
    plaintext = json.dumps({"email": email, "password": password}).encode()

    # 3. Pass sharedSecret as AAD (aad= parameter) โ€” required
    ciphertext_with_tag = aesgcm.encrypt(iv, plaintext, shared_secret.encode())

    # AESGCM.encrypt appends the 16-byte tag automatically
    # Result layout: IV || Ciphertext || AuthTag
    return base64.b64encode(iv + ciphertext_with_tag).decode()
GOLANGGo standard library
import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "io"
)

func EncryptCredentials(email, password, sharedSecret string) (string, error) {
    // 1. Derive 32-byte AES key from sharedSecret
    hash := sha256.Sum256([]byte(sharedSecret))
    key := hash[:]

    plaintext, err := json.Marshal(map[string]string{
        "email":    email,
        "password": password,
    })
    if err != nil {
        return "", err
    }

    // 2. Generate fresh random IV
    iv := make([]byte, 12)
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        return "", err
    }

    block, err := aes.NewCipher(key)
    if err != nil {
        return "", err
    }

    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return "", err
    }

    // 3. Pass sharedSecret as AAD โ€” the 4th argument to Seal
    aad := []byte(sharedSecret)
    ciphertextWithTag := gcm.Seal(nil, iv, plaintext, aad)

    // GCM.Seal appends the 16-byte auth tag automatically
    // Pack: IV || Ciphertext || AuthTag
    packed := append(iv, ciphertextWithTag...)
    return base64.StdEncoding.EncodeToString(packed), nil
}

Where to find your Shared Secret

Your Shared Secret is available in the Settings โ†’ Security section of your TaxStreem developer dashboard. Store it securely in your environment variables and never commit it to source control.

Shared Secret Location

The Shared Secret shown above is the value you pass as sharedSecret in all three encryption examples.

Common Errors

ErrorRoot CauseFix
Unsupported state or unable to authenticate dataAAD missing or wrong โ€” sharedSecret not passed as additional authenticated dataPass sharedSecret as AAD in your cipher call
PAYLOAD_ERROR: Failed to decrypt in-flight encrypted payloadWrong sharedSecret used for encryption, or payload was corrupted in transitVerify you are using the exact sharedSecret from the dashboard
PAYLOAD_ERROR: No encrypted payload foundencryptedPayload field is missing or empty in the request bodyEnsure the field is included and non-empty before sending
Base64 decode errorOutput was URL-safe Base64 but standard Base64 is required, or extra whitespace was addedUse standard Base64 encoding (btoa / base64.StdEncoding), trim whitespace

Security Checklist

  • Generate a fresh random IV for every request โ€” never hardcode or reuse
  • Always pass sharedSecret as AAD
  • Store your sharedSecret in environment variables โ€” never in source code
  • Never log the plaintext credentials or the sharedSecret
  • Rotate your sharedSecret via the dashboard if you suspect it has been compromised