Crypto-agility, or: why algorithm choice is the easy part.
Picking the right algorithm is a Tuesday-afternoon decision. Building a system that can swap that algorithm five years from now, without rewriting the application around it, is a multi-year discipline. That discipline is crypto-agility — and it has been the difference between teams that handled MD5, SHA-1, RC4, and TLS 1.0 quietly and teams that handled them as incidents. Here's what crypto-agility actually is, what an agile system looks like in code, and the four habits that make it real.

Atakan Özalan
Co-founder & engineering lead, GOGOGO LLC

There's a moment that every long-lived production system has, and most of them have it more than once. The moment is: we have to change the cryptography. Sometimes it's because a primitive was broken (MD5, SHA-1, RC4). Sometimes it's because a protocol version got too old to defend (TLS 1.0, SSLv3). Sometimes it's because a regulator moved the floor. And — the one coming next — sometimes it's because the cryptographic ground beneath the whole industry shifted, the way post-quantum is shifting it now.
When that moment arrives, every codebase falls cleanly into one of two buckets. Agile codebases change the cryptography in a config file and ship. Inflexible codebases discover that the algorithm name was hard-coded into 400 different places, that ciphertexts in their database have no algorithm tag, that key rotation is a rumour, and that the migration is now a six-month incident. The difference between the two buckets is not 'who picked the right algorithm five years ago.' It's whether the team practiced crypto-agility — and that practice is what this post is about.
What crypto-agility actually is
Crypto-agility is the property of a system that lets you change cryptographic algorithms — hash, cipher, signature, key-exchange — without rewriting the application around them. It is not a library, not a NIST publication, and not a feature you turn on. It's a way of writing software, the same way observable or testable is a way of writing software.
The cleanest one-line definition I've seen: the algorithm should be data, not code. When the algorithm is data, swapping it is a config change and a key rotation. When it's code, swapping it is a refactor of every site that names the algorithm by hand. Whether your system is one or the other is, in my experience, the single largest factor in how painful any future crypto migration will be — including the post-quantum migration.
The history lesson nobody is allowed to skip
In the last 25 years, the cryptography community has formally retired or downgraded: MD5 (1996 weakened, 2004 broken), SHA-1 (2017 collision), RC4 (2015 prohibited in TLS), 3DES (2018 deprecated), TLS 1.0 and 1.1 (2020 deprecated), and is in the middle of moving the whole public-key stack to post-quantum. Six widely-deployed primitives have either died or been put on notice in a single career-length. The next one is on its way. The one after that will be on its way too.
Any system that wants to live for a decade has to assume its current crypto choices are temporary. Crypto-agility is the engineering response to that assumption. Pick the strongest algorithms you can today; design as if you will replace them next year.
What an agile system looks like
Concretely, in a codebase. There is exactly one module that knows the names of cryptographic algorithms — call it crypto/registry. Every other module asks the registry to hash, encrypt, sign, or verify; nothing else in the codebase mentions SHA-256, AES-GCM, Ed25519, or ML-KEM-768 by name. The registry is a map from algorithm IDs to implementations. Adding a new algorithm is: add an entry. Removing an algorithm is: remove an entry. The rest of the application doesn't move.
Every artifact the system produces — every ciphertext, every signature, every wrapped key — carries the ID of the algorithm that made it. A signature isn't <bytes>; it's <algorithm-id, bytes>. A ciphertext isn't a blob; it's an envelope: {alg, kid, iv, ct, tag}. Verifying or decrypting an artifact starts by reading the algorithm ID out of it and asking the registry which implementation to use. The verifier doesn't know which algorithm signed the artifact when the call starts; it learns it from the artifact itself.
The four habits that make agility real
Defining crypto-agility is easy. Practicing it is four habits, all unglamorous.
- Tag every artifact with the algorithm that made it. Every signature, ciphertext, MAC, and wrapped key carries its
alg(and ideally akid— key ID — too). Standard envelopes already exist: JWS / JWE / COSE / JOSE for tokens, and the IETF HPKE / NIST envelope formats for general use. Use one of them; do not invent your own. The single most common reason a migration is painful is that legacy artifacts cannot be told apart from new ones at decode time. - Wrap, never inline. Every cryptographic operation goes through one interface (
registry.sign,registry.verify,registry.encrypt). The interface accepts the algorithm name as data — typically from configuration, key metadata, or the artifact itself. If a developer can grep your codebase for'AES-GCM'and find more than one hit, your system has a leak; that leak is where the next migration will hurt. - Treat rotation as a normal operation, not an emergency. A system that has never rotated a key in production is a system that will fail to rotate one when it matters. Build rotation into the runbook: schedule it quarterly, test it in staging monthly, alert on keys older than a threshold. The day a primitive gets broken should look operationally like a quarterly drill, not a fire.
- Hybrid is the default for transitions. When changing primitives, run the old and the new together long enough that you can roll back without data loss. For signatures, double-sign (old + new) and accept either; for key exchange, derive the session key from both classical and post-quantum halves. Hybrid is not a hack — it's how every careful migration has happened, and every careful one ahead will happen too.
The anti-patterns that get teams later
The mirror image of the four habits is the four patterns that always look fine in the moment and always become bills due later.
- Algorithm names hard-coded across the codebase. The grep for
'SHA-256'returns 70 hits across 30 files. None of them are wrong today. All of them are work later. - Untagged artifacts on disk. Ciphertexts saved without an algorithm envelope, signatures stored as raw bytes. Migrating these requires a separate dance: re-encrypt or re-sign every record under the new primitive, and meanwhile keep the old verifier alive for years.
- Keys that have never rotated. Six-year-old API signing keys, root certificates baked into binaries, JWT secrets that pre-date the team. The first rotation is always the hardest; the team that hasn't done one has not yet learned which assumptions break.
- A single 'algorithm' configuration boolean.
useEcdsa: true. The next migration addsuseMlDsa: true. Two migrations later you have a five-way boolean salad and nobody can answer which path runs in production today. Use named algorithm IDs (and key IDs) — never booleans.
Why this matters more in a multi-agent system
Everything above is true for any long-lived system. It is especially true for the multi-agent systems we build at GOGOGO LLC. A single product has many agents passing signed state to each other, persisting traces that need to remain verifiable for years, and federating with external systems whose crypto choices we don't control. The number of cryptographic seams multiplies. Each seam that hard-codes an algorithm is a future point of failure.
Concretely: when an agent emits a trace that another agent (or another customer's auditor) will verify a year later, that trace must carry not only the signature but the algorithm that produced it. When the message bus between agents authenticates packets, the packet header must carry the algorithm ID. When a customer's eval system grades a months-old agent decision against a fresh signature scheme, the old grade must still verify under the algorithm it was originally signed with. None of this is hard if agility was designed in. All of it is a project if it wasn't.
“Pick the right algorithm today and you've solved a Tuesday-afternoon problem. Build a system that can change its algorithm next year and you've solved every Tuesday afternoon you don't yet know about. Crypto-agility is the second skill, and it pays out across the whole life of the product.”
Where this fits in the post-quantum picture
The reason crypto-agility is the centre of any honest post-quantum migration plan is the same reason it has been the centre of every prior migration. The teams that come out of a primitive transition quietly are the teams whose code didn't notice the swap. That outcome is not luck. It's the result of years of small, unglamorous decisions: tag every artifact, route every operation through one interface, rotate as a drill, run hybrid through every transition. Start those habits now and the post-quantum migration is a config rollout. Start them after the deadline and it's an incident with stakeholders.
The next post in this short series is about what all of this — post-quantum cryptography and crypto-agility — specifically means for systems built out of many agents sharing, signing, and verifying state. If you want a head start, run a grep for the algorithm names in your codebase today and count the hits. That number is your current crypto-agility score. More on how we build at gogogollc.com.