Skip to content
Migration engineering

Implementing crypto-agility: patterns for swappable cryptography

8 min

Also mentioned

RSARSARivest, Shamir and AdlemanA widely used public-key algorithm for encryption and digital signatures whose security relies on the difficulty of factoring large numbers.Read the full entry, ML-KEMML-KEMModule-Lattice-based Key Encapsulation MechanismModule-Lattice-Based Key-Encapsulation Mechanism, the NIST-standardized post-quantum KEM derived from CRYSTALS-Kyber and specified in FIPS 203.Read the full entry, SLH-DSASLH-DSAStateless Hash-based Digital Signature AlgorithmStateless Hash-Based Digital Signature Algorithm, the NIST-standardized signature scheme derived from SPHINCS+ and specified in FIPS 205.Read the full entry, TLSTLSTransport Layer SecurityTransport Layer Security, the protocol that encrypts and authenticates most internet traffic, including HTTPS. It uses key exchange, certificates, and symmetric encryption to protect a session.Read the full entry, CBOMCBOMcryptographic bill of materialsA Cryptographic Bill of Materials, a structured, machine-readable inventory of the cryptographic assets used by a system or application, often expressed as an extension of a software bill of materials (SBOM).Read the full entry are defined in the glossary.

TL;DR

Read this first

Crypto-agility is an engineering property you build from a handful of concrete patterns: negotiate the algorithm at runtime, route every operation through one provider, choose the algorithm from configuration, store a versioned algorithm identifier next to the data, size every field for the largest primitive, and automate rotation with dual-stack support. Apply them and the post-quantum swap, and the one after it, becomes a configuration change rather than a programme of work.

This is the implementation companion to crypto-agility (the concept): that guide argues why agility, not the algorithm swap, is the real deliverable of a post-quantum migration. This one is the how. None of the patterns below are new: they are the mechanisms mature protocols already use to survive algorithm change, but they have to be applied deliberately, because rigidity is what you get by default. Each pattern moves one decision out of hardcoded code and into a place you can change cheaply later.

Negotiate the algorithm at runtime

The most durable pattern is to let the endpoints agree on the algorithm when they connect, rather than fixing it in the wire format. TLS is the model: cipher suites and supported_groups let a client and server pick a mutually supported primitive, the same mechanism that makes the TLS hybrid group deployable without a flag day. Design new protocols with an explicit negotiation step and a version field from day one, even when only one algorithm exists, so the second one is an addition rather than a breaking change.

Put every operation behind one provider

Application code should never name a concrete algorithm. Route all cryptographic operations through a single interface (a provider, in the JCA/JCE or PKCS#11 sense) that takes an abstract request ("establish a shared secret", "sign this") and resolves the actual primitive internally. The algorithm becomes an implementation detail behind a stable boundary, so a swap is one change in one place instead of an edit at every call site. This is also what makes negotiation and configuration enforceable: there is exactly one code path to govern.

Select from configuration and policy

With a provider in place, the choice of primitive belongs in configuration or a policy engine, never in identifiers scattered through the source. Centralising it means a change (a new parameter set, a deprecation, an emergency response to a broken algorithm) is a single edit that propagates everywhere, ideally without a redeploy. Keep the policy versioned alongside your migration roadmap so the running configuration and the plan for what changes next stay in step.

Store self-describing, versioned data

Anything cryptographic that outlives a single session must carry its own algorithm identifier, so you can rotate primitives and still read what you wrote before. This is exactly what X.509 AlgorithmIdentifier, the JOSE/COSE alg header, and message-format version fields already do. Mirror them in your own formats. Two rules make it work in practice:

  • Tag the data, don't infer it. Persist an explicit algorithm-and-parameters identifier next to every key, signature, and ciphertext; never assume the algorithm from context or deployment date.
  • Size for the largest primitive. Never hardcode key, signature, or ciphertext lengths: post-quantum outputs dwarf classical ones, and SLH-DSA's large signatures can run to tens of kilobytes. Use length-prefixed or variable-length fields in schemas, columns, and buffers.
Pitfall

Agility is measured by the next change, not this one

The common failure is treating post-quantum as a one-time swap: ripping out RSA and hardcoding ML-KEM into exactly the same fixed fields and inline calls. You spend a multi-year effort and rebuild the same rigidity, so the next transition costs just as much. Judge every pattern here by one test: how cheap does it make the change after this one? If the answer is "another migration programme," you have swapped the algorithm but not built agility.

Rotate continuously and run dual-stack

Agility only holds if you exercise it. Automate key and certificate rotation so changing a primitive is a routine, tested operation rather than a rare manual event. A rotation path that is never used is a rotation path that will fail when you need it. And support running old and new algorithms at once: hybrid key exchange, dual signatures, or parallel certificate chains let you cut over gradually and roll back safely, instead of forcing every endpoint to upgrade in lockstep.

Retrofitting agility into something already shipping

These patterns are cheap to state and hard to retrofit, because the rigidity is spread across code, schemas, and protocols that already ship. We start with an audit that produces a machine-readable inventory, a CBOM, of where cryptography is hardcoded, pinned, or size-locked, so you can find and track what still needs to change. We turn it into a migration roadmap that fixes the boundaries: negotiation, provider layers, config-driven selection, versioned formats, so the post-quantum swap and every swap after it is a configuration change. And we run training so your engineers reach for these patterns by default.

Frequently asked questions

How is this different from the crypto-agility concept guide?

The concept guide explains what crypto-agility is and argues that building it, rather than the one-time algorithm swap, is the real goal of a post-quantum migration. This guide is the implementation companion: the concrete engineering patterns (runtime negotiation, a provider abstraction, config-driven selection, self-describing versioned formats, size-agnostic storage, automated rotation and dual-stack) that make an algorithm a replaceable component in a real system.

What is the single most important pattern to start with?

Route every cryptographic operation through one provider interface so application code never names a concrete algorithm. It is the foundation the other patterns build on: with a single code path, you can add runtime negotiation, move the choice into configuration, and enforce policy centrally. Without it, the algorithm is referenced in dozens of call sites and every other pattern has to be applied dozens of times.

Why do fixed-size fields matter so much for post-quantum work?

Post-quantum keys, ciphertexts, and especially signatures are far larger than their classical equivalents: SLH-DSA signatures can reach tens of kilobytes against a few hundred bytes for ECDSA. Any database column, wire field, or buffer sized to a classical primitive breaks the moment a post-quantum one arrives. Using length-prefixed or variable-length fields, and never hardcoding lengths, keeps storage and protocols agnostic to which algorithm produced the bytes.

How do self-describing formats enable algorithm rotation?

If every key, signature, and ciphertext carries an explicit algorithm-and-parameters identifier, as X.509 AlgorithmIdentifier and the JOSE/COSE alg header already do, you can introduce a new primitive while still reading everything produced under the old one. Rotation becomes additive rather than a breaking change, because the reader dispatches on the stored identifier instead of assuming a single fixed algorithm across all data.

Take the next step

Related reading

References

Get started

Turn quantum risk into a credential.

Book a discovery call and get an indicative scope and pricing for your organisation.