iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

KMS / Vault

A Key Management Service stores cryptographic keys in hardened hardware (HSMs) and lets your code request operations — encrypt, decrypt, sign, verify — without ever seeing the raw key. AWS KMS, GCP KMS, Azure Key Vault, and HashiCorp Vault Transit are the standard offerings.

Envelope encryption, rotation, IAM

EXAMPLE
// 1) Envelope encryption — the central pattern
//
//   Plain data → encrypt with DEK (Data Encryption Key, generated per file)
//   DEK         → encrypt with KEK (Key Encryption Key, lives in KMS)
//   Store      → ciphertext + wrapped DEK (the DEK encrypted by KEK)
//
//   Pros:
//     • Small KMS calls (encrypt 32-byte DEK) instead of huge data
//     • Per-record / per-tenant DEKs without per-record KMS roundtrips
//     • Rotate KEK without re-encrypting all data (just re-wrap DEKs)
//
//   The DEK is generated locally and used once; the KEK never leaves KMS.

// 2) AWS KMS — encrypt a file with envelope encryption
import { KMSClient, GenerateDataKeyCommand, DecryptCommand } from '@aws-sdk/client-kms';
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';

const kms = new KMSClient({ region: 'ap-southeast-2' });
const KEY_ID = 'alias/app-data';

async function encrypt(plaintext) {
    // 1. Ask KMS for a fresh DEK
    const { Plaintext: dek, CiphertextBlob: wrappedDek } = await kms.send(
        new GenerateDataKeyCommand({ KeyId: KEY_ID, KeySpec: 'AES_256' }),
    );

    // 2. Encrypt the data locally with AES-GCM
    const iv = randomBytes(12);
    const cipher = createCipheriv('aes-256-gcm', dek, iv);
    const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
    const authTag = cipher.getAuthTag();

    // 3. Persist: { wrappedDek, iv, authTag, ciphertext }
    return {
        wrappedDek: Buffer.from(wrappedDek).toString('base64'),
        iv:         iv.toString('base64'),
        authTag:    authTag.toString('base64'),
        ciphertext: ciphertext.toString('base64'),
    };
}

async function decrypt(record) {
    // 1. Unwrap the DEK via KMS
    const { Plaintext: dek } = await kms.send(new DecryptCommand({
        CiphertextBlob: Buffer.from(record.wrappedDek, 'base64'),
    }));

    // 2. Decrypt locally
    const decipher = createDecipheriv('aes-256-gcm', dek, Buffer.from(record.iv, 'base64'));
    decipher.setAuthTag(Buffer.from(record.authTag, 'base64'));
    return Buffer.concat([decipher.update(Buffer.from(record.ciphertext, 'base64')), decipher.final()]);
}

// 3) Encryption context — extra integrity bound to the wrap
await kms.send(new GenerateDataKeyCommand({
    KeyId: KEY_ID,
    KeySpec: 'AES_256',
    EncryptionContext: { tenantId: 't_123', purpose: 'invoice' },
}));

// Decryption requires the SAME context. Stops a leaked wrappedDek from being used
// against a different tenant's record.

// 4) GCP KMS — same idea
import { KeyManagementServiceClient } from '@google-cloud/kms';
const kms = new KeyManagementServiceClient();
const keyName = kms.cryptoKeyPath('project', 'global', 'app-keyring', 'app-key');

const [enc] = await kms.encrypt({ name: keyName, plaintext: dek });
const [dec] = await kms.decrypt({ name: keyName, ciphertext: enc.ciphertext });

// 5) HashiCorp Vault Transit
import vault from 'node-vault';
const client = vault({ endpoint: 'https://vault.example.com', token });
await client.write('transit/encrypt/app-data', { plaintext: Buffer.from('hi').toString('base64') });
await client.write('transit/decrypt/app-data', { ciphertext: 'vault:v1:...' });

// Transit handles BYO key, automatic rotation, key versioning.

// 6) Key rotation
// • AWS KMS: enable automatic rotation (yearly) on a symmetric key
//   Old key versions remain available for decryption; only new encrypts use the new version
// • Manual rotation: create new key, update aliases, re-wrap DEKs over time
// • Vault Transit: vault write -f transit/keys/app-data/rotate
// • Versioned ciphertexts carry the key version — decrypt finds the right version

// 7) IAM scoping
// AWS KMS key policy + IAM identity policy together gate who can:
//   • kms:Encrypt        — generate ciphertext
//   • kms:Decrypt        — read plaintext
//   • kms:GenerateDataKey — start the envelope dance
//   • kms:Sign            — produce signatures (for KMS-backed signing keys)
//
// Least privilege: the app role gets Decrypt + GenerateDataKey but NOT permissions to
// change the key policy or delete the key. A separate admin role does the lifecycle.

{
    "Version":"2012-10-17",
    "Statement":[
        {
            "Sid":"AppCanUse",
            "Effect":"Allow",
            "Principal":{ "AWS":"arn:aws:iam::111122223333:role/app" },
            "Action":["kms:Encrypt","kms:Decrypt","kms:GenerateDataKey"],
            "Resource":"*",
            "Condition":{
                "StringEquals":{ "aws:RequestTag/tenantId":"${aws:PrincipalTag/tenantId}" }
            }
        }
    ]
}

// 8) Multi-region
// • AWS KMS Multi-Region Keys — same key id in multiple regions; encrypt anywhere, decrypt anywhere
// • GCP KMS — automatic regional replication on regional keys
// • Vault — replicate via DR / performance secondaries
//
// Cross-region disaster recovery without re-encrypting data.

// 9) Performance — minimise KMS calls
// • Cache DEKs in memory for the lifetime of one document
// • Reuse the DEK for related records (per-tenant, per-day) — refresh on rotation
// • Pre-generate DEKs in a background pool for write-heavy apps

// 10) BYOK / HYOK (Bring Your Own Key / Hold Your Own Key)
// • BYOK: customer-managed master key imported into the cloud KMS
// • HYOK: customer-managed key in the customer's HSM; cloud calls home to use it (slow, high compliance)
// Choose based on contractual requirements (financial / healthcare).

// 11) Signing
// Many KMS services also expose Sign / Verify for asymmetric keys (RSA, ECDSA, Ed25519).
// Use cases: JWT signing, webhook authentication, code signing.
// The private key never leaves KMS.

await kms.send(new SignCommand({
    KeyId: 'alias/jwt-signing',
    Message: Buffer.from(payload),
    MessageType: 'RAW',
    SigningAlgorithm: 'RSASSA_PSS_SHA_256',
}));

// 12) Auditing
// • CloudTrail / Audit Logs — every KMS call (Encrypt, Decrypt, Sign) logged with caller + context
// • Stream to your SIEM
// • Alert on:
//     • Decrypt calls from unexpected IAM principals
//     • Spike in Decrypt rate (potential data exfil)
//     • Failed KeyPolicy changes (someone trying to escalate)
//     • Region access from outside approved geos

// 13) Disaster recovery + key escape
// • Backup wrappedDek + ciphertext to a different region/account
// • Keep the cloud account containing KMS keys SEPARATE from app accounts
// • Document: 'if KMS region X is down, how do we read encrypted data?'
// • Test the runbook quarterly — restore a sample, decrypt it, verify

// 14) Common bugs / mistakes
// • Using KMS Encrypt for big payloads — limit is 4-8 KB; use envelope encryption
// • Forgetting EncryptionContext — leaked wrappedDek can be reused
// • Reusing IVs with the same DEK — catastrophic for AES-GCM
// • Hard-coded key id in code — use alias; rotate behind the alias
// • KMS calls in a hot loop — cache the DEK or batch operations
// • Cross-region migration without multi-region keys → can't decrypt in DR region
// • Storing wrappedDek WITHOUT version — can't tell which key version to decrypt with after rotation
// • Treating KMS as a substitute for access control — KMS gates ops; combine with IAM policy on the DATA
// • Single KMS account for all envs — dev key compromise affects prod; separate accounts/keys per env

Why it matters

Envelope encryption is the pattern: KMS protects the KEK, your code generates a DEK per record, the DEK encrypts the data locally, and the wrapped DEK lives next to the ciphertext. Pair with EncryptionContext, rotate KEKs yearly, restrict KMS Decrypt via IAM, and audit every call — KMS gates operations, not access to the data behind them.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// AWS KMS / GCP KMS / Azure Key Vault / HashiCorp Vault — managed key custody.
// Apps call "encrypt" / "decrypt" / "sign" without seeing the raw key material.
Try it Yourself »

Discussion

Loading…