logo
▼
Projects
Collaborations
Resources
Our Partners
Our Community
Projects
Collaborations
Resources
Our Partners
Our Community
Account
Sign InJoin UsHelp & Support

The Cometbid
Technology Foundation

Empowering innovation through open-source collaboration. TCTF supports developers, organizations, and communities worldwide in building the future of technology with transparent, vendor-neutral governance and world-class open-source projects.


Follow Us

Our Community

  • About Us
  • Upcoming Events
  • Projects
  • Collaborations
  • Membership
  • TCTF Training
  • Corporate Sponsorship

Learn

  • FAQ
  • TCTF Incubator Programs
  • Brand Guidelines
  • Logo Specifications

Legal

  • Privacy Policy
  • Terms of Use
  • Compliance
  • Code of Conduct
  • Contribution Guidelines
  • Legal & Trademark
  • Manage Cookies

More

  • Report a Vulnerability
  • Report Bugs
  • Mailing Lists
  • Contact Us
  • Support
  • Support Tickets
  • TCTF Social Network

Subscribe to our Newsletter

Pluggable Encryption: How We Built a Provider-Agnostic Crypto Service
Framework Deep DivesFramework Series #8

Pluggable Encryption: How We Built a Provider-Agnostic Crypto Service

Inside our encryption architecture — supporting AWS KMS, HashiCorp Vault, and local providers with a unified interface, automatic key rotation, envelope encryption, and zero-downtime provider switching.

July 15, 2026· 13 min read
TCTF Editorials
TCTF Newsletter
Home›Newsletter›Pluggable Encryption: How We Built a Provider-A...

In This Edition

  • Why Pluggable Encryption
  • The EncryptionProvider Interface
  • The Providers
  • The PluggableEncryptionService
  • The Factory and Connection Pooling
  • Where We Use It
  • Lessons Learned
4Providers
7Interface Methods
AutomaticKey Rotation
YesEnvelope Encryption
Built-inConnection Pooling
Zero DowntimeProvider Switching

A platform that handles user profiles, messages, payments, and project collaboration encrypts a lot of data. Pagination tokens that hide DynamoDB key structures from clients. Session data that must be tamper-proof. User PII that must be encrypted at rest. API keys that must be stored securely. File uploads that must be encrypted before hitting S3. Each of these has different requirements — different key sizes, different algorithms, different compliance needs. And the encryption provider might change. You start with AWS KMS, then a compliance requirement pushes you to HashiCorp Vault, then a new region needs a different key management strategy. If your encryption code is tightly coupled to one provider, every change is a rewrite. At TCTF, we solved this with a pluggable encryption service — a provider-agnostic interface that lets you swap encryption backends without changing a single line of business logic.

01Why Pluggable Encryption

Most encryption code is tightly coupled to a specific provider. You import the AWS KMS SDK, call kms.encrypt(), and move on. It works. But it creates three problems.

First, testing is painful. KMS calls require AWS credentials, network access, and a real KMS key. Unit tests become integration tests. Mocking the KMS SDK is fragile and breaks when the SDK updates.

Second, provider switching is expensive. If you need to move from KMS to Vault — for compliance, for multi-cloud, for cost — you are rewriting every file that calls kms.encrypt(). In a platform with 34 services, that is hundreds of call sites.

Third, different environments need different providers. Production uses KMS with automatic key rotation. Development uses a local provider with a static key. Staging might use Vault. If the provider is hardcoded, you need conditional logic everywhere.

The pluggable encryption service solves all three. Business logic calls encrypt() and decrypt() on a provider-agnostic interface. The factory creates the right provider based on configuration. Tests use the local provider. Production uses KMS. Vault is available when compliance requires it. The switch is a configuration change, not a code change.

🔌

Business logic calls encrypt() and decrypt() on a provider-agnostic interface. The factory creates the right provider. Switching from KMS to Vault is a config change, not a code change.

Pluggable encryption architecture showing callers (pagination tokens, session data, user PII, API keys, file encryption) flowing through the PluggableEncryptionService and EncryptionProvider interface to four swappable providers (AWS KMS, HashiCorp Vault, KMS Encryption with envelope encryption, Local Provider) managed by the EncryptionServiceFactory.
Fig. 1 — Pluggable encryption architecture showing callers (pagination tokens, session data, user PII, API keys, file encryption) flowing through the PluggableEncryptionService and EncryptionProvider interface to four swappable providers (AWS KMS, HashiCorp Vault, KMS Encryption with envelope encryption, Local Provider) managed by the EncryptionServiceFactory.

02The EncryptionProvider Interface

The EncryptionProvider interface defines the contract that every provider must implement. It has seven methods.

encrypt(data, context) takes a string and an optional encryption context (key-value pairs that bind the ciphertext to a specific purpose) and returns an encrypted string. decrypt(encryptedData, context) reverses the operation.

generateHash(data, algorithm) produces a cryptographic hash — used for integrity checks, deduplication, and password-adjacent operations. verifyHash(data, hash, algorithm) checks if a hash matches.

encryptLargeData(data) handles objects that exceed the provider's size limit. KMS, for example, has a 4KB limit on direct encryption. Large data uses envelope encryption — encrypt the data with a data key, encrypt the data key with the master key, return both. decryptLargeData reverses this.

The interface is deliberately minimal. Every provider must support encrypt and decrypt. The other methods are optional — providers that do not support hashing or large data encryption can omit them, and the PluggableEncryptionService will throw a clear error if a caller tries to use an unsupported method.

📐

Seven interface methods: encrypt, decrypt, generateHash, verifyHash, encryptLargeData, decryptLargeData, plus encryption context binding. Minimal contract, maximum flexibility.

The Providers

03The Providers

Four providers implement the EncryptionProvider interface.

The AWS KMS Provider uses AWS Key Management Service for encryption. It calls kms:Encrypt and kms:Decrypt with a customer-managed key (CMK). Key rotation is automatic — AWS rotates the backing key annually, and old ciphertexts remain decryptable. The provider supports encryption context, which binds ciphertext to a specific purpose (pagination, session, PII) and prevents context-stripping attacks.

The HashiCorp Vault Provider uses Vault's Transit secrets engine. It authenticates via token or AppRole, then calls the transit/encrypt and transit/decrypt endpoints. Vault handles key management, rotation, and access control independently of AWS. This provider is for organizations that use Vault as their central secrets management platform or need encryption that is not tied to a single cloud provider.

The KMS Encryption Provider adds envelope encryption on top of KMS. Instead of encrypting data directly with the CMK (which has a 4KB limit), it generates a data encryption key (DEK), encrypts the data with the DEK using AES-256-GCM, encrypts the DEK with the CMK, and returns both. This supports arbitrarily large data while keeping the CMK secure. Data key caching reduces KMS API calls for repeated operations.

The Local Provider uses Node.js crypto with AES-256-GCM and a static key from environment variables. It is for development and testing only — never for production. It provides the same interface as the other providers, so code that works with the local provider works identically with KMS or Vault.

04The PluggableEncryptionService

The PluggableEncryptionService is the class that callers interact with. It wraps an EncryptionProvider and adds higher-level operations.

encryptObject takes an object and a list of field names, and encrypts only those fields — leaving the rest untouched. This is how we encrypt PII fields (email, phone, address) in a DynamoDB item without encrypting the partition key or sort key. decryptObject reverses it.

encryptLargeData serializes an object to JSON, compresses it, and encrypts the result using the provider's large data support. This is used for encrypting file metadata, large configuration objects, and bulk export data.

The service supports runtime provider switching via setProvider(). This enables zero-downtime migration between providers. You can start encrypting new data with Vault while still decrypting old data with KMS. Once all old data has been re-encrypted, you remove the KMS provider entirely.

The fromEnvironment() factory method reads the ENCRYPTION_PROVIDER environment variable and creates the appropriate provider. Set it to kms for AWS KMS, vault for HashiCorp Vault, kms-encryption for envelope encryption, or local for development. The same code, different providers, controlled by a single environment variable.

⚙️

Set ENCRYPTION_PROVIDER=kms for production, vault for compliance, local for development. Same code, different providers, one environment variable.

05The Factory and Connection Pooling

The EncryptionServiceFactory creates providers with proper configuration, caching, and connection pooling.

Provider instances are cached by configuration key. If two services request a KMS provider with the same key ARN, they get the same instance. This prevents connection proliferation in Lambda functions that handle multiple encryption operations per invocation.

Connection pooling is configurable: maximum connections, idle timeout, and connection reuse strategy. For KMS, this controls the HTTP connection pool to the KMS API. For Vault, it controls the HTTP connection pool to the Vault server. The defaults are tuned for Lambda — small pool sizes, short idle timeouts, aggressive connection reuse.

The factory also handles Vault authentication. The Vault provider needs a token to call the Transit engine. The factory supports token-based auth (for development) and AppRole auth (for production). AppRole credentials are stored in AWS Secrets Manager and resolved at runtime.

The clearCache() method resets all cached providers — useful for testing and for Lambda functions that need to pick up configuration changes without a cold start.

06Where We Use It

The encryption service is used across the platform for different purposes.

Pagination tokens: The PaginationEncryption utility encrypts DynamoDB lastEvaluatedKey objects into tamper-proof tokens. Clients receive an opaque string. They cannot see the DynamoDB key structure, cannot modify it, and cannot forge a token for a different page. The encryption context binds the token to the pagination purpose, preventing it from being used as a session token or API key.

Session data: Refresh tokens and session metadata are encrypted before storage. Even if the DynamoDB table is compromised, the session data is unreadable without the encryption key.

User PII: Email addresses, phone numbers, and other personally identifiable information are encrypted at the field level using encryptObject. The DynamoDB item stores encrypted values — a database dump reveals nothing.

API keys: Service-to-service API keys are encrypted before storage and decrypted at validation time. The encryption context includes the service name, preventing a key issued for one service from being used with another.

File encryption: Files uploaded to S3 are encrypted using the large data provider before storage. The encrypted file and the encrypted data key are stored together. Decryption requires both the file and access to the master key.

🔐

Pagination tokens, session data, user PII, API keys, file uploads — all encrypted through the same pluggable service. One interface, consistent protection, across every service.

07Lessons Learned

Building a pluggable encryption service taught us several lessons.

First, encryption context is not optional. Without context binding, an encrypted pagination token could be replayed as a session token. Context ties ciphertext to its purpose and prevents cross-purpose attacks.

Second, the local provider is essential for developer experience. If every unit test requires KMS credentials, developers will skip encryption in tests. The local provider makes encryption testable without infrastructure.

Third, envelope encryption is necessary for any data larger than 4KB. Direct KMS encryption has a size limit. Envelope encryption removes that limit while keeping the master key secure in KMS.

Fourth, provider switching should be a configuration change, not a migration. The pluggable interface means you can run KMS and Vault side by side during a transition. New data uses the new provider. Old data is decrypted with the old provider and re-encrypted with the new one on access. Zero downtime, zero data loss.

Fifth, cache provider instances aggressively. Creating a new KMS client or Vault connection for every encryption call is expensive. The factory's caching ensures one instance per configuration, reused across all operations in a Lambda invocation.

Encryption is one of those things that should be invisible when it works. Users do not know their pagination tokens are encrypted. They do not know their PII is encrypted at the field level. They do not know that the platform can switch from KMS to Vault without a single line of code changing. And that is exactly the point. The pluggable encryption service makes security a default, not a feature — consistent, testable, and provider-agnostic across all 34 services.

Editor's Note: This is Framework Series #8 in the TCTF Newsletter. Next in the series: Error Handling Architecture — from custom errors to automatic recovery.

Never miss an edition

Subscribe to get TCTF newsletters delivered to your inbox.

Subscribe
PreviousTCTF's DynamoDB Framework, Part 3: Transaction Builder and Advanced Patterns
NextError Handling Architecture: From Custom Errors to Automatic Recovery

In This Edition

  • Why Pluggable Encryption
  • The EncryptionProvider Interface
  • The Providers
  • The PluggableEncryptionService
  • The Factory and Connection Pooling
  • Where We Use It
  • Lessons Learned

Browse by Month

May
  • The Struggles of Timelines and Schedules: When Building Gets Real
  • How to Stay Motivated in the Face of Uncertainties: Faith Beyond Doubt
  • Cognito Middleware: Building an Authentication Pipeline for Serverless APIs
  • Building TCTF's DynamoDB Query Framework, Part 1: Single-Table Design Patterns
April
  • Built to Last: Why Sustained Collaboration Is the Future of Tech Teams
  • Q2 2026 Roadmap: What's Next for the TCTF Portal
March
  • How We Built a Real-Time Messaging System with AWS Lambda and WebSockets
  • From Forum to Social Network: The Origin Story of Cometbid Social
  • Agentic AI: What It Means for Software Development and Why We're Paying Attention
February
  • Platform Update: Social Network Architecture, Achievement Engine, and What's Next
  • How We Built 34 Serverless Microservices: Architecture Patterns Behind the TCTF Platform
January
  • How We Secure the TCTF Platform: Principles Every Developer Should Know
  • New Year, New Projects: TCTF 2026 Roadmap

More From TCTF Newsletter

Built to Last: Why Sustained Collaboration Is the Future of Tech TeamsVol. 1, Issue 4

Built to Last: Why Sustained Collaboration Is the Future of Tech Teams

Most platforms optimize for transactions — post a job, hire, move on. TCTF is built around sustained collaboration: long-term teams, milestone-driven projects, language support that breaks barriers, and a community where everyone — not just developers — has a seat at the table.

April 15, 2026
Q2 2026 Roadmap: What's Next for the TCTF PortalQ2 2026

Q2 2026 Roadmap: What's Next for the TCTF Portal

Our quarterly roadmap for Q2 — what shipped in April, the origin of Cometbid Social, and the plan for May and June as we build toward user accounts, authentication, and the social network launch.

April 1, 2026
How We Built a Real-Time Messaging System with AWS Lambda and WebSocketsTech Series #3

How We Built a Real-Time Messaging System with AWS Lambda and WebSockets

Inside the architecture of TCTF's messaging platform — three services handling real-time chat, campaign delivery, and transactional notifications, all built on Lambda, API Gateway WebSockets, SQS, and multi-provider email with automatic failover.

March 15, 2026

Browse by Month

2026

May
  • The Struggles of Timelines and Schedules: When Building Gets Real
  • How to Stay Motivated in the Face of Uncertainties: Faith Beyond Doubt
  • Cognito Middleware: Building an Authentication Pipeline for Serverless APIs
  • Building TCTF's DynamoDB Query Framework, Part 1: Single-Table Design Patterns
April
  • Built to Last: Why Sustained Collaboration Is the Future of Tech Teams
  • Q2 2026 Roadmap: What's Next for the TCTF Portal
March
  • How We Built a Real-Time Messaging System with AWS Lambda and WebSockets
  • From Forum to Social Network: The Origin Story of Cometbid Social
  • Agentic AI: What It Means for Software Development and Why We're Paying Attention
February
  • Platform Update: Social Network Architecture, Achievement Engine, and What's Next
  • How We Built 34 Serverless Microservices: Architecture Patterns Behind the TCTF Platform
January
  • How We Secure the TCTF Platform: Principles Every Developer Should Know
  • New Year, New Projects: TCTF 2026 Roadmap