Complete Pipeline
This pipeline combines all field encryption techniques from this tutorial:
- Credit card encryption - AES-256-GCM encryption, preserve last 4 digits
- PII encryption - Encrypt emails, SSNs while preserving domain/last-4
- Address encryption - Encrypt street, preserve city/state/ZIP prefix
- Audit metadata - Track encryption timestamp, key version
The mappings generate a separate 12-byte nonce for each encrypted field and base64-encode both nonce and ciphertext. Keep each nonce with its ciphertext; decryption requires both, plus the matching key version. Key rotation and authorization remain responsibilities of the integrating system.
Full Configuration
name: encrypt-data-complete
type: pipeline
description: Complete Field Encryption Pipeline - Encrypts payment, PII, and address data while preserving analytics-safe fields.
namespace: production
labels:
category: data-security
pattern: field-encryption
config:
input:
http_server:
address: '0.0.0.0:8080'
path: /transactions
allowed_verbs: ['POST']
pipeline:
processors:
# Step 1: Encrypt payment card data (PCI-DSS)
# Keys come from the environment; a per-message nonce travels with the ciphertext.
- mapping: |
root = this
let p = this.payment
let key = env("CARD_KEY").or(throw("CARD_KEY is not set"))
let card_number_iv = uuid_v4().hash("sha256").slice(0, 12)
let cvv_iv = uuid_v4().hash("sha256").slice(0, 12)
let cardholder_name_iv = uuid_v4().hash("sha256").slice(0, 12)
let clean_card = $p.card_number.or("").string().re_replace_all("[^0-9]", "")
root.payment = if this.exists("payment") {
{
"card_number_nonce": $card_number_iv.encode("base64"),
"card_number_encrypted": $p.card_number.string().encrypt_aes("gcm", $key, $card_number_iv).encode("base64"),
"card_last_four": $clean_card.slice(-4),
"card_brand": match {
$clean_card.has_prefix("4") => "visa",
$clean_card.re_match("^5[1-5]") => "mastercard",
$clean_card.re_match("^3[47]") => "amex",
_ => "other"
},
"cvv_nonce": $cvv_iv.encode("base64"),
"cvv_encrypted": $p.cvv.string().encrypt_aes("gcm", $key, $cvv_iv).encode("base64"),
"cardholder_nonce": $cardholder_name_iv.encode("base64"),
"cardholder_encrypted": $p.cardholder_name.string().encrypt_aes("gcm", $key, $cardholder_name_iv).encode("base64"),
"expiration": $p.expiration
}
} else { deleted() }
# Step 2: Encrypt customer PII (GDPR/CCPA)
- mapping: |
root = this
let c = this.customer
let key = env("PII_KEY").or(throw("PII_KEY is not set"))
let ssn_iv = uuid_v4().hash("sha256").slice(0, 12)
let email_iv = uuid_v4().hash("sha256").slice(0, 12)
let phone_iv = uuid_v4().hash("sha256").slice(0, 12)
root.customer = if this.exists("customer") {
{
"ssn_nonce": $ssn_iv.encode("base64"),
"ssn_encrypted": $c.ssn.string().encrypt_aes("gcm", $key, $ssn_iv).encode("base64"),
"ssn_last_four": $c.ssn.string().re_replace_all("[^0-9]", "").slice(-4),
"email_nonce": $email_iv.encode("base64"),
"email_encrypted": $c.email.string().encrypt_aes("gcm", $key, $email_iv).encode("base64"),
"email_domain": $c.email.string().split("@").index(1),
"phone_nonce": $phone_iv.encode("base64"),
"phone_encrypted": $c.phone.string().encrypt_aes("gcm", $key, $phone_iv).encode("base64"),
"phone_area_code": $c.phone.string().re_replace_all("[^0-9]", "").re_replace_all("^1([0-9]{10})$", "$1").slice(0, 3)
}
} else { deleted() }
# Step 3: Encrypt address data
- mapping: |
root = this
let a = this.address
let key = env("ADDR_KEY").or(throw("ADDR_KEY is not set"))
let street_iv = uuid_v4().hash("sha256").slice(0, 12)
let zip_iv = uuid_v4().hash("sha256").slice(0, 12)
root.address = if this.exists("address") {
{
"street_nonce": $street_iv.encode("base64"),
"street_encrypted": $a.street.string().encrypt_aes("gcm", $key, $street_iv).encode("base64"),
"zip_nonce": $zip_iv.encode("base64"),
"zip_encrypted": $a.zip.string().encrypt_aes("gcm", $key, $zip_iv).encode("base64"),
"zip_prefix": $a.zip.string().slice(0, 3),
"city": $a.city,
"state": $a.state,
"country": $a.country.or("US")
}
} else { deleted() }
# Step 4: Add encryption metadata
- mapping: |
root = this
root.encryption_metadata = {
"encrypted": true,
"timestamp": now(),
"algorithm": "AES-256-GCM",
"key_version": env("KEY_VERSION").or("v1"),
"pipeline": "field-encryption"
}
- catch:
- mapping: root = deleted()
output:
stdout: {}
logger:
level: INFO
format: json
metrics:
prometheus:
path: /metrics
Input example
The displayed tutorial configuration reads CARD_KEY, PII_KEY, and
ADDR_KEY. Supply valid AES keys through your environment before adapting it.
# Send data with sensitive fields
curl -X POST http://localhost:8080/transactions \
-H "Content-Type: application/json" \
-d '{
"transaction_id": "txn_001",
"payment": {
"card_number": "4532123456789010",
"cvv": "123",
"cardholder_name": "Synthetic User"
},
"customer": {
"email": "[email protected]",
"ssn": "123-45-6789",
"phone": "4155550100"
},
"address": {
"street": "123 Main St",
"city": "San Francisco",
"state": "CA",
"zip": "94102"
}
}'
# Output: card_number → encrypted, email → encrypted, ssn → encrypted
# Analytics-safe fields preserved: last_four, domain, city, state, zip_prefix
Deploy
# Deploy to Expanso orchestrator
expanso-cli job deploy encrypt-data.yaml
# Or run locally with expanso-edge
expanso-edge run --config encrypt-data.yaml
Download
The downloadable integration variant uses /payments/transactions on port
8443 with server TLS and Authorization: Bearer authentication against
PAYMENTS_INGEST_TOKEN. It reads CARD_ENCRYPTION_KEY, PII_ENCRYPTION_KEY,
and ADDRESS_ENCRYPTION_KEY; inspect its inputs and destinations separately
from the stdout tutorial above.
Download encrypt-data.yaml
What's Next?
- Troubleshooting - Common issues and solutions
- Encryption Patterns - Advanced encryption strategies