Chapter 26: Crypto agility
Chapter 25 built the inventory artifact: a CycloneDX 1.6 CBOM over a five-touchpoint Python application. Chapter 26 is the architectural discipline that uses it. A CBOM tells a team which algorithms are deployed where; crypto agility tells the team how to replace each of them without rearchitecting the surrounding code. RFC 7696 (Housley, 2015) defines algorithm agility as a property of a protocol that “can easily migrate from one algorithm suite to another more desirable one, over time”. NIST Cybersecurity White Paper (CSWP) 39 (Barker et al., 2025) extends the definition to full systems, naming the capabilities needed to replace and adapt cryptographic algorithms “in protocols, applications, software, hardware, firmware, and infrastructures while preserving security and ongoing operations”.
The chapter walks six architectural areas where the agile-or-brittle question has to be decided separately. The six-area split is an Encryptorium synthesis, not a taxonomy claimed by any single standards document. Each area is sourced to its primary reference. The running example reuses the four application-layer Ch 25 touchpoints (tls_endpoint_api, jwt_signing, password_hashing, webhook_hmac) so the chapter stays concrete without introducing a new application. The fifth Ch 25 touchpoint, blockchain_validator_sig, is anchored once in the Protocol-level adaptability section, where the smart-contract immutability constraint specifically applies.
A JWT signer that cannot move
Section titled “A JWT signer that cannot move”The jwt_signing touchpoint from Ch 25 is a short signing path. Ch 25 inventoried it as RS256. Block 1 substitutes HMAC-SHA256 so the block stays standard-library only (CPython ships no RSA), and the agility boundary it draws is the same for either primitive. Exercise 2 puts an asymmetric family back. The first version hard-codes the algorithm in every call site. The second version stores the algorithm as an identifier, routes through a registry, and reads the identifier back out of the token header at verification time. Block 1 shows both versions and signs the same payload under each.
# Block 1: brittle vs agile JWT signer, stdlib only.import base64, hashlib, hmac, json
SECRET = b"pedagogical-secret-bytes-only"
def b64url(data): return base64.urlsafe_b64encode(data).rstrip(b"=").decode()
# --- brittle: HS256 hard-coded everywhere ---def sign_brittle(payload): header = b64url(b'{"typ":"JWT"}') body = b64url(json.dumps(payload).encode()) signed = f"{header}.{body}".encode() sig = hmac.new(SECRET, signed, hashlib.sha256).digest() return f"{header}.{body}.{b64url(sig)}"
def verify_brittle(token): header, body, sig_in = token.split(".") signed = f"{header}.{body}".encode() expected = b64url(hmac.new(SECRET, signed, hashlib.sha256).digest()) return hmac.compare_digest(sig_in, expected)
# --- agile: identifier registry, header-driven verification ---REGISTRY = { "HS256": (hashlib.sha256, False), "HS384": (hashlib.sha384, False), "HS512": (hashlib.sha512, False), "HS1": (hashlib.sha1, True), # True = deprecated}
def sign_agile(payload, alg): hash_fn, deprecated = REGISTRY[alg] if deprecated: raise ValueError(f"algorithm {alg} is deprecated") header = b64url(json.dumps({"typ": "JWT", "alg": alg}).encode()) body = b64url(json.dumps(payload).encode()) signed = f"{header}.{body}".encode() sig = hmac.new(SECRET, signed, hash_fn).digest() return f"{header}.{body}.{b64url(sig)}"
def verify_agile(token): header, body, sig_in = token.split(".") alg = json.loads(base64.urlsafe_b64decode(header + "==="))["alg"] if alg not in REGISTRY: return False hash_fn, deprecated = REGISTRY[alg] if deprecated: return False signed = f"{header}.{body}".encode() expected = b64url(hmac.new(SECRET, signed, hash_fn).digest()) return hmac.compare_digest(sig_in, expected)
payload = {"sub": "user-42"}brittle = sign_brittle(payload)agile256 = sign_agile(payload, "HS256")agile512 = sign_agile(payload, "HS512")print(verify_brittle(brittle), verify_agile(agile256), verify_agile(agile512))# ==> True True TrueEvery Python block this chapter prints is also a standalone file in the companion repository, under chapter-code/ch26/, one file per block. Appendix C covers the clone and the environment they run on.
The brittle version emits no alg header at all, so a migration-audit tool reading on-wire tokens has no way to know which hash function signed them. The agile version carries the identifier in the header, so the same tool counts HS256 uses without reading source. Adding HS512 is a registry edit, not a change to the signer or verifier. Retiring HS1 is a deprecation flag. Both signing and verification refuse the algorithm without a caller change.
The brittle version is deliberately a JWT-like pedagogical token rather than a standards-compliant JWS: RFC 7515 §4.1.1 marks the alg Header Parameter as MUST be present (Jones et al., 2015), so the brittle object would be rejected by a conforming verifier. Omitting alg makes the observability cost visible. A production verifier needs more than the agile version’s header dispatch. RFC 8725 §3.1 requires libraries to (a) accept only a caller-specified algorithm set, (b) check that the header alg matches the operation performed, and (c) use each key with exactly one algorithm, checked at the point the operation runs (Sheffer et al., 2020). Block 1 demonstrates identifier-driven dispatch only. Production JWT validation also needs key-id lookup, issuer and audience checks, expiry checks, and a key-to-algorithm binding.
Figure 26.1: the agility boundary. The mutable primitive is on one side of the boundary; the stable identifier and parameter-handling code is on the other.
jwt_signing touchpoint, drawn as the production-architecture shape rather than the exact Block 1 code. The primitive on the left can be replaced without touching the stable components on the right. Block 1 covers the identifier-registry edge. The key-id-lookup edge is a JWKS layer that production code is expected to add.Math preliminaries: identifiers, MTI, deprecation signaling
Section titled “Math preliminaries: identifiers, MTI, deprecation signaling”RFC 7696 §2.1 requires that a protocol have “a mechanism to identify the algorithm or suite that is being used” (Housley, 2015). The identifier can live in the protocol itself (the JWT alg header, the TLS CipherSuite codepoint, the X.509 AlgorithmIdentifier OID) or in a management plane. RFC 7696 §2.1 recommends IANA registries. Once an identifier is added to a registry it “should not be changed or removed” but may be marked as deprecated.
An identifier in isolation is not enough. RFC 7696 §2.2 requires that IETF protocols employing cryptography “specify one or more strong mandatory-to-implement algorithms or suites” (Housley, 2015). For a protocol embedded in another, §2.2.1 places that choice in the enclosing system-level specification: S/MIME specifies the mandatory-to-implement algorithms for its use of CMS, and CMS does not. The mandatory-to-implement (MTI) set is the floor every conforming implementation agrees on. Optional algorithms extend the set without fragmenting it. The guidance in §2.2 is to keep the MTI set small and to treat it as something that “will necessarily change over time”. The MTI set is the knob a standards body uses to push an ecosystem through a migration.
Identifiers plus MTI give a protocol the shape of a menu. Deprecation signaling tells an implementation when to stop serving an item on the menu. NIST SP 800-131A Rev. 2 codifies four approval statuses, with explicit cutover dates (Barker & Roginsky, 2019).
| Status | What the document says it permits |
|---|---|
| Acceptable | Use, with no security risk currently known |
| Deprecated | Use, with the user accepting some security risk |
| Legacy use | Processing already-protected data only |
| Disallowed | No use for applying cryptographic protection |
A status attaches to an algorithm, a key length, and an operation together, not to an algorithm alone. That is what lets two of these hold for one primitive at once: two-key TDEA encryption is disallowed while two-key TDEA decryption is legacy use, so an implementation stops issuing under it without stranding what it already issued (Barker & Roginsky, 2019). A well-designed registry carries the approval status alongside the identifier, as REGISTRY["HS1"] does in Block 1.
Namespaced identifiers package the algorithm family, parameter set, and mode into a single string. Block 2 parses a small example.
# Block 2: a namespaced identifier parser.def parse_algid(algid): parts = algid.split("/") primitive = parts[0] params = {} for kv in parts[1:]: k, _, v = kv.partition("=") params[k] = v if v else True return primitive, params
print(parse_algid("RSA-PSS/SHA-256/salt=32"))print(parse_algid("ML-DSA-65"))print(parse_algid("HMAC-SHA256/deprecated"))# ==> ('RSA-PSS', {'SHA-256': True, 'salt': '32'})# ==> ('ML-DSA-65', {})# ==> ('HMAC-SHA256', {'deprecated': True})The parser separates the primitive (RSA-PSS, ML-DSA-65, HMAC-SHA256) from a flag-and-value parameter set. The TLS SignatureScheme codepoints are tighter than this; the CycloneDX 1.6 parameterSetIdentifier field that Ch 25 used is looser. Both follow the same principle: the identifier carries enough to route a verifier without the caller having to know which primitive to call.
Six architectural areas
Section titled “Six architectural areas”The six areas below are an Encryptorium synthesis across RFC 7696, CSWP 39, and NIST IR 8547 (Moody et al., 2024). CSWP 39 covers protocol-level mechanisms in Section 3, system implementations in Section 4, and strategic planning in Section 5 (Barker et al., 2025). The six-area split cuts across those three sections rather than following CSWP 39’s own outline. Each area opens by naming its primary source and identifies the Ch 25 touchpoint that most clearly illustrates it.
Cryptographic observability
Section titled “Cryptographic observability”Primary sources: NIST IR 8547 (Moody et al., 2024) and CycloneDX CBOM (OWASP CycloneDX, 2025). Ch 25 touchpoint: tls_endpoint_api.
Ch 25 handed the inventory back as a CycloneDX 1.6 document with bom-ref and encryptorium:quantum-status on every entry. Agility makes the observability continuous. The agile pattern regenerates the CBOM from live configuration on every release and treats a diff in the encryptorium:quantum-status column as a change-management event. The brittle pattern inventories once at the start of a migration and then never updates the artifact.
The TLS endpoint illustrates what is hardest to observe. Edge termination, keys held in a hardware security module (HSM), and vendor-managed PKI all push algorithm choice outside the application’s source tree. Ch 25 discussed these as out-of-band cryptography under “what makes an inventory wrong”. The agility consequence is that observability has to reach into the HSM catalogue and the load-balancer configuration, not only the application repository.
Governance and policy
Section titled “Governance and policy”Primary sources: NIST CSWP 39 §5.1—5.2 (Barker et al., 2025), NSA CNSA 2.0 (US National Security Agency, 2022), NCSC 2025 (UK National Cyber Security Centre, 2025). Ch 25 touchpoint: password_hashing.
CSWP 39 §5.1 requires a crypto-agility effort to consider the effects of standards, regulations, and mandates on algorithm transitions. It names none of the instruments sorted below, so the sorting is an Encryptorium synthesis. FIPS 203, 204, and 205 are NIST standards. CNSA 2.0 is NSA policy for U.S. National Security Systems, not a regulation. The Health Insurance Portability and Accountability Act (HIPAA) Security Rule is a U.S. federal regulation binding covered entities and business associates. The Payment Card Industry Data Security Standard (PCI DSS) is an industry standard enforced by contract, and the NCSC migration timelines are national guidance.
CSWP 39 §5.2 covers enforcement: a security policy that names mandatory-to-implement algorithms, disallows vulnerable algorithms in a timely fashion, and is translated into machine-consumable configuration that automated tools can deploy. NSA CNSA 2.0 pins specific parameter choices for National Security Systems: ML-KEM-1024, ML-DSA-87, and LMS or XMSS for firmware and software signing (US National Security Agency, 2022). NCSC 2025 names 2028 as the discovery-and-planning checkpoint, 2031 for the “early, highest-priority PQC migration activities” milestone, and 2035 as the end-of-migration deadline (UK National Cyber Security Centre, 2025).
The agile pattern names an owner for each approved-algorithm list, publishes the list with explicit effective and expiry dates, and uses the expiry dates to gate CI checks that confirm no unapproved identifier has reached the deployed fleet. The brittle pattern leaves the approved-algorithm list implicit in code review practice, with no record of who made the choice or when it expires.
The password_hashing touchpoint shows the governance question in miniature. PBKDF2 iteration counts have to rise as attacker hardware improves, which means the count is a tunable parameter rather than a fixed choice. Whose decision is it to raise the count from 600,000 to 1,200,000, when does that decision happen, and what forces the rollout across the user table? Without explicit governance, the answer is “whoever remembered”, which scales to “no-one, ever”.
Modular cryptographic architecture
Section titled “Modular cryptographic architecture”Primary sources: RFC 7696 §2.1 (Housley, 2015), CSWP 39 §3.1 and §4 (Barker et al., 2025). Ch 25 touchpoint: webhook_hmac.
The agile pattern routes every cryptographic call through a registry keyed by algorithm identifier, stores the identifier with the artifact (ciphertext, signature, key), and reads it back at verification. CSWP 39 §3.1 specifies algorithm identification as the first protocol-level requirement. CSWP 39 §4 covers the same question at the system level, across six subsections. Three of them are the layers where the decoupling can live in an application deployment: a crypto-library API (§4.1), an OS-kernel API (§4.2), or a service-mesh surface in a cloud-native environment (§4.3). The other three take the question to embedded systems (§4.4), hardware (§4.5), and a crypto gateway fronting a legacy system (§4.6).
The webhook_hmac touchpoint is where the brittle pattern is easiest to fall into, because the agile version has the least to buy. A webhook signer that calls hmac.new(key, body, hashlib.sha256) directly in the handler is three lines of code; a registry-based version is ten. The tradeoff is not about the cost of the ten lines; it is about the cost of the ten lines times every handler times every minor version where an SHA-256 reference is spread across the codebase.
Block 3 elaborates the Block 1 registry with the SP 800-131A four-state vocabulary and a policy selector keyed by Ch 25 touchpoint name. Each POLICY entry names the touchpoint the registry serves. The sign guard refuses both disallowed and deprecated at call time.
# Block 3: a registry with the SP 800-131A four-state vocabulary.import hashlib, hmac
REGISTRY = { "HMAC-MD5": {"hash": hashlib.md5, "state": "disallowed"}, "HMAC-SHA1": {"hash": hashlib.sha1, "state": "deprecated"}, "HMAC-SHA256": {"hash": hashlib.sha256, "state": "acceptable"}, "HMAC-SHA512": {"hash": hashlib.sha512, "state": "acceptable"},}
POLICY = { "webhook_hmac": "HMAC-SHA256", "internal_bus": "HMAC-SHA512", "legacy_connector": "HMAC-SHA1",}
def sign(touchpoint, key, body): alg = POLICY[touchpoint] entry = REGISTRY[alg] if entry["state"] in ("disallowed", "deprecated"): raise ValueError(f"{alg} is {entry['state']}") return alg, hmac.new(key, body, entry["hash"]).digest()
alg, sig = sign("webhook_hmac", b"key", b"body")print(alg, len(sig))try: sign("legacy_connector", b"key", b"body")except ValueError as e: # expected: policy rejects HMAC-SHA1 (deprecated) print(e)# ==> HMAC-SHA256 32# ==> HMAC-SHA1 is deprecatedThe same pattern applies at the protocol layer, where identifiers are codepoints rather than strings, and at the system layer, where the registry is a crypto API rather than a Python dict. The shape is the same: the caller passes a touchpoint (or connection, or policy), and the registry returns the primitive.
Protocol-level adaptability
Section titled “Protocol-level adaptability”Primary sources: RFC 7696 §2.4 and §2.6 (Housley, 2015), CSWP 39 §3.2 (Barker et al., 2025), RFC 9846 (Rescorla, 2026), RFC 9370 (Tjhai et al., 2023). Ch 25 touchpoints: tls_endpoint_api, blockchain_validator_sig.
RFC 9846 (TLS 1.3) defines the negotiation machinery: cipher suites, the supported_groups and signature_algorithms extensions, and key_share (Rescorla, 2026). The PQ and hybrid codepoints layered on top of it come from separate IETF specifications. RFC 10024 defines X25519MLKEM768 and the SecP256r1MLKEM768 / SecP384r1MLKEM1024 hybrids (Kwiatkowski et al., 2026). They are not part of RFC 9846 itself.
RFC 9370 extends IKEv2 with seven additional key exchange transform types named ADDKE1 through ADDKE7 (Tjhai et al., 2023). A single SA setup can therefore layer up to seven additional key exchanges over the standard Transform Type 4 KEX. In a PQC migration, those additional exchanges can carry one or more post-quantum mechanisms alongside classical (EC)DH, and the final shared secret is intended to remain secure if any one of the combined exchanges remains secure. That is how IPsec deployments carry a classical (EC)DH exchange alongside one or more post-quantum KEMs without a protocol redesign. CSWP 39 §3.2.1 makes the same point from the protocol-designer side: preserving interoperability during a transition requires the protocol to accept both the old and the new algorithm for a defined window.
RFC 7696 §2.4 adds the defensive constraint: cryptographic algorithm negotiation “SHOULD be integrity protected” (Housley, 2015). Unprotected negotiation is what the downgrade-attacks entry in “How agility fails in practice” shows in miniature. CSWP 39 §3.2.3 names the same requirement as integrity for algorithm negotiation.
The tls_endpoint_api entry from Ch 25 exposes both agile and brittle elements. The agile element is that TLS 1.3 is a negotiated and extensible protocol, so the endpoint can adopt ML-KEM hybrid key-exchange through new supported_groups and key_share codepoints (defined in RFC 10024) and library support, without a new TLS version. The brittle element is the pinned negotiation list behind the endpoint (an operator who fixed ssl_ecdh_curve to a single classical group in the load balancer, or a TLS library too old to know the hybrid codepoint), which flattens the agility to whatever the operator rebuilds.
Some protocols rule out in-place algorithm changes by design. Ethereum Virtual Machine (EVM) precompiles, the consensus protocol, and the signature scheme accepted by validator clients are fixed at the network layer. A deployed smart contract’s bytecode at a fixed address is similarly immutable, modulo proxy patterns that delegate to a swappable implementation contract. A network-wide cryptographic upgrade lands through a coordinated hard fork, not through a registry rewrite. The hard-fork cadence is the operator decision: which clients ship the upgrade in which release, which fork-activation block the network agrees on, which fallback path applies if the upgrade misses its activation. The blockchain_validator_sig touchpoint added to Ch 25 anchors this case: algorithm agility at the protocol layer is bounded by the operator’s deployment artifact, which here is the validator-client release. Chapter 41 walks the governance machinery and three case studies of coordinating such an upgrade: a Bitcoin soft-fork proposal through the BIP process, an Ethereum hard fork through the All Core Devs forum, and a bridge upgrade.
Operational tooling
Section titled “Operational tooling”Primary sources: NIST CSWP 39 §4.3 and §5.3 (Barker et al., 2025). Ch 25 touchpoint: jwt_signing.
Certificate lifecycle managers, key rotation automation, CI/CD cryptographic linters, and dependency scanners all belong in this layer. CSWP 39 §4.3 covers service meshes as a cross-cutting place to centralize cryptographic policy. CSWP 39 §5.3 covers the supply-chain dimension, where the tooling has to reach upstream into vendor-provided components.
The agile pattern integrates cryptographic posture checks into CI. A lint fails a build when a pull request introduces a hard-coded "SHA-1" or an unrotated private key. A nightly job regenerates the CBOM and diffs it against last night’s. The brittle pattern treats algorithm choice as a code-review question with no automated check.
The jwt_signing touchpoint shows the operational surface concretely. Where does the signing key live? How often is it rotated? Is the public key distributed over a JSON Web Key Set (JWKS) endpoint that supports key-id-based rollover, or is it a single static key that requires redeployment to change? The algorithm identifier machinery from the modular architecture area is necessary. Without the JWKS rotation machinery, an algorithm change still requires a simultaneous all-verifier redeploy.
Lifecycle management
Section titled “Lifecycle management”Primary sources: NIST CSWP 39 §3.2.2 (Barker et al., 2025), NIST SP 800-131A Rev. 2 (Barker & Roginsky, 2019). Ch 25 touchpoint: password_hashing.
CSWP 39 §3.2.2 covers “providing notices of expected changes”, which is the mechanism by which a protocol designer signals to implementers that an algorithm is heading toward deprecated, legacy-use, or disallowed. NIST SP 800-131A Rev. 2 gives the transition schedule: the four approval-status categories with explicit cutover dates for each algorithm and key-size pair (Barker & Roginsky, 2019). CSWP 39 §2.1 (“Long Period for a Transition”) documents that the Triple-DES-to-AES transition took 23 years between AES standardization (2001) and three-key Triple-DES being disallowed for applying cryptographic protection after December 31, 2023 (SP 800-131A Rev. 2, though CSWP 39 §2.1 describes the same disallowance as completing in 2024) (Barker et al., 2025) (Barker & Roginsky, 2019).
The agile pattern writes the rotation cadence into the registry (the state field in Block 3), ships a rotation runbook that can be exercised on schedule, and records each rotation against the CBOM. The brittle pattern rotates on a fire-drill schedule, driven by CVEs rather than by a plan.
The password_hashing touchpoint is the subtlest lifecycle case because the primitive output lives inside a password hash stored per user. Rotating PBKDF2 iteration counts cannot force every user to log in and re-derive the hash. The agile pattern carries a per-row iteration count and a target iteration count, and migrates each row on the user’s next successful login. The brittle pattern either freezes the iteration count at first deployment or forces a global password reset.
Figure 26.2: the six-areas map overlaid on the four application-layer Ch 25 touchpoints. A compact matrix with the six architectural areas as rows and the four application-layer Ch 25 touchpoints as columns. Shaded cells indicate areas that dominate each touchpoint’s agility posture. The fifth Ch 25 touchpoint, blockchain_validator_sig, is omitted from this matrix. It is treated in the Protocol-level adaptability section above.
How agility fails in practice
Section titled “How agility fails in practice”Four architectural failure modes. Each corresponds to one of the six areas above where the brittle pattern was chosen. Each has a named example so the failure is concrete rather than generic.
Downgrade attacks. RFC 7696 §2.4 recommends integrity protection for algorithm negotiation for exactly this reason, at SHOULD strength rather than MUST (Housley, 2015). The POODLE attack (2014, CVE-2014-3566) used an unprotected SSL 3.0 fallback path to force a client and server to negotiate a broken CBC-mode cipher when the modern TLS handshake was available. FREAK (2015, CVE-2015-0204 for OpenSSL) was a related downgrade to 512-bit export-grade RSA. In both cases the application code was modular enough to support the stronger cipher; the protocol design permitted the negotiation to be forced downward. Protocol-level adaptability and downgrade resistance are the same architectural property approached from two directions.
Parameter spoofing. When the algorithm identifier is trusted without integrity check, an attacker can change it. The JWT alg: none family of bypasses (Auth0’s March 2015 write-up “Critical vulnerabilities in JSON Web Token libraries”) turned every library that honored the header without cross-checking against a policy into an acceptance oracle for unsigned tokens. A related class is algorithm confusion, where a library accepts an HS-signed token against a key registered for RS verification (CVE-2015-9235 in jsonwebtoken). CSWP 39 §3.2.3 recommends the same thing in its own words: the mechanism a protocol uses to negotiate algorithms should include integrity protection (Barker et al., 2025). The same principle applies at the token level. The Block 1 agile verifier resists the alg: none and unknown-algorithm classes by consulting the registry. Defending against HS/RS confusion also requires binding each key record to its algorithm family, which Block 1’s single-secret design does not cover. Exercise 2 is the natural place to add the binding alongside Ed25519.
Policy drift. An approved-algorithm list that is not mechanically enforced drifts away from the deployed fleet. A governance policy that says “no SHA-1 after 2020” is meaningful only if some tool can confirm that no service is using SHA-1 today. CSWP 39 §5.2 names policy enforcement as a strategic-plan question (Barker et al., 2025). The mitigation is the operational tooling area: a CI check that fails a build when an unapproved algorithm identifier appears, and a CBOM diff that names entries whose quantum-status column changed.
HSM lock-in. An HSM centralizes key handling but also centralizes the algorithm menu. An HSM that exposes RSA-2048 and ECDSA-P-256 through its management API gives the application a narrow set of choices regardless of what its registry supports. Migration to a post-quantum signing algorithm requires an HSM vendor firmware update, a new key-generation ceremony, and in some cases a replacement HSM. The failure mode is not an attack. It is that the migration schedule is now set by the HSM vendor, not by the organization’s own plan. The observability and governance areas need to name the HSM boundary explicitly so the vendor’s roadmap is tracked as an agility input.
Tradeoffs: what agility buys and what it costs
Section titled “Tradeoffs: what agility buys and what it costs”The table names the agile and brittle pattern for each area, and names the Ch 25 touchpoint that most clearly illustrates each. Brittle patterns are not always wrong.
| Area | Agile pattern | Brittle pattern | Ch 25 touchpoint |
|---|---|---|---|
| Observability | CBOM regenerated each release; diff on quantum-status | One-off inventory document | tls_endpoint_api |
| Governance | Owned approved-algorithm list with effective / expiry dates | Algorithm choice implicit in code review | password_hashing |
| Modular architecture | Registry-keyed cryptographic API; identifier stored with artifact | Algorithm hard-coded at every call site | webhook_hmac |
| Protocol adaptability | TLS 1.3 negotiation; RFC 9370 hybrid KEX | Pinned cipher suite; single-algorithm protocol | tls_endpoint_api |
| Operational tooling | CI crypto lint; nightly CBOM diff; JWKS-based key rotation | Code-review-only checks; static JWKs | jwt_signing |
| Lifecycle management | Per-artifact parameter tags with planned rotation cadence | Rotation driven by fire drills | password_hashing |
Not every system needs agility in every area. RFC 7696 §3.2 is titled “Too Many Choices Can Be Harmful” (Housley, 2015). An unbounded cipher-suite menu hurts interoperability and confuses implementers. For a long-lived system whose threat model includes the 2035 NCSC deadline or a comparable national deadline, the engineering investment is justified. For a throwaway script whose keys expire within a week, agility is overhead. Ch 25’s inventory is the artifact that tells a team which systems fall on which side of the line.
The minimum viable agility for a long-lived system is narrower than “support every algorithm”. The required pieces are five. Know where cryptography is used (observability). Store identifiers with the artifacts they protect (modular architecture). Bind identifiers to a written policy with effective and expiry dates (governance). Rotate keys without redeploying every verifier (operational tooling). Run a dated deprecation path that does not strand already-protected data (lifecycle management). RFC 7696 makes the same point in the negative direction. An identifier mechanism alone is not enough: the people maintaining implementations and operating services still have to develop, deploy and adjust configuration settings for the newer algorithms, and to deprecate or disable the older ones (Housley, 2015, sec. 1). Its §2.6 records how reluctant implementers and administrators are to take that last step.
Hybrid schemes (Ch 27) are one specific protocol-level agility mechanism, not the whole of agility. RFC 9370 and CSWP 39 §3.2.4 treat hybrids as a transition-period mechanism. Agility is the architectural property that makes hybrids one choice among several rather than the only path.
Exercises
Section titled “Exercises”-
Pick a service or open-source project you know well. Walk it through the six architectural areas (observability, governance, modular architecture, protocol adaptability, operational tooling, lifecycle management). Produce a six-row table: for each area, name one concrete brittle point and one agile element already present. Cite the algorithm identifiers the system uses (TLS cipher suites, JWT
algvalues, OIDs) explicitly. -
Extend the Block 1 registry to cover Ed25519 in addition to the HMAC family. The signing and verification paths need separate key material and separate primitive calls, so the registry entry now has to carry a family identifier alongside the hash function. Sign a test payload under
HS256andEd25519and verify that the header-driven selector picks the right verifier for each. A standard-library Ed25519 ships with Chapter 27:ed25519_signanded25519_verifyin thehybridpackage undersolutions/ch27-hybrid. -
Extend the Ch 25 CBOM generator. Add an
encryptorium:agility-statusproperty to eachcryptographic-assetcomponent. The value is"agile"if the touchpoint has an algorithm identifier and a rotation policy,"partial"if it has one of the two, and"brittle"if it has neither. Run the generator against the four application-layer Ch 25 touchpoints and print the resulting labels. -
Write a one-page deprecation plan for an RSA-2048 signing key that must be retired by the NCSC 2035 deadline. Cover four questions. Detection: how does a team know the key is still in use? Rotation trigger: what event schedules the replacement? Rollback path: what happens if the replacement fails verification in production? Observability signal: what does the CBOM diff show across the rollout? Name which of the six architectural areas each step touches.
Worked solutions and editorial notes for these exercises are in Appendix D, Chapter 26. A separate track, for rebuilding rather than reading: the package exercises/ch26-agility has every function the chapter teaches replaced by a stub. Run PQC_IMPL=exercises pytest tests/ch26 to grade your version against the suite that proves the reference one.
References
Section titled “References”Last updated: