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
| Feature | AWS KMS | CipherStash Encryption |
|---|---|---|
| Basic Encryption | Requires manual buffer/base64 handling | One-line API, JSON payload |
| Key Management | You manage key ARNs | Zero-knowledge, automatic |
| Searchable Encryption | Not possible | Built-in for PostgreSQL |
| Identity-Aware Encryption | Custom implementation | Built-in LockContext |
| Bulk Operations | Manual batching | Native bulk API |
| Error Handling | Try/catch, manual types | Type-safe Result pattern |
| Type Safety | Manual typing | Full TypeScript inference |
| Storage Format | Binary (needs encoding) | JSON (database-ready) |
| ORM Integration | Manual integration | Built-in Drizzle support |
| Zero-Knowledge | AWS has key access | True zero-knowledge |
| Setup Complexity | Medium (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: stringStorage 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.dataComplete 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
CipherStash vs Homomorphic Encryption
Searchable encryption is not FHE. See how CipherStash queries encrypted data and why it's far faster than fully homomorphic encryption for real workloads.
Encrypt Query Language (EQL)
Learn how EQL adds PostgreSQL types, operators, and functions for querying encrypted data, covering the eql_v2_encrypted type and searchable index types.