CipherStashDocs

CipherStash vs AWS KMS

Compare CipherStash Encryption and AWS KMS for application-level encryption, covering searchable encryption, bulk operations, and developer experience.

Encrypting data should not require managing binary buffers, base64 encoding, key ARNs, or building custom search solutions. CipherStash Encryption eliminates these complexities with a developer-friendly API.

The simple truth: Encrypting a value

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('[email protected]');

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(
  '[email protected]',
  { 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(
  '[email protected]',
  { 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)

Developer experience comparison

Error handling

AWS KMS: Try/catch with manual error type checking:

try {
  const response = await client.send(command)
} catch (error) {
  if (error.name === "AccessDeniedException") {
    // Handle access denied
  } else if (error.name === "InvalidKeyUsageException") {
    // Handle invalid key usage
  }
}

CipherStash Encryption: Type-safe Result pattern:

const result = await client.encrypt(plaintext, options)

if (result.failure) {
  switch (result.failure.type) {
    case "EncryptionError":
      // TypeScript knows this is an EncryptionError
      break
    case "ClientInitError":
      // TypeScript knows this is a ClientInitError
      break
  }
}

Type safety

AWS KMS: Manual typing with binary data handling:

const plaintext: string = Buffer.from(response.Plaintext).toString("utf-8")

CipherStash Encryption: Full TypeScript inference:

const plaintext = decryptResult.data // Type: string

Storage format

AWS KMS: Binary data that needs encoding:

// Returns Uint8Array, must encode for storage
const base64 = Buffer.from(ciphertext).toString("base64")

CipherStash Encryption: JSON payload ready for database:

// Returns JSON payload ready for JSONB storage
const ciphertext = encryptResult.data

Complete workflow comparison

AWS KMS: Full implementation

import { KMSClient, EncryptCommand, DecryptCommand } 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 encrypt(plaintext: string): Promise<string> {
  const command = new EncryptCommand({
    KeyId: keyId,
    Plaintext: Buffer.from(plaintext),
  })
  const response = await client.send(command)
  return Buffer.from(response.CiphertextBlob).toString("base64")
}

async function decrypt(base64Ciphertext: string): Promise<string> {
  const command = new DecryptCommand({
    CiphertextBlob: Buffer.from(base64Ciphertext, "base64"),
  })
  const response = await client.send(command)
  return Buffer.from(response.Plaintext).toString("utf-8")
}

const encrypted = await encrypt("[email protected]")
const decrypted = await decrypt(encrypted)

~25 lines for basic encrypt/decrypt. You manage: key ARNs, binary conversions, base64 encoding, error handling, AWS credentials, regions.

CipherStash Encryption: Full implementation

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("[email protected]", {
  column: users.email,
  table: users,
})

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

const decryptResult = await client.decrypt(encryptResult.data)

~20 lines including setup. @cipherstash/stack handles everything.

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