Skip to content

Security Library and Message Security

Security in data transformation is often an afterthought — until an audit finds credit card numbers in log files or patient data crossing a network boundary unencrypted. This chapter covers UTL-X's security functions, the practical decisions around message encryption in integration flows, and what should (and absolutely should NOT) happen with sensitive data during transformation.

The Security Library (stdlib/crypto)

UTL-X's security functions live in the crypto package within the standard library (stdlib/crypto). These include hashing, HMAC, AES encryption, RSA signing, and JWT/JWS operations — all using JDK built-in cryptography with zero external dependencies.

Available Functions

CategoryFunctionsUse case
Hashmd5, sha1, sha256, sha512Data integrity, checksums, fingerprinting
HMAChmacSha256, hmacSha512Message authentication, API signing
Base64base64Encode, base64DecodeBinary-to-text encoding
HexhexEncode, hexDecodeByte representation
URLurlEncode, urlDecode, urlEncodeComponentURL-safe encoding

Hashing

Hash functions produce a fixed-size fingerprint from arbitrary input. They're one-way — you can't reverse a hash to get the original data.

utlx
sha256("sensitive-data")
// "6f2c7b22f1c4e3a8d9f..."  (64 hex characters, always)

md5($input.document)
// "5d41402abc4b2a76..."     (32 hex characters)

Common uses in integration:

  • Deduplication: hash the message content, compare to previously seen hashes

  • Integrity verification: hash before sending, hash after receiving, compare

  • Anonymization: hash a customer ID to create a pseudonymous identifier

  • Cache keys: hash the transformation source + input to cache compiled results

HMAC (Hash-based Message Authentication)

HMAC combines a hash with a secret key — proving both integrity AND authenticity:

utlx
hmacSha256("message-body", "shared-secret-key")
// "a1b2c3d4..."

// Verify: receiver computes the same HMAC with the same key
// If they match, the message hasn't been tampered with

Common uses:

  • API authentication: sign requests for webhook verification (Stripe, GitHub, Shopify)

  • Message integrity: ensure messages haven't been modified in transit

  • Token generation: create short-lived authentication tokens

Note: UUID functions (generateUuid, generateUuidV7, etc.) are utility functions, not security functions. They live in the core/util packages — see Chapter 16.

Transport Security: mTLS

Before discussing message-level security, let's be clear about what's already handled:

Transport Layer Security (TLS) encrypts the connection between two endpoints. Every message UTL-X receives over HTTPS is encrypted in transit. The message content is decrypted at the TLS termination point (the container's ingress) and is in cleartext inside the container.

Mutual TLS (mTLS) goes further — both client AND server present certificates, proving identity in both directions. Azure Container Apps supports mTLS. Dapr sidecars use mTLS automatically between services in Kubernetes.

For most integration scenarios, TLS/mTLS is sufficient:

  • Messages are encrypted between endpoints

  • Identity is verified (who sent this message?)

  • No additional encryption needed inside the trusted network

Message-Level Encryption

When It's Required

Some regulations require encryption of the message payload itself — not just the connection:

  • PCI DSS: cardholder data must be encrypted at rest and in transit

  • HIPAA: protected health information (PHI) must be encrypted

  • NEN 7510: Dutch healthcare information security standard

  • Government classified data: often requires end-to-end encryption

The Encrypt-Transform-Re-encrypt Pattern

When message-level encryption is required, the transformation flow becomes:

Encrypted message arrives
    ↓ decrypt (UTLXe needs the decryption key)
    ↓ transform (cleartext in memory, briefly)
    ↓ encrypt (UTLXe needs the encryption key for the target)
Encrypted message sent

This works but adds complexity:

  • UTLXe needs access to decryption AND encryption keys

  • Key management: rotation, storage, access control

  • Performance: decrypt + encrypt adds 1-5ms per message

  • Debugging: encrypted messages cannot be inspected in logs or queues

  • The message IS in cleartext in UTLXe's memory during transformation — this is unavoidable

Why This Is a Maintenance Burden

Keys to manage:           per source × per target = N × M keys
Certificate lifecycle:    each expires, must be renewed
Key rotation:             planned rotations, emergency rotations
Access control:           who can access which keys?
Audit:                    prove keys are handled correctly
Testing:                  tests need test keys (not production keys)

For 5 source systems and 3 target systems: 15 key pairs to manage. Each with its own rotation schedule. This is why most integration platforms rely on transport encryption (TLS) and access controls — message-level encryption is reserved for regulated data that truly requires it.

Recommendation

Use transport encryption (TLS/mTLS) for all communication. Add message-level encryption only when regulation specifically requires it (PCI DSS, HIPAA, classified data). Don't encrypt "just in case" — the operational cost is significant and usually unnecessary.

Digital Signatures and Integrity

When You Need Signatures

Digital signatures prove two things: integrity (the message hasn't been modified) and authenticity (the message came from a known sender). They don't encrypt — they verify.

Common in:

  • Peppol e-invoicing: UBL invoices are digitally signed with XAdES

  • Banking (ISO 20022): payment messages signed for non-repudiation

  • API webhooks: HMAC signature in HTTP header (simpler than PKI)

  • JWT tokens: signed claims for API authentication

Impact on Transformation

Critical rule: if you transform a digitally signed message, the signature becomes invalid. The signature covers the exact bytes of the original — any change (even whitespace) breaks it.

The correct flow:

  1. Verify the signature (before transformation)

  2. Transform the content (signature is now invalid)

  3. Re-sign the output (with the sender's key for the target)

This means UTLXe must:

  • Verify signatures on input (pre-validation step)

  • Sign output (post-transformation step)

  • Have access to the appropriate keys

What UTL-X Can Do Today

utlx
// HMAC verification (simple API webhooks)
let expectedSig = hmacSha256($input.body, "webhook-secret")
if (expectedSig != $input.headers.signature)
  error("Invalid webhook signature")

// Hash for integrity check
let contentHash = sha256(renderJson($input))
{...$input, integrityHash: contentHash}

What UTL-X Already Has

  • JWS/JWT: decode, inspect, and extract claims from JWT and JWS tokens (decodeJWT, decodeJWS, getJWTClaim, getJWSHeader, getJWSPayload, isJWTExpired, and more)

  • AES encryption: symmetric encrypt/decrypt with AES-128 and AES-256 (encryptAES, decryptAES, encryptAES256, decryptAES256, generateKey, generateIV)

  • Hashing: SHA-256, SHA-512, SHA-1, SHA-384, SHA-224, SHA3, MD5, HMAC variants

  • Masking: PII protection (mask, sha256 for irreversible hashing)

What UTL-X Cannot Do Yet (Future)

  • JWS signing and verification: creating signed tokens and verifying signatures (decode/inspect is available, but signing requires private key management)

  • XMLDSig / XAdES: XML digital signature verification — required for Peppol e-invoicing compliance

  • RSA sign/verify: asymmetric cryptography for digital signatures

  • Key Vault integration: retrieve keys and secrets from Azure Key Vault, GCP Secret Manager, AWS Secrets Manager

Large Files and Sensitive Data

The Claim Check Pattern

Sensitive large files (contracts, medical images, financial documents) should NOT travel through the message body. Use the claim check pattern:

  1. Store the file in secure object storage (Azure Blob, S3, GCS) with encryption at rest

  2. Put a reference in the message (URL, blob name, CMIS object ID)

  3. UTL-X transforms the message metadata — not the file content

  4. The consuming application retrieves the file from storage using the reference

utlx
// Transform the reference, not the file:
{
  invoiceId: $input.invoiceId,
  document: {
    storageType: "azure-blob",
    container: "invoices",
    blobName: concat($input.invoiceId, ".pdf"),
    url: concat("https://storage.blob.core.windows.net/invoices/", $input.invoiceId, ".pdf")
  }
}

Avoid Base64 Embedding

Embedding binary content as Base64 in messages is an anti-pattern for sensitive data:

  • 33% size increase (Base64 overhead)

  • The entire file is in UTL-X memory during transformation

  • The file content may appear in logs, error messages, queue storage

  • No access control — anyone with the message has the file

For files under 50 KB (small images, digital signatures), Base64 is acceptable. For anything larger or sensitive, use the claim check pattern.

What NOT to Log

This topic is covered in depth in Chapter 38 (Logging and Compliance). The short version:

Never log: credit card numbers (PCI DSS), patient data (HIPAA/GDPR), passwords, API keys, session tokens, private keys.

Always log: transformation ID, timestamp, duration, success/failure, error field names (not values).

UTL-X's default logging (INFO level) logs no message content — only operational metadata. DEBUG and TRACE levels can expose sensitive data and must NEVER be enabled in production with sensitive workloads.

Released under AGPL-3.0.