CipherStash Docs

CipherStash vs AWS KMS

A side-by-side comparison of CipherStash Encryption and AWS KMS for application-level encryption

CipherStash vs AWS KMS

Encrypting data shouldn't require managing binary buffers, base64 encoding, key ARNs, or building custom search solutions. CipherStash Encryption eliminates these complexities, giving you encryption that "just works" with a developer-friendly API.

The simple truth: Encrypting a value

Let's start with the most basic operation — encrypting a single value.

AWS KMS: Manual work required

import { KMSClient, EncryptCommand } from '@aws-sdk/client-kms';

const client = new KMSClient({ region: 'us-west-2' });
const keyId = 'arn:aws:kms:us-west-2:123456789012:key/abcd1234-efgh-5678-ijkl-9012mnopqrst';

async function encryptWithKMS(plaintext: string): Promise<string> {
  try {
    const command = new EncryptCommand({
      KeyId: keyId,
      Plaintext: Buffer.from(plaintext),
    });

    const response = await client.send(command);
    const ciphertext = response.CiphertextBlob;
    const base64Ciphertext = Buffer.from(ciphertext).toString('base64');

    return base64Ciphertext;
  } catch (error) {
    console.error('Error encrypting data:', error);
    throw error;
  }
}

const encrypted = await encryptWithKMS('secret@squirrel.example');

What you're managing: Key ARNs, binary buffer conversions, base64 encoding/decoding, manual error handling, region configuration, AWS credential setup.

CipherStash Encryption: One simple call

import { Encryption } from '@cipherstash/stack';
import { encryptedTable, encryptedColumn } from '@cipherstash/stack/schema';

const users = encryptedTable('users', {
  email: encryptedColumn('email'),
});

const client = await Encryption({
  schemas: [users],
});

const encryptResult = await client.encrypt(
  'secret@squirrel.example',
  { column: users.email, table: users }
);

if (encryptResult.failure) {
  throw new Error(encryptResult.failure.message);
}

const ciphertext = encryptResult.data;

What you get: No key management (handled by ZeroKMS), no binary conversions, no base64 encoding, type-safe error handling, JSON payload ready for storage, zero-knowledge encryption by default.

Decryption

AWS KMS

async function decryptWithKMS(base64Ciphertext: string): Promise<string> {
  try {
    const ciphertextBlob = Buffer.from(base64Ciphertext, 'base64');
    const command = new DecryptCommand({ CiphertextBlob: ciphertextBlob });
    const response = await client.send(command);
    return Buffer.from(response.Plaintext).toString('utf-8');
  } catch (error) {
    console.error('Error decrypting data:', error);
    throw error;
  }
}

CipherStash Encryption

const decryptResult = await client.decrypt(ciphertext);

if (decryptResult.failure) {
  throw new Error(decryptResult.failure.message);
}

const plaintext = decryptResult.data;

Features that AWS KMS can't do without major custom work

1. Searchable encryption: Built-in vs impossible

AWS KMS requires decrypting everything and searching in memory, storing plaintext indexes, or building a custom searchable encryption solution.

CipherStash Encryption has searchable encryption built-in:

const users = encryptedTable('users', {
  email: encryptedColumn('email')
    .freeTextSearch()
    .equality()
    .orderAndRange(),
});

const searchTerms = await client.encryptQuery('secret', {
  column: users.email,
  table: users,
});

2. Identity-aware encryption: Built-in vs custom implementation

AWS KMS has no built-in support. You must implement custom logic with encryption context as a workaround.

CipherStash Encryption has built-in identity-aware encryption:

import { LockContext } from '@cipherstash/stack/identity';

const lc = new LockContext();
const lockContext = await lc.identify(userJwt);

const encryptResult = await client.encrypt(
  'secret@squirrel.example',
  { column: users.email, table: users }
).withLockContext(lockContext);

const decryptResult = await client.decrypt(ciphertext)
  .withLockContext(lockContext);

3. Bulk operations: Native API vs manual batching

AWS KMS has no bulk API — you must manually batch operations and handle rate limits.

CipherStash Encryption has native bulk encryption:

const bulkPlaintexts = [
  { id: '1', plaintext: 'Alice' },
  { id: '2', plaintext: 'Bob' },
  { id: '3', plaintext: 'Charlie' },
];

const bulkResult = await client.bulkEncrypt(bulkPlaintexts, {
  column: users.name,
  table: users,
});

const encryptedMap = bulkResult.data;

Feature comparison

FeatureAWS KMSCipherStash Encryption
Basic EncryptionRequires manual buffer/base64 handlingOne-line API, JSON payload
Key ManagementYou manage key ARNsZero-knowledge, automatic
Searchable EncryptionNot possibleBuilt-in for PostgreSQL
Identity-Aware EncryptionCustom implementationBuilt-in LockContext
Bulk OperationsManual batchingNative bulk API
Error HandlingTry/catch, manual typesType-safe Result pattern
Type SafetyManual typingFull TypeScript inference
Storage FormatBinary (needs encoding)JSON (database-ready)
ORM IntegrationManual integrationBuilt-in Drizzle support
Zero-KnowledgeAWS has key accessTrue zero-knowledge
Setup ComplexityMedium (AWS credentials, regions)Low (just environment variables)

When to use each

Use AWS KMS when:

  • You need encryption for AWS services (S3, EBS, etc.)
  • You're encrypting infrastructure-level resources
  • You don't need to search encrypted data
  • You're comfortable with manual buffer/base64 handling

Use CipherStash Encryption when:

  • You're building applications with databases
  • You need to search encrypted data
  • You want a developer-friendly API
  • You need identity-aware encryption
  • You want zero-knowledge key management
  • You value type safety and developer experience

References

On this page