Complete data encryption system with envelope encryption, auditing, security policies, and KMS integration. Implements security best practices for storing encrypted content in databases without risk of discovery.
Keyring implements a practical, secure, and applicable plan for storing encrypted content in a database without discovery risk. The system covers principles, recommended architecture (envelope encryption + KMS), storage schema, defense-in-depth strategies, and ready-to-use examples in Node.js/TypeScript.
✅ Envelope Encryption: Each data has a unique DEK, encrypted by a KEK
✅ AES-256-GCM: Authenticated encryption (confidentiality + integrity)
✅ Complete Auditing: Logs all operations without exposing sensitive data
✅ Security Policies: Rate limiting, access control, automatic rotation
✅ KMS Integration: Support for AWS KMS, GCP KMS, Azure Key Vault
✅ Anomaly Detection: Identifies attack attempts
✅ Production Ready: Complete tests and documentation
npm install
npm run buildimport { Keyring, RSAKeyProvider } from './src';
// Generate key pair (in production, use KMS)
const { publicKey, privateKey } = RSAKeyProvider.generateKeyPair();
const provider = new RSAKeyProvider(publicKey, privateKey);
// Create Keyring instance
const keyring = new Keyring({ keyProvider: provider });
// Encrypt
const encrypted = await keyring.encrypt('Sensitive data');
// Save to database
// await db.save({ id: '123', ...encrypted });
// Decrypt
const decrypted = await keyring.decrypt(encrypted);
console.log(decrypted.data.toString('utf8')); // "Sensitive data"- Usage Guide: Practical examples and integration
- API Reference: Complete documentation of all functions
- Examples: 7 real use case examples
# All tests (4 suites, 31 tests)
npm test
# Unit tests only
npm run test:unit
# Integration tests only
npm run test:integration
# Run examples
npm run examplesTest Results:
✓ DEK Generator Tests ✓ PASSED
✓ Data Encryption Tests ✓ PASSED
✓ Key Wrapping Tests ✓ PASSED
✓ Integration Tests ✓ PASSED
Total: 4/4 test suites passed
🎉 All tests passed! System is ready for production.
src/
├── index.ts # Entry point, exports everything
├── keyring.ts # Main system class
├── types/
│ └── index.ts # TypeScript type definitions
├── crypto/
│ ├── dek-generator.ts # DEK and IV generation
│ ├── data-encryption.ts # AES-256-GCM encryption
│ └── key-wrapping.ts # Envelope encryption (RSA/KMS)
├── audit/
│ └── audit-logger.ts # Audit system
├── policies/
│ └── security-policies.ts # Security policies
├── examples/
│ └── usage-examples.ts # 7 practical examples
└── tests/
├── dek-generator.test.ts
├── data-encryption.test.ts
├── key-wrapping.test.ts
├── integration.test.ts
└── run-all-tests.ts
- Key Principles
Encrypt on the client (or in an application layer you control) before sending to DB — never rely solely on database encryption.
Envelope encryption: each file has a unique Data Encryption Key (DEK); the DEK is then encrypted (wrapped) by a Key Encryption Key (KEK) maintained in KMS/HSM.
Authenticity + integrity: use authenticated modes (AES-GCM, ChaCha20-Poly1305) to detect tampering.
Unique IV/nonce per encryption: use random IVs/nonce and store them with the ciphertext.
Don't leak sensitive metadata: original names, types, sizes, timestamps can reveal information — minimize or encrypt them too.
Minimum privilege and auditing for who can request keys/decrypt.
Defense layers: TLS in transit, TDE in database as extra layer (doesn't replace client-applied encryption).
1. Generate DEK → crypto.randomBytes(32)
2. Encrypt data → AES-256-GCM with DEK
3. Wrap DEK → KEK (via KMS) encrypts the DEK
4. Store → DB stores: ciphertext + iv + authTag + wrappedKey
1. Fetch from DB → ciphertext + iv + authTag + wrappedKey
2. Unwrap DEK → KEK (via KMS) decrypts the DEK
3. Decrypt data → AES-256-GCM with DEK
4. Validate integrity → Verifies authTag (detects tampering)
┌─────────────────────────────────────────────────────────────┐
│ Application │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Keyring │
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ Encryption │ │ Policies │ │ Audit │ │
│ └──────────────┘ └──────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────────┐
│ Key Provider (KMS) │ │ Audit Database │
│ - Wrap/Unwrap DEKs │ │ - Persisted logs │
│ - Managed KEK │ │ - Alerts │
└─────────────────────────┘ └─────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Main Database │
│ - ciphertext (encrypted data) │
│ - wrapped_key (encrypted DEK) │
│ - iv, auth_tag (encryption metadata) │
└─────────────────────────────────────────────────────────────┘
- Recommended architecture (summary)
Generate random DEK (e.g., 32 bytes).
Encrypt the file with DEK using AES-256-GCM (or ChaCha20-Poly1305). Store ciphertext, iv, auth_tag.
Use KMS (AWS KMS / GCP KMS / Azure Key Vault) or HSM to wrap (encrypt) the DEK with the managed KEK. The DB stores only the encrypted DEK (wrapped_key).
Store in DB: {wrapped_key, key_id, version, iv, auth_tag, ciphertext, minimal_metadata}.
For reading: app requests KMS to unwrap the DEK (verifying permissions), then decrypts the content in memory/stream and provides to the user.
Implement KEK rotation and DEK versioning.
CREATE TABLE encrypted_files (
id UUID PRIMARY KEY,
ciphertext TEXT NOT NULL, -- Base64
iv TEXT NOT NULL, -- Base64
auth_tag TEXT NOT NULL, -- Base64
wrapped_key TEXT NOT NULL, -- Base64
key_id TEXT NOT NULL, -- Identifies which KEK to use
key_version TEXT NOT NULL, -- Key version
metadata_enc TEXT, -- Base64 (optional)
created_at TIMESTAMP NOT NULL,
INDEX idx_key_id (key_id)
);{
"id": "file-123",
"ciphertext": "<bytes base64…>",
"iv": "<base64 iv>",
"auth_tag": "<base64 tag>",
"wrapped_key": "<base64 wrapped DEK>",
"key_id": "projects/.../keys/production-kek/v1",
"key_version": "v1",
"mime_type_enc": "<optional: encrypted mime type or null>",
"metadata_enc": "<optional: encrypted metadata>",
"created_at": "2025-11-03T12:00:00Z"
}Never store the DEK in plain text.
key_id identifies which KEK/KMS to use for unwrap.
- ✅ Use KMS in production (AWS KMS, GCP KMS, Azure Key Vault)
- ✅ Enable auditing and persist logs in secure database
- ✅ Configure rate limiting to prevent abuse
- ✅ Rotate keys regularly (90 days)
- ✅ Monitor anomalies continuously
- ✅ Use TLS for data in transit
- ✅ Enable TDE in database as extra layer
- ❌ Don't use RSAKeyProvider in production (dev/testing only)
- ❌ Don't log DEKs, plaintexts or sensitive data
- ❌ Don't store DEKs without wrapping
- ❌ Don't expose private keys
- ❌ Don't ignore authentication failures
- ❌ Don't share Keyring instances between security contexts
const encrypted = await keyring.encrypt('Sensitive data');
// Save to DB: encrypted.ciphertext, encrypted.wrappedKey, etc.const encrypted = await keyring.encrypt(fileBuffer, {
metadata: {
originalName: 'confidential-document.pdf',
mimeType: 'application/pdf',
size: fileBuffer.length,
}
});import { KMSClient, EncryptCommand, DecryptCommand } from '@aws-sdk/client-kms';
import { KMSKeyProvider } from './src';
const kmsClient = new KMSClient({ region: 'us-east-1' });
const provider = new KMSKeyProvider(
'arn:aws:kms:us-east-1:123456789:key/abc-def',
async (dek, keyId) => {
const result = await kmsClient.send(new EncryptCommand({
KeyId: keyId,
Plaintext: dek,
}));
return Buffer.from(result.CiphertextBlob);
},
async (wrappedKey, keyId) => {
const result = await kmsClient.send(new DecryptCommand({
KeyId: keyId,
CiphertextBlob: wrappedKey,
}));
return Buffer.from(result.Plaintext);
}
);
const keyring = new Keyring({ keyProvider: provider });import {
configureGlobalAuditLogger,
SecurityPolicyManager,
RECOMMENDED_POLICIES
} from './src';
// Configure auditing
configureGlobalAuditLogger({
persistLog: async (log) => await db.auditLogs.insert(log),
alertCallback: async (log) => {
if (!log.success) await notifySecurityTeam(log);
}
});
// Configure policies
const policyManager = new SecurityPolicyManager(
RECOMMENDED_POLICIES.rateLimitPolicy,
RECOMMENDED_POLICIES.accessControlPolicy,
RECOMMENDED_POLICIES.rotationPolicy
);
const keyring = new Keyring({ keyProvider, policyManager });const newProvider = new RSAKeyProvider(newPublicKey, newPrivateKey);
for (const record of await db.encryptedData.findAll()) {
const reencrypted = await keyring.rotateKey(record, newProvider);
await db.encryptedData.update(record.id, reencrypted);
}The system automatically detects attack attempts:
// Check for anomalies (10+ failures in 5 minutes)
const hasAnomalies = keyring.hasAnomalies(5, 10);
if (hasAnomalies) {
console.log('⚠ Possible attack detected!');
// Alert security team
// Block suspicious IP
// Review audit logs
}const stats = keyring.getAuditStats();
console.log({
total: stats.total,
byOperation: stats.byOperation,
successRate: stats.successRate,
failures: stats.failures
});
const recentLogs = keyring.getRecentLogs(100);The system supports automatic key rotation based on policies:
// Check if rotation is needed
if (keyring.shouldRotateKey()) {
// Perform rotation
await rotateAllKeys();
policyManager.markKeyRotation();
}Keyring exports all necessary functions for integration:
// In your database system
import { EncryptedData, Keyring } from '@keyring/secure-encryption';
export async function saveEncryptedFile(
userId: string,
data: Buffer,
keyring: Keyring
): Promise<string> {
const encrypted = await keyring.encrypt(data);
const id = generateId();
await db.files.insert({
id,
userId,
...encrypted,
});
return id;
}
export async function getDecryptedFile(
id: string,
keyring: Keyring
): Promise<Buffer> {
const record = await db.files.findById(id);
const decrypted = await keyring.decrypt(record);
return decrypted.data;
}If envelope encryption is used correctly, an attacker with access only to the database will only see ciphertext and wrapped_key — without the KEK (KMS), decryption is infeasible.
Extra protections:
- KMS with policy that refuses unwrap if called from suspicious IP/role
- MFA sessions
- Limit number of unwraps per time
- KEK revocation/rotation process and DEK rewrap (re-encrypt)
This project was created following security best practices. To contribute:
- Fork the repository
- Create a branch for your feature
- Run the tests:
npm test - Commit your changes
- Open a Pull Request
MIT
For questions or issues:
- Review the practical examples
- Check the API documentation
- Run tests to validate behavior
- Read the usage guide
- RSAKeyProvider is for development/testing only. Use KMS in production.
- Never log DEKs, private keys, or decrypted data.
- Configure rate limiting and auditing before using in production.
- Implement key rotation every 90 days.
- Monitor anomalies and authentication failures continuously.
Developed with focus on security and compliance 🔐
