Applied Cryptography for Developers Module 9 – Data at rest and key management
Key lifecycle, rotation and KMS
The states a key moves through, why ciphertexts name their keys, and what a KMS adds: encryption context, audit logs, resumable rotation and secret scanning.
What you will learn
- Plan a key's life from generation and storage through rotation, revocation and destruction
- Record a key ID and a version in every ciphertext so that keys and algorithms can change
- Use a KMS with an encryption context and read the audit log it leaves
- Run a re-encryption job that can stop and start again without losing a record
- Detect secrets in source code, sample configuration and logs, and say where each belongs
Before you start
What you’ll learn · Before you start
What you will learn
- Plan a key's life from generation and storage through rotation, revocation and destruction
- Record a key ID and a version in every ciphertext so that keys and algorithms can change
- Use a KMS with an encryption context and read the audit log it leaves
- Run a re-encryption job that can stop and start again without losing a record
- Detect secrets in source code, sample configuration and logs, and say where each belongs
Before you start
On this page
A service holds 40 million encrypted rows under a key made three years ago, and the engineer who knew where that key lives has left. If the ciphertexts do not say which key made them, nobody can tell which rows a rotation would touch, so the choice is to re-encrypt everything or to do nothing.
This lesson follows the key lifecycle: The stages of a key's life as NIST SP 800-57 lays them out: generation, activation, use, deactivation, possible compromise, and destruction. Each state allows different uses of the key. from generation to destruction, puts a key ID and a version into every ciphertext so that keys and algorithms can change, and shows what a KMS: A key management service: it generates, stores and uses keys on your behalf through an API, so that key material never leaves it and every use can be logged. adds. A KMS keeps keys where code cannot read them, binds an encryption context to each ciphertext and logs every use.
A key moves through states, and each state allows different uses
NIST SP 800-57 describes the key lifecycle as a few states. A new key is pre-activation: generated but not yet authorised, so it protects nothing. In the active state it may protect new data and open data it protected. When a newer key takes over, it becomes deactivated: it protects nothing new, but it may still open old data until the last copy of that data is gone. A key can also be suspended while a possible exposure is checked: it protects nothing new, and it returns to active or moves on. A key that is exposed, or suspected to be, is compromised: it is revoked and must never protect again. At the end every key is destroyed, and only metadata such as its name and history may stay. Step through one key’s life:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Generated. A key is made by a key store or an HSM from a good random source. In this first state it may prove that it exists, but it may not protect any data yet.
- Active. When the key is authorised, its cryptoperiod begins. It may protect new data, which is its originator-usage period, and it may open the data it protected.
- A newer key takes over. A rotation makes another key the active one. This key becomes deactivated. It protects nothing new, but it still opens old data until the last copy of that data is gone, its recipient-usage period.
- Set aside. A key can be suspended for a time, for instance while a possible exposure is checked. Nothing may be protected with it, and it may return to active or move on.
- Exposed. A key that is exposed, or suspected to be, becomes compromised and is revoked. It must never protect anything again, and everyone who relied on it is told.
- Destroyed. Every state ends in destruction. The key material is erased wherever it was kept, and only metadata such as its name, type and history may stay for audit. A copy that survives undoes it.
A key’s cryptoperiod: The span of time in which a key is authorised for use. It has an originator-usage period, when the key may protect data, and a recipient-usage period, when it may still open what it protected. has two parts: the originator-usage period, when it may protect data, and the recipient-usage period, when it may still open what it protected. SP 800-57 gives order-of-magnitude advice for a data-encryption key: a day or a week when it encrypts large volumes quickly, up to two years for smaller volumes, and a recipient period of no more than three years beyond. Rotation is what ends one key’s originator period and starts the next one’s. The automatic rotation of an AWS KMS key makes new key material every 365 days unless you choose another period.
Key idea
Plan the whole life of a key, from the day it is made to the day it is destroyed, because each state lets it do something different.
Every ciphertext names its key and version, so keys and algorithms can change
A ciphertext that does not name its key can only be opened by trying keys, and trying keys is how rotations go wrong. The fix is a short header in front of every ciphertext: a version, which fixes the algorithm and the layout, and a key ID, which picks the key. The header is not secret, but it must not be changeable, so it goes into the associated data of the AEAD. ASVS 5.0 asks for this agility: requirement 11.2.2 says it must be possible to replace keys and re-encrypt data, and requirement 11.1.2 asks for an inventory of every key, algorithm and certificate in use. The program keeps a key ring with those rules, rotates it, and retires a key twice:
// A key ring with a versioned ciphertext: version byte, key id, nonce and AES-256-GCM output, with the header as associated
// data. A key is active (it encrypts and decrypts), decrypt-only (a newer key took over) or gone. The program rotates, shows
// that old records still open, that a header cannot be altered, that every failure looks the same, and what retiring a key
// does to records that still name it. The key bytes are lab values; production keys come from a KMS.
import { createCipheriv, createDecipheriv, createHmac, randomBytes } from 'node:crypto';
const VERSION = 1;
const fail = () => new Error('cannot decrypt');
class KeyRing {
#keys = new Map(); // id -> { bytes, state }
add(id, bytes) {
this.#keys.set(id, { bytes, state: 'decrypt-only' });
}
activate(id) {
if (!this.#keys.has(id)) throw new Error(`no key ${id}`);
for (const k of this.#keys.values()) if (k.state === 'active') k.state = 'decrypt-only'; // the old active key stops encrypting
this.#keys.get(id).state = 'active';
}
retire(id) {
if (this.#keys.get(id)?.state === 'active') throw new Error('the active key cannot be retired');
this.#keys.delete(id);
}
get activeId() {
return [...this.#keys].find(([, k]) => k.state === 'active')?.[0];
}
encrypt(text, aad = '') {
const id = this.activeId;
const header = Buffer.concat([Buffer.from([VERSION, id.length]), Buffer.from(id)]);
const nonce = randomBytes(12);
const c = createCipheriv('aes-256-gcm', this.#keys.get(id).bytes, nonce);
c.setAAD(Buffer.concat([header, Buffer.from(aad)]));
return Buffer.concat([header, nonce, c.update(text, 'utf8'), c.final(), c.getAuthTag()]);
}
// Every failure is the same error: a short blob, another version, an unknown key, a changed header, a bad tag.
decrypt(blob, aad = '') {
try {
if (blob.length < 2 + 12 + 16 || blob[0] !== VERSION) throw fail();
const idLen = blob[1];
const header = blob.subarray(0, 2 + idLen);
const key = this.#keys.get(blob.subarray(2, 2 + idLen).toString());
if (!key) throw fail();
const d = createDecipheriv('aes-256-gcm', key.bytes, blob.subarray(2 + idLen, 14 + idLen));
d.setAAD(Buffer.concat([header, Buffer.from(aad)]));
d.setAuthTag(blob.subarray(-16));
return Buffer.concat([d.update(blob.subarray(14 + idLen, -16)), d.final()]).toString('utf8');
} catch {
throw fail();
}
}
}
const keyIdOf = (blob) => blob.subarray(2, 2 + blob[1]).toString(); // the header is readable without any key
const lab = (label) => createHmac('sha256', 'msc key ring lab').update(label).digest();
const tryDecrypt = (ring, blob, aad) => {
try {
return ring.decrypt(blob, aad);
} catch (e) {
return e.message;
}
};
const ring = new KeyRing();
ring.add('k1', lab('k1'));
ring.activate('k1');
const rows = ['alice', 'bhavna', 'chitra'].map((name, i) => ({ id: i, blob: ring.encrypt(`note for ${name}`, `row ${i}`) }));
console.log(`k1 active: ${rows.length} records, all name ${[...new Set(rows.map((r) => keyIdOf(r.blob)))].join(', ')} in their header`);
// Rotation: k2 is added and activated. k1 keeps opening what it sealed, and no longer seals.
ring.add('k2', lab('k2'));
ring.activate('k2');
const fresh = { id: 3, blob: ring.encrypt('note for deepak', 'row 3') };
rows.push(fresh);
const byKey = rows.reduce((m, r) => m.set(keyIdOf(r.blob), (m.get(keyIdOf(r.blob)) ?? 0) + 1), new Map());
console.log(`after the rotation, active ${ring.activeId}: records by key ${[...byKey].map(([k, n]) => `${k}=${n}`).join(' ')}; old records still open: ${rows.every((r) => tryDecrypt(ring, r.blob, `row ${r.id}`).startsWith('note for'))}`);
// What the header protects.
const moved = Buffer.from(rows[0].blob);
moved[2] = '2'.charCodeAt(0); // k1 becomes k2 in the header
console.log(`header changed from k1 to k2: ${tryDecrypt(ring, moved, 'row 0')}; another row's associated data: ${tryDecrypt(ring, rows[0].blob, 'row 9')}`);
const unknown = Buffer.from(rows[0].blob);
unknown[3] = '9'.charCodeAt(0); // k1 becomes k9
console.log(`unknown key k9: ${tryDecrypt(ring, unknown, 'row 0')}, a truncated blob: ${tryDecrypt(ring, rows[0].blob.subarray(0, 10), 'row 0')}: the same error each time`);
// Retiring a key that records still name.
try {
ring.retire('k2');
} catch (e) {
console.log(`retire the active key: ${e.message}`);
}
const lost = new KeyRing();
lost.add('k2', lab('k2'));
lost.activate('k2');
console.log(`k1 retired too early: ${rows.filter((r) => tryDecrypt(lost, r.blob, `row ${r.id}`).startsWith('note for')).length} of ${rows.length} records open`);
for (const r of rows.filter((r) => keyIdOf(r.blob) === 'k1')) r.blob = ring.encrypt(ring.decrypt(r.blob, `row ${r.id}`), `row ${r.id}`); // re-encrypt under the active key
ring.retire('k1');
console.log(`k1 retired after re-encrypting its ${byKey.get('k1')} records: ${rows.filter((r) => tryDecrypt(ring, r.blob, `row ${r.id}`).startsWith('note for')).length} of ${rows.length} records open`); Output
k1 active: 3 records, all name k1 in their header after the rotation, active k2: records by key k1=3 k2=1; old records still open: true header changed from k1 to k2: cannot decrypt; another row's associated data: cannot decrypt unknown key k9: cannot decrypt, a truncated blob: cannot decrypt: the same error each time retire the active key: the active key cannot be retired k1 retired too early: 1 of 4 records open k1 retired after re-encrypting its 3 records: 4 of 4 records open
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node keyring.mjs
After the rotation the ring encrypted a new record under k2 while the three k1 records still opened, and every record named its key, so none had to be tried against the other. A header changed from k1 to k2, an unknown key k9, another row’s associated data and a truncated blob all gave the same error. Retiring k1 too early left 1 of 4 records readable, because the three that still named it could not be opened. Re-encrypting those three first left 4 of 4.
Checkpoint
Check: The states of a key · A key ID and version in every ciphertext
What a KMS adds to a key ring
A key ring inside your process still lets any code in that process read the key. Moving the keys out of reach brings in five terms:
- A KMS, a key management service, generates, stores and uses keys on your behalf, so that the key material never leaves it and every use can be logged.
- An HSM: A hardware security module: tamper-resistant hardware that holds keys and does cryptographic operations inside it, so that the keys cannot be read out. Many KMS products run on HSMs. is tamper-resistant hardware that holds keys and does the cryptography inside it; many KMS products run on HSMs.
- An encryption context: Non-secret key and value pairs, such as a tenant id, that a KMS binds to a ciphertext as associated data. The same pairs must be given to decrypt, and the service writes them to its log in clear text. is non-secret data, such as a tenant id, bound to a ciphertext so that it opens only when the same data is presented again.
- An audit log records each use of a key: who called, which operation, which key and context, and whether the call succeeded.
- A secrets manager: A service that stores secrets such as passwords and tokens, gives each application access to only its own, can rotate them, and logs their use. stores and rotates secrets such as passwords and tokens, and hands them to applications when they run.
Production keys belong in a service like this, and the key ring above is the part of your code that talks to it.
A KMS call with an encryption context, and the audit log it leaves Pro: opens with a Pro, Premium or Ultimate pass
A KMS call carries more than a key name: it carries who is asking and a context that must match. This part runs a mock KMS through eight calls and reads the log they leave.
In this part:
- An encryption context binds a ciphertext to the facts of its use
- A mock KMS that refuses the wrong context, with its audit log
- KMS, HSM or secrets manager: what each one holds
1 diagram · 1 runnable example (JavaScript) · about 6 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Rotating at scale: cryptoperiods, a re-encryption job and its numbers Pro: opens with a Pro, Premium or Ultimate pass
Rotation is a project, not a button: choose a period, move the records, and retire the old key only when nothing names it. This part works out the numbers, and runs a job that crashes half way through a batch.
In this part:
- What a managed rotation changes, and what it leaves
- A re-encryption job that survives a crash
- Build the key ring and its job
- A key ring with versioned ciphertexts and a re-encryption job
- Results of the sample tests
1 diagram · 1 runnable example (Python) · 1 exercise with sample tests · about 8 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Keeping secrets out of source, samples and logs: a scanner that finds seven, then none Pro: opens with a Pro, Premium or Ultimate pass
A key that is perfectly managed in a KMS still fails if a copy sits in a repository, a sample file or a log. This part sets the places a secret may live in order, scans a weak repository and a fixed one, and gives the order of a response when a secret leaks.
In this part:
- The storage ladder, from never to a KMS
- A scanner on a weak repository and on the fixed one
- When a secret leaks: the order of the response
1 runnable example (JavaScript) · about 4 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Practise
A practice session asks about the states of a key and the header of a ciphertext, and gives the reason with every answer:
Practice that adapts to you
Practise this lesson
6 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
-
Fresher to mid · Warm-up
Why should every ciphertext carry a version and a key ID, and what does a rotation look like when it does?
A ciphertext is read long after it is written, perhaps by newer code, so it must say how to read it. The version fixes the algorithm and the layout, and the key ID picks the key, so the reader never tries keys in turn. Both go into the associated data, so a changed id makes the tag fail instead of steering the reader. Then a rotation has three steps. Add the new key and make it the active one, so that new data is written under it. Leave the old key in the ring for decrypting only, so that nothing stops opening. Then re-encrypt the old records, counting by key id, and retire the old key only when the count is zero. In the lesson's run, retiring a key while 3 of 4 records still named it left 1 of 4 readable.
-
Senior to staff
An AES key encrypts 40 TB across 200 million objects in object storage, written by many services. Plan its rotation: how new writes switch, how reads keep working, how the re-encryption job is ordered and resumed, what limits you expect, and when the old key goes.
-
Mid to senior
A developer committed a cloud access key to a private repository three days ago. Walk through the response in order, say how you find out what the key did in those three days, and say what you change so that it cannot recur.
-
Senior to staff
Design the encryption contexts for a multi-tenant service that uses a KMS: what goes into a context, what must never go in one, what it lets you enforce in policy, and what the audit log can then answer.
Flashcards · 6 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
What are the states of a key's lifecycle in NIST SP 800-57?
Pre-activation: generated, protects nothing. Active: protects new data and opens old. Suspended: protects nothing new while a possible exposure is checked. Deactivated: protects nothing new but may still open old data. Compromised: exposed or suspected, revoked, never protects again. Destroyed: only metadata such as its name and history may stay.
-
What is a cryptoperiod, and what does SP 800-57 advise for a data-encryption key?
The span in which a key is authorised for use. The originator-usage period is when it may protect data; the recipient-usage period is when it may still open what it protected. Advice: a day or a week when it encrypts large volumes quickly, up to two years for smaller volumes, with a recipient period of no more than three years beyond. AWS KMS automatic rotation makes new key material every 365 days by default.
-
Why put a version and a key ID in the header of every ciphertext?
A ciphertext that does not name its key can only be opened by trying keys, which is how rotations go wrong. The version fixes the algorithm and layout and the key ID picks the key. The header is not secret but must not be changeable, so it goes into the AEAD's associated data. ASVS 5.0 asks for it: 11.2.2 wants keys that can be replaced, with the data re-encrypted, and 11.1.2 a list of all keys, algorithms and certificates in use.
-
What happened in the key ring when k1 was retired too early?
After the rotation, new records used k2 while the three k1 records still opened. Retiring k1 too early left only 1 of 4 records readable, because the three that still named k1 could not be opened; re-encrypting those three first left 4 of 4. A changed header, an unknown key, another row's associated data and a truncated blob all gave the same error.
-
What do a KMS, an HSM, an encryption context and an audit log each add?
A key ring inside your process lets any code in it read the key. A KMS generates, stores and uses keys for you, so key material never leaves it. An HSM is tamper-resistant hardware that does the cryptography inside it. An encryption context is non-secret data, such as a tenant id, bound to a ciphertext so it opens only when presented again. An audit log records who called, which operation, key and context, and whether it succeeded.
-
What does a secrets manager do?
A service that keeps secrets such as passwords and tokens, rotates them, and passes them to applications while they run.
Key takeaways
- A key moves through pre-activation, active, suspended, deactivated, compromised and destroyed states, and each allows different uses, so its whole life is planned on the day it is made.
- A version and a key ID in the authenticated header of every ciphertext let keys and algorithms change: after a rotation old records still opened, and a key retired too early left 1 of 4 records readable.
- A KMS keeps keys out of the application’s reach and adds an encryption context, an audit log and, with an HSM, hardware protection.
Terms in this lesson
- cryptoperiod
- The span of time in which a key is authorised for use. It has an originator-usage period, when the key may protect data, and a recipient-usage period, when it may still open what it protected.
- encryption context
- Non-secret key and value pairs, such as a tenant id, that a KMS binds to a ciphertext as associated data. The same pairs must be given to decrypt, and the service writes them to its log in clear text.
- HSM
- A hardware security module: tamper-resistant hardware that holds keys and does cryptographic operations inside it, so that the keys cannot be read out. Many KMS products run on HSMs.
- key lifecycle
- The stages of a key's life as NIST SP 800-57 lays them out: generation, activation, use, deactivation, possible compromise, and destruction. Each state allows different uses of the key.
- KMS
- A key management service: it generates, stores and uses keys on your behalf through an API, so that key material never leaves it and every use can be logged.
- secrets manager
- A service that stores secrets such as passwords and tokens, gives each application access to only its own, can rotate them, and logs their use.
References
- SP 800-57 Part 1 Rev. 5: Recommendation for Key Management, General (sections 5.3 and 7) (National Institute of Standards and Technology (NIST))
- OWASP Application Security Verification Standard 5.0, V11 Cryptography (requirements 11.1.1, 11.1.2 and 11.2.2) (OWASP Foundation)
- AWS KMS developer guide: Rotate AWS KMS keys (Amazon Web Services)
- AWS KMS developer guide: Encryption context (Amazon Web Services)
- Cloud KMS documentation: Envelope encryption (Google Cloud)
- OWASP Key Management Cheat Sheet (OWASP Foundation)
- OWASP Secrets Management Cheat Sheet (OWASP Foundation)
- OWASP Cryptographic Storage Cheat Sheet (OWASP Foundation)
- Controller of Certifying Authorities, India: eSign (Controller of Certifying Authorities, Government of India)
- Node.js 24 documentation: Crypto (OpenJS Foundation (Node.js))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress