Encrypting stored data creates a second problem that is easy to overlook: the application must keep the right decryption key available for as long as the protected data still needs to be read.

If an application replaces an encryption key and immediately deletes the old one, existing ciphertext may become permanently unreadable. If it never replaces keys, one long-lived key can accumulate more data and more operational exposure than intended. A practical design needs to support both change and continuity.

The useful mental model is: rotation changes the key used for new encryption; it does not automatically change the key required by existing ciphertext. This article explains how to make that distinction explicit, how key identifiers make gradual migration possible, when old keys can be retired, and what rotation does not protect against.

Rotation is a lifecycle change, not a global replacement

Suppose an application encrypts customer records with key K1. The database contains ciphertext created at different times, but every record currently depends on K1.

Later, the application introduces K2.

A naive rotation might look like this:

before: K1 encrypts and decrypts
rotate: delete K1, install K2
after:  K2 encrypts and decrypts

The problem is that ciphertext produced with K1 generally requires K1 for decryption. Installing K2 does not transform old ciphertext into ciphertext under K2.

A safer transition separates two roles:

new encryption: K2
old decryption:  K1 when needed
new decryption:  K2

K1 becomes a retired encryption key: the application stops using it to protect new data, but may retain controlled decryption access while ciphertext that depends on it still exists.

This is the core idea behind practical key rotation.

Record which key protects each ciphertext

Once more than one key can exist, the application needs a reliable way to select the correct key during decryption. Guessing by age, trying every key, or assuming the newest key will work creates unnecessary complexity and fragile failure modes.

Instead, store a non-secret key identifier with the encrypted object. For example:

key_id:     customer-data-2026-09
ciphertext: <encrypted bytes>

The identifier is metadata. It tells the decryption path which managed key to request; it is not the key itself and does not need to be confidential merely because it names a key.

A simplified decryption flow becomes:

record = load_record()
key = key_store.get(record.key_id)
plaintext = decrypt(key, record.ciphertext)

Production encryption formats usually need additional fields required by the chosen authenticated-encryption construction, such as a nonce and authentication tag. Those details must follow the cryptographic library or format being used. The important lifecycle property here is that the ciphertext carries enough trusted metadata to identify its required key version.

Protect the integrity of security-relevant metadata as part of the encryption design. If an attacker can freely alter a key identifier, the application should not respond by silently trying unrelated keys or bypassing authentication failures.

Make one key current for encryption

A key set is easier to reason about when exactly one eligible key is designated for new encryption at a time.

For example:

K1: decrypt only
K2: encrypt + decrypt   <- current

When the application writes new protected data, it obtains K2 and records the corresponding key identifier. When it reads an older object, it follows that object’s identifier and may obtain K1 instead.

This separation matters operationally. Without it, two application instances with different configuration could continue producing new K1 ciphertext after the team believes rotation has completed. The old key would then remain necessary for longer than expected.

The source of truth for the current encryption key should therefore be deliberate and observable. During rollout, verify not merely that K2 exists, but that all intended writers have stopped creating new ciphertext under K1.

Decide whether old data must be re-encrypted

Rotating the write key does not require every existing ciphertext to be rewritten immediately. There are two common transition strategies, and the right choice depends on the data and threat model.

Keep old keys for existing ciphertext

The simplest approach is to let old ciphertext remain under its original key until the data expires, is deleted, or is otherwise rewritten.

This works well when records have short natural lifetimes or when rewriting a large dataset would create more operational risk than benefit. The key store must retain each required decryption key with appropriate access controls until no remaining data depends on it.

The trade-off is that retiring K1 from encryption does not remove the consequences of a later K1 compromise while K1-protected data still exists and the key remains usable for decryption.

Re-encrypt data under the new key

If the goal includes eliminating an old key dependency, existing plaintext must eventually be protected under a new key. Conceptually:

ciphertext(K1)
    -> decrypt with K1
    -> encrypt with K2
    -> store ciphertext(K2) and key_id=K2

This can happen in a controlled batch migration or gradually when records are read or updated. A batch gives a clearer completion point but can create substantial load and a larger failure domain. Lazy migration spreads the work over time but may leave rarely accessed records on the old key indefinitely.

Whichever approach is used, treat re-encryption as a data migration. Make it restartable, monitor failures, and do not destroy K1 merely because a migration job has started.

Retirement requires evidence that the old key is no longer needed

Deleting an old key is different from stopping new encryption with it. Key destruction is an availability-sensitive action because ciphertext that still depends on the key may become unrecoverable.

Before destroying K1, establish that no required data still references it. The evidence might come from a complete inventory, a migration query, object metadata, or another authoritative record appropriate to the storage system.

A useful lifecycle is:

active -> decrypt-only -> no dependencies -> destroy

Do not collapse those stages into one button press.

Backups complicate the decision. A current database may contain no K1 ciphertext while an older backup still contains records protected by K1. If the organization expects that backup to be restorable and readable, it must preserve a protected way to recover the required key for the backup’s retention period, or deliberately rework the backup strategy.

That is a recovery decision, not just a cryptographic one. Test restores should include the key dependencies needed to read protected data.

Rotation after suspected compromise is different

Scheduled rotation and compromise response have different goals.

Routine rotation limits how long a key is used for new protection and creates a tested path for changing keys. If K1 is suspected to have been exposed, however, simply making K2 current does not undo the exposure of data that an attacker could already decrypt with K1.

The response may need to include identifying which data was protected by K1, assessing whether copies of that ciphertext could have been obtained, re-encrypting data that still requires confidentiality, revoking access to the compromised key where the key-management system supports that distinction, and investigating how the key was exposed.

The exact response depends on what the key was used for and what the attacker could access. Rotation reduces future use of the compromised material; it does not erase prior compromise.

Keep key access narrower than data access

Rotation works only if key handling itself has a defensible trust boundary.

An application that can decrypt customer data needs some ability to use the relevant decryption keys. That does not mean every developer, batch job, support tool, or database user should receive raw key material.

Where the platform permits it, give workloads only the key operations they need and keep key administration separate from ordinary data administration. A database backup and a key-store backup should not casually collapse into the same unrestricted access path, because possession of both may defeat the intended separation.

Key identifiers also help here. Application records can refer to managed key versions without embedding secret key bytes in the database.

These controls reduce exposure, but they do not solve an application compromise in which an attacker can legitimately invoke the application’s decryption path. Key rotation is not a substitute for application isolation, authorization, monitoring, or incident response.

Test the transition, not only the steady state

Key rotation often fails at boundaries rather than during normal operation. Tests should therefore cover mixed key versions.

A useful test sequence is:

  1. Encrypt a record with K1.
  2. Make K2 current for new encryption.
  3. Confirm the K1 record still decrypts through its recorded key identifier.
  4. Create a new record and confirm it is protected under K2.
  5. Re-encrypt or expire the K1 record according to the chosen migration strategy.
  6. Confirm no required object still depends on K1 before testing retirement.

Also test failure behavior. An unknown key identifier, unavailable key service, corrupted ciphertext, or authentication failure should produce an explicit error rather than plaintext fallback or silent use of another key.

Operational monitoring should distinguish these cases where practical. A sudden increase in requests for an old key version, for example, can reveal a writer that was not migrated or a workload processing unexpectedly old data.

Avoid rotation rules without a reason

There is no universal rotation interval that fits every encryption key. Appropriate key lifetimes depend on factors such as the key’s purpose, the amount and sensitivity of protected data, the environment in which the key is stored and used, recovery requirements, cryptographic policy, and the cost and reliability of rotation.

The important engineering property is not an arbitrary calendar number. It is having a defined key lifecycle and a rotation mechanism that can be exercised when policy or an incident requires it.

A simpler system may reasonably use one managed key for a bounded dataset when its threat model and platform guidance support that choice. More sensitive or long-lived systems may justify shorter usage periods, versioned keys, stronger separation of key administration, and planned re-encryption. The design should follow the risk rather than treating frequent rotation as automatically better.

Treat key rotation as a data-compatibility problem

The safest way to reason about encryption-key rotation is to stop thinking of a key as a configuration value that can simply be overwritten.

Ciphertext has a dependency on the key that created it. Record that dependency explicitly, use one current key for new encryption, retain old keys only while they are needed, and migrate old ciphertext when the threat model requires the dependency to end.

Then make retirement evidence-based. A key is ready to disappear only when the data and recovery paths that matter no longer require it.

That design turns rotation from a risky replacement event into a controlled lifecycle: introduce a new key, move new writes to it, preserve compatibility, remove old dependencies, and only then retire the old key.