Skip to content
JavaAgentic

Type at least two characters. Try “RAG”, “pgvector” or “tool calling”.

Cryptographic Key Management

Managing keys across their lifecycle: generation, storage in HSMs and KMS, envelope encryption, rotation without re-encrypting everything, and the key hierarchy that makes it work.

Advanced6 min readUpdated
On this page

A key is only as protected as the weakest point in its lifecycle. Strong encryption with a key sitting in a config file is not security — it is obfuscation with extra steps. Key management is the discipline that makes the encryption meaningful.

Key Takeaways

  • The lifecycle — generation, storage, rotation, revocation, destruction — all matters, not just the algorithm.
  • Keys should live in a KMS or HSM, where the application never sees the master key.
  • Envelope encryption keeps bulk data local and makes rotation cheap.
  • A key hierarchy limits the blast radius of any single key compromise.
  • Rotate on a schedule, and design so rotation does not require re-encrypting everything.

The lifecycle

Every stage is a place a key can be exposed. A key management system exists to enforce the whole cycle rather than just the storage.

The stages people skip are distribution and rotation. A key emailed to set up an environment is compromised the moment it is in an inbox; a key that is never rotated means a single past exposure grants access forever.

The key hierarchy

Three tiers limit blast radius. A leaked data key exposes one record; the master key, which could expose everything, never leaves the hardware.

The hierarchy is what bounds a compromise. A leaked data encryption key exposes only the data it protected. The master key — whose exposure would be catastrophic — is generated inside an HSM or KMS and never exists in plaintext outside it.

Envelope encryption with KMS

EnvelopeEncryptionService.java
@Service
public class EnvelopeEncryptionService {
 
    private final KmsClient kms;
    private final String masterKeyId;
 
    public EncryptedRecord encrypt(byte[] plaintext, String context) {
        // KMS generates a data key and returns it twice: plaintext to use now,
        // and encrypted under the master key to store. The master key stays
        // inside KMS throughout.
        var dataKey = kms.generateDataKey(b -> b
                .keyId(masterKeyId)
                .keySpec(DataKeySpec.AES_256)
                // Encryption context is bound into the operation and must match
                // on decrypt — it prevents a data key being used out of context.
                .encryptionContext(Map.of("purpose", context)));
 
        byte[] plaintextKey = dataKey.plaintext().asByteArray();
        try {
            byte[] ciphertext = aesGcm(plaintextKey, plaintext);
            return new EncryptedRecord(ciphertext,
                    dataKey.ciphertextBlob().asByteArray(), context);
        } finally {
            // Wipe the plaintext key from memory the instant it is done with.
            Arrays.fill(plaintextKey, (byte) 0);
        }
    }
 
    public byte[] decrypt(EncryptedRecord record) {
        var decrypted = kms.decrypt(b -> b
                .ciphertextBlob(SdkBytes.fromByteArray(record.encryptedDataKey()))
                .encryptionContext(Map.of("purpose", record.context())));
 
        byte[] plaintextKey = decrypted.plaintext().asByteArray();
        try {
            return aesGcmDecrypt(plaintextKey, record.ciphertext());
        } finally {
            Arrays.fill(plaintextKey, (byte) 0);
        }
    }
}

Two details matter beyond the structure. Encryption context binds the operation to a purpose, so a data key generated for one tenant cannot decrypt another's data even if the ciphertext is swapped. And wiping the plaintext key after use limits its exposure to the moment of the operation rather than the lifetime of the process.

Rotation

The reason envelope encryption is standard is what it does for rotation:

KeyRotation.java
@Scheduled(cron = "0 0 3 1 * *")   // monthly
public void rotateMasterKey() {
    // KMS rotates the master key material. Existing data keys were encrypted
    // under the old version; KMS can still decrypt them because it retains
    // old versions. New data keys use the new version automatically.
    kms.enableKeyRotation(b -> b.keyId(masterKeyId));
    // No data is re-encrypted. Only the master key version advances.
}

Rotating the master key re-wraps nothing — KMS retains old versions to decrypt existing data keys, and new data keys use the new version. The alternative, where the encryption key encrypts data directly, means rotation requires decrypting and re-encrypting every record, which for a large dataset is a migration project you will keep deferring.

For data keys, re-wrapping is cheap because they are small:

RewrapDataKeys.java
// Re-encrypt data keys under a new master version WITHOUT touching the data.
public void rewrapDataKeys(List<EncryptedRecord> records) {
    records.forEach(record -> {
        var rewrapped = kms.reEncrypt(b -> b
                .ciphertextBlob(SdkBytes.fromByteArray(record.encryptedDataKey()))
                .sourceKeyId(oldMasterKeyId)
                .destinationKeyId(newMasterKeyId));
        record.updateEncryptedDataKey(rewrapped.ciphertextBlob().asByteArray());
    });
}

HSMs

An HSM provides hardware-level key protection: keys are generated inside the device, never leave it, and cryptographic operations happen on the device. FIPS 140-2 Level 3 hardware physically resists extraction and zeroes itself on tampering.

Pkcs11.java
// Java accesses an HSM through the PKCS#11 standard interface via a provider.
Provider hsmProvider = Security.getProvider("SunPKCS11").configure("/etc/hsm/pkcs11.cfg");
Security.addProvider(hsmProvider);
 
KeyStore keyStore = KeyStore.getInstance("PKCS11", hsmProvider);
keyStore.load(null, hsmPin.toCharArray());
// The private key handle references a key that never leaves the device.
PrivateKey signingKey = (PrivateKey) keyStore.getKey("signing-key", null);

Use an HSM when a compliance regime mandates it — payment processing under PCI, certain government work — or when the keys protect something valuable enough to justify the cost and the operational weight. AWS CloudHSM and Azure Dedicated HSM offer this as a managed service, which removes the physical operation while keeping the hardware guarantee.

For most applications a cloud KMS is the right default: strong protection, per-use audit logging, automatic rotation, and far less to run.

What to protect and how

Key typeProtectionRotation
Master / KEKHSM or KMS, never exportedYearly, or on suspicion
Data encryption keysEncrypted under the master, stored with dataRe-wrap on master rotation
JWT signing keysKMS or a keystore, published via JWKSQuarterly, overlapping
TLS private keysManaged by cert-manager or the platformWith certificate renewal
API keys / secretsVault, dynamic where possiblePer policy

Every key needs an owner, a documented rotation schedule, and monitoring for use. An unexpected spike in KMS decrypt operations, or use of a key that should be dormant, is a signal worth alerting on.

What to take away

Manage the whole lifecycle, not just the algorithm. Keep master keys in a KMS or HSM where the application never sees them, and use envelope encryption so bulk data stays local and rotation re-wraps small data keys rather than re-encrypting everything. Build a key hierarchy to bound blast radius, rotate on a schedule, and reach for an HSM only when compliance or value demands it.

Frequently Asked Questions

Why not just keep the encryption key in configuration?
Because anyone who can read the configuration can decrypt everything, forever, and there is no way to rotate or audit. A key management system keeps the key where the application never sees it, logs every use, and rotates without exposing the key material — none of which a config value can do.
What is envelope encryption and why is it standard?
You encrypt data with a data key generated per record or per tenant, and encrypt that data key with a master key held in a KMS or HSM. Bulk data never travels to the KMS, so throughput is local, and rotating the master key means re-wrapping small data keys rather than re-encrypting terabytes.
Do I need an HSM?
Only when a compliance regime requires FIPS 140-2 Level 3 hardware protection, or the keys are valuable enough to justify the cost and operational complexity. For most applications a cloud KMS gives strong protection with far less to manage, and it is the right default.

Related tutorials