Against Cipher Agility in Cryptography Protocols

Imagine that you want to build a brick wall.

However, instead of laying each brick deliberately and using mortar to assemble the desired structure, you are instead instructed to assemble a three-dimensional lattice of mortar, like so:

This might seem strange, so naturally you ask what the purpose is for such a design. You are told: "This will allow the inhabitants to hot-swap bricks whenever they need to. For example, if an influx of termites that can eat clay brick infest the area, they might want to switch to concrete bricks to protect their house."

Would you trust such a wall to support the weight of a roof?

Clearly not.

So why do we expect cryptography designed this way to be secure?

Cipher Agility Considered Harmful

A recent trend in cryptography engineering has been to prioritize versioned protocols over cipher agility.

The clearest example is the difference between PASETO and JSON Web Tokens.

The reason for this trend is actually quite simple: It's easier to assure the security of a small, robust, fit-for-purpose cryptography tool that doesn't come with a lot of knobs and levers than for a "let's solve 99.9% of use cases, poorly" kitchen sink design.

Additionally, most cryptography vulnerabilities exist in the mortar, not within the bricks. How you join primitives together can impact security more than which primitives you're using.

The brick/mortar story above is an allegory for cipher agility: Protocols where you can have multiple choices of cryptographic primitives for a single purpose. (For example, being able to encrypt with Blowfish, Triple-DES, AES, or ChaCha20 is an example of cipher agility, whereas only having One True Primitive for a given version of the protocol is not.)

Cipher agility is why so many commercial VPN providers (which are, in and of themselves, a very bad idea) powered by OpenVPN accidentally use Blowfish for transport-layer encryption.

Eschewing cipher agility is one of the reasons why WireGuard is better than OpenVPN.

Libsodium is better than mcrypt and/or openssl for application-layer encryption for the same reason: You get one API, tuned for security, and you don't get to hot-swap components.

With versioned protocols instead of cipher agility: If a vulnerability is discovered in protocol version 1, you simply upgrade to protocol version 2.

No insecure configurations. No accidentally encrypting in ECB mode (or authenticated CBC mode with PKCS#7 padding and exposing a 17 year-old padding oracle vulnerability in a new codebase).

It's either safe, or it isn't. If it's not, upgrade to a safe version. If it is, your job is done.

Cipher agility is fun for security researchers. Runtime negotiation often leads to being able to coerce a well-configured system to behave poorly and leak secrets.

In contrast, versioned protocols are boring: You can't even guarantee that the system you're interested in even implements the older, vulnerable versions of the protocol, so your room for downgrade attacks might be zero.

We've covered this before, but it comes up often enough, in enough weird places, to warrant a post dedicated to this topic.

What's the Formal Definition for "Cipher Agility"?

Cipher Agility is a term that means runtime negotiation of the primitives in a cryptography protocol.

It refers to any protocol where you have to specify a cipher, a cipher mode, a hash function, or any other options when you use it.

For example, JWT has the alg header which can contain HS256 (meaning HMAC-SHA256) or RS256 (meaning RSA with PKCS#1 v1.5 padding and SHA256). The fact that each token advertises different primitives is what makes the JOSE standards cipher-agile (which is a bad thing).

In non-cipher-agile protocols, you don't negotiate your primitives at runtime.

PASETO is not cipher-agile. Your only options are version and purpose ( local or public ). You don't get to directly choose which hash function PASETO uses. There is no 3DES, Blowfish, SHA1, or RSA-PKCS1v15 option anywhere in PASETO (and never will be). PASETO is a versioned protocol.

PASETO doesn't totally eliminate end user choice (since you can specify a "purpose" too), but you don't have to know the difference between AES-CBC and AES-GCM to use it safely.

WireGuard is not cipher-agile. You always get X25519, XChaCha20-Poly1305, SipHash-2-4, etc. If these primitives are broken, WireGuard will publish a superseding protocol using more secure primitives, carefully constructed to maximize security. But this won't happen at runtime.

So What Makes Cipher Agility Bad?

Going back to our analogy, most cryptography vulnerabilities in the real world exist at the joinery of the primitives (the mortar) rather than within them (the bricks).

Giving non-experts the possibility of choosing bad primitives (or using them badly) decreases the security of an otherwise boring protocol.