Encryption

How Data Border protects sensitive data with AES-256-GCM encryption

Encryption

Data Border uses industry-standard encryption to protect sensitive data at rest and in transit.

Overview

Data TypeProtection Method
Amazon refresh tokensAES-256-GCM encryption
Session cookiesAES encryption
Tenant/seller refresh tokensStored unencrypted (rotatable)
All API trafficHTTPS/TLS 1.3
DatabaseSQLite at rest
Object storageS3 server-side encryption

AES-256-GCM Encryption

Amazon refresh tokens are encrypted using AES-256-GCM, a modern authenticated encryption algorithm.

How It Works

Loading diagram...

Storage Format

Encrypted data is stored as a single base64 string containing:

[Salt 32 bytes][IV 16 bytes][Auth Tag 16 bytes][Encrypted Data]
ComponentSizePurpose
Salt32 bytesUnique per encryption, used with SCRYPT
IV16 bytesInitialization vector for GCM
Auth Tag16 bytesAuthentication/integrity verification
Encrypted DataVariableThe actual encrypted content

Key Derivation

Keys are derived from the amazonTokenSecret using SCRYPT:

// SCRYPT parameters
const KEY_LENGTH = 32  // 256 bits
const salt = randomBytes(32)
const derivedKey = await scrypt(amazonTokenSecret, salt, KEY_LENGTH)

SCRYPT is a memory-hard function that makes brute-force attacks computationally expensive.


The amazonTokenSecret

This client-provided secret is the encryption key for Amazon tokens.

Requirements

RequirementValue
Minimum length32 bytes when base64 decoded
Maximum length64 characters
FormatBase64 encoded random data
UniquenessOne per seller

Generating a Secure Secret

# Using OpenSSL
openssl rand -base64 32

# Using Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"

Security Properties

  • Data Border never stores the secret - Only encrypted tokens are stored
  • Secret never leaves your WMS - Only sent in request headers over HTTPS
  • Per-seller isolation - Each seller has a unique secret
  • Non-recoverable - If lost, the seller must re-authorize

If you lose the amazonTokenSecret, the seller's Amazon tokens cannot be decrypted. The seller will need to complete the OAuth flow again to generate new tokens.


Session cookies (used during OAuth) are encrypted with a separate key.

RequirementValue
Length64 characters
FormatRandom alphanumeric + symbols
PurposeEncrypt OAuth session state

Generating the Key

openssl rand -hex 32

What's Encrypted

  • OAuth state (prevents CSRF)
  • sellerRedirectUrl (prevents tampering)
  • Temporary session data

JWT Signing

JWTs are signed (not encrypted) for authentication.

JWT_SECRET

Used for HS256 signing when JWKS_ENDPOINT is not configured:

# Generate a secure JWT secret
openssl rand -base64 32

JWKS_ENDPOINT (Optional)

For RS256/ES256 validation via external identity provider:

JWKS_ENDPOINT=https://your-idp.com/.well-known/jwks.json

When set, JWTs are validated against the public keys from this endpoint instead of using JWT_SECRET.


Data at Rest

Database

SQLite database on Fly.io volumes:

  • Single-tenant isolation per instance
  • Litestream continuous backup to S3
  • Volume encryption (platform-level)

Object Storage (Tigris/S3)

Labels and files stored with:

  • Server-side encryption (SSE-S3)
  • Tenant-isolated prefixes
  • Signed URLs for time-limited access

Data in Transit

All traffic uses TLS 1.3:

Loading diagram...

Certificate Validation

  • Data Border validates Amazon's TLS certificates
  • Carrier certificates are validated
  • No certificate pinning (allows rotation)

Encryption Lifecycle

During OAuth

Loading diagram...

During API Calls

Loading diagram...

Security Considerations

What Data Border Can Access

DataData Border Access
Amazon refresh tokensOnly when secret is provided
Amazon access tokensTemporary, in-memory during requests
Customer PIIFetched on-demand, never stored
Labels/documentsStored encrypted in S3

What Data Border Cannot Access

  • Your amazonTokenSecret (not stored)
  • Amazon tokens without the secret
  • Data for other tenants

Threat Mitigation

ThreatMitigation
Database breachTokens encrypted, require secret to decrypt
Man-in-the-middleTLS 1.3, certificate validation
Secret brute forceSCRYPT memory-hard KDF
Token reuseTokens isolated per seller
Session hijackingShort-lived encrypted cookies

Implementation Reference

Encryption Function

async function encrypt(data: string, secret: string): Promise<string> {
  const salt = randomBytes(32)
  const iv = randomBytes(16)
  const key = await scrypt(secret, salt, 32)
  
  const cipher = createCipheriv('aes-256-gcm', key, iv)
  let encrypted = cipher.update(data, 'utf8', 'hex')
  encrypted += cipher.final('hex')
  const tag = cipher.getAuthTag()
  
  return Buffer.concat([salt, iv, tag, Buffer.from(encrypted, 'hex')])
    .toString('base64')
}

Decryption Function

async function decrypt(encryptedData: string, secret: string): Promise<string> {
  const combined = Buffer.from(encryptedData, 'base64')
  
  const salt = combined.subarray(0, 32)
  const iv = combined.subarray(32, 48)
  const tag = combined.subarray(48, 64)
  const encrypted = combined.subarray(64)
  
  const key = await scrypt(secret, salt, 32)
  const decipher = createDecipheriv('aes-256-gcm', key, iv)
  decipher.setAuthTag(tag)
  
  let decrypted = decipher.update(encrypted, undefined, 'utf8')
  decrypted += decipher.final('utf8')
  
  return decrypted
}

Next Steps

Token Types

Understand the different token types in Data Border.

WAF & Rate Limiting

Learn about request protection.