The anatomy of a bad idea

I’ve been running a fever all day and am only coming back to my senses now, thanks to a huge (and possibly toxic) dose of Motrin. Sometimes when you’re under the weather, things kind of blur together. This is probably why I can only recall two things I read this afternoon.

The first was a news story concerning a series of tests in which the US government dropped nuclear bombs on a variety of canned beverages, apparently to see how well they held up (but possibly just because they had some nukes). The second was the W3C Web Cryptography API, a draft Javascript crypto API that will ultimately power a lot of the crypto we use on the web.

Even in my addled state, I can tell that only one of these things is a good idea.

(In case you’re still guessing, here’s a hint: we’ll still need beer after the bombs fall.)

Before I go further, let me be clear: I think the web absolutely should have a secure browser-based crypto API! My problem is that I can’t bear to watch the W3C screw it up. Because screw it up they absolutely will. The W3C, bless their sweet little hearts, has left a trail of wreckage behind every crypto project they’ve ever taken on. And these were small projects!

The Web Crypto API is far more ambitious, which means it will blow up that much more spectacularly when it goes. And unfortunately, the W3C contributors seem immune to all attempts to disuade them from this course.

So what is it?

The Web Cryptography API (henceforth WC-API) is a perfectly lovely idea: to build a standard Javascript API for doing crypto on the web. In theory it’ll be supported by lots of browsers and will include all the logic you need to manage keys securely and perform common cryptographic operations. Things like encryption, signing, etc.

This can’t be deployed soon enough — if it’s done right. The problem with current Javascript implementations is that they’re really bad at managing keys securely, and the crypto implementations are frequently ad-hoc, slow, and vulnerable to side-channel attacks. A common API should allow us to do much, much better.

But at the same time we need to recognize that cryptography is hard (seriously: this should be the name of my blog.) Moreover, Javascript development is — no offense — the last place we want people making hard cryptographic decisions. Decisions like, for example, ‘should I use the obsolete and very broken PKCS#1v1.5 encryption padding scheme in my protocol?’ These are things that very few people need to think about, and most people who are thinking about them are going to make the wrong choice.

Two visions

This is not a new argument. For two competing visions of how a cryptography library should work, consider two existing libraries: OpenSSL and NaCl. OpenSSL is the grand old lady of cryptography, ‘beloved’ by millions. Actually OpenSSL isn’t a crypto library except by accident — it’s an SSL library that happens to expose a bunch of underlying crypto routines. NaCl is much newer and basically tries to repair the damage wrought by OpenSSL.

If we must resort to analogies, let’s try these: OpenSSL is the space shuttle of crypto libraries. It will get you to space, provided you have a team of people to push the ten thousand buttons required to do so. NaCl is more like an elevator — you just press a button and it takes you there. No frills or options.

I like elevators.

Now obviously there are different reasons to take the space shuttle. Sometimes you need to go to places that aren’t supported by the buttons on an elevator. But you’d better know exactly what you’re doing, because making a mistake on the space shuttle is not something you get to do twice.

More concretely, NaCl takes the position that most users don’t need their crypto to be backwards compatible with other systems, and instead provides a simplified abstraction in the form of the ‘crypto_box‘ operation. This abstracts all the messy details of public and secret key cryptography into a single secure function call. This approach may seem a bit extreme, but it’s also sufficient for a huge amount of what most people need to get done when they’re encrypting things.

WC-API does not adhere to the NaCl vision. Instead, it follows in the OpenSSL tradition. Here the basic unit is the crypto primitive, which can be selected from a menu provided by the designers. This menu includes some pretty solid primitives like RSA-OAEP, ECDH and AES-GCM, but it also includes ridiculous stuff that should be banned from the universe — things like the aforementioned RSA-PKCS#1v1.5 (signature and encryption) padding as well as several unauthenticated symmetric modes of operation. Worse, some of the bad primitives are also the recommended ones.

The user has to pick the right ones, generate the right keys, and hopefully (god bless) tie them all together without running afoul of the various practical attacks that they’ll be victim to when they make the wrong choice.

Some will do this. Some will not.

But maybe you’re just being too sensitive…?

This is possible, but gosh, don’t take my word for it. If you think I’m just blowing smoke, please at least listen to IETF Crypto Forum Research Group representatives Kenny Paterson, Tibor Jager and Juraj Somorovsky when they say exactly the same things as me but in scarier language:

We noted that the standard contains some legacy crypto algorithms,
which have well-known serious weaknesses but are (unfortunately) still
widely used. These weaknesses frequently lead to attacks on systems
that naively use these algorithms.

For instance, several works [Ble98,BFKST12,JSS12, …] demonstrate the
vulnerability of RSA PKCS#1 v1.5 to variants of Bleichenbacher’s
attack in various (practical and widely-used) applications. Using
PKCS#1 v1.5 in a secure way is highly non-trivial and often
impossible, in particular in Web-based applications
. Moreover, it is
known that the pure availability of PKCS#1 v1.5 encryption may also
spoil the security of other algorithms, like RSA-OAEP or
RSASSA-PKCS1-v1_5 signatures.

Similarly, unauthenticated block-cipher modes of operation, like CTR
and CBC, have in the past been shown to enable “padding-oracle”-like
, for instance in [Vau02,DR’10,DR’11,JS’11,AP’12, …]. These
modes of operation should not be used without additional security
measures. An appropiate measure is a message authentication code,
which should be computed over the ciphertext and must be verified by
the receiver.

Actually we would like to recommend that these legacy ciphers are
removed from the standard
. While in some special cases countermeasures
against the known attacks are possible, implementing these is highly
non-trivial and error-prone, thus much harder than implementing new
libraries supporting secure algorithms. Even worse, in many examples
there exists no countermeasure.

What about backwards compatibility?

Yes, I concede that there may occasionally be times when your Javascript needs to decrypt a message that was encrypted by some other, archaic piece of crypto software. And for those times there really is a need to support some of the obsolete crypto schemes provided in the current W3C specification. But “support” is not the same as “provide in your mainline API”.

Unfortunately, several proposals to separate the older algorithms into a different namespace have been shot down, as have proposals to strongly warn users against the bad algorithms. I can’t see any justification for these decisions, except that possibly there is a God and he wants cryptographers/pentesters to stay busy for the next few years.

So what does it all mean?

A while ago on Twitter somebody asked why I spend so much time criticizing things that are old and broken, rather than making things new and shiny. When I finished sputtering, I realized that the answer is simple: I’m lazy.

But that’s ok, because the truth is, most software developers are too. Not in a bad way, but simply in the sense that we want to get from point A to point B without being distracted by a lot of useless garbage. Give us a way to get between those two points and we’ll do it with alacrity. Make us understand that PKCS#1v1.5 is universally insecure unless you use it in a very particular way (that’s documented almost nowhere), and that even this relies on some pretty unusual assumptions — and you’ll tend to find a lot of people just making mistakes.

And in case this is all too doom-and-gloom for you, here’s a parting thought to cheer you up. No matter how bad this API turns out to be, at least we don’t have to drink it.

Update (12/30): A few people have criticized this post on the grounds that I should join the WebCrypto WG rather than complaining about it. Usually the implication is that I’m after blog hits.

The short answer is: if I really wanted blog hits, I wouldn’t be bitching about an obscure web standard! I’d be complaining about Apple. I speak from experience here.

The longer response is that many bright folks in and outside of the WG have made these points (see the CFRG note above). Their input has been respectfully considered and rejected. My impression is that we’re not dealing with a lack of knowledge here, but rather a more fundamental difference in approach. The WG has made an institutional decision to emphasize backwards compatibility over security. This works if you’re designing a standard and hoping to get it adopted quickly. It just doesn’t lead to a good standard.

I think there’s a certain kind of person who has the time and political skills to fix this kind of organizational problem, and I sincerely hope that person gets involved. But sadly, that person is not me.

18 thoughts on “The anatomy of a bad idea

  1. Devs don't care. The moment they actually need this stuff, they'll use pure JS implementations. When those are solidified, and performance appears to matter, maybe the browser can get around to accelerating them in C.

    And look, pure NaCL in JS is out and about!

  2. It seems to me that the WebCrypto API is inherent compromised, because it is designed to be called by web app JS code that is running in the browser. Neither the web app provider nor the web user has full control of the in-browser JS code. So, for example, a SecureWebMail(tm) web app might offer to decrypt a message locally (in the browser) using an opaque private key provided by the user, but then secretly send the decrypted message back to the server.

    So the choice of algorithms is, unfortunately, not particularly important.

    Or am I missing something here?

  3. “Moreover, Javascript development is — no offense — the last place we want people making hard cryptographic decisions.”

    Can we please get over this ridiculous language chauvinism already? The statement holds in any language. If you're doing something that uses crypto, you should not be making important decisions, you should be reusing the decisions made by others.

    Shitty programmers are everywhere. At least in a high level language, you have the luxury of quick and easy refactoring to allow you to correct mistakes. In a traditional typed language, bad decisions are much more locked in.

  4. The W3C WebCrypto WG compiled this table “Platform Support for JWA Crypto Algorithms”:

    There is a “NO” in the line RSA-OAED for both PHP and NSS.
    If I remember correctly this was the reason not to ban RSA1_5 completely. There are too many PHP based web servers.

    AES-GCM is another example. Yes we want that but there are many cells with NO. In the IETF JOSE WG we mandate integrity protection for CBC modes.

    I guess one could write a similar blog post about too.

    Anyway thanks for the input. Did you consider joining the two WGs?

  5. To be clear, it is increasingly my sense that we want developers to be using a NaCL style box. I, too, like elevators. Language chauvinism is kind of irrelevant, in that anyone expressing it becomes irrelevant 🙂

  6. I've gotten a lot of comments like this, so let me be clear. There's nothing wrong with Javascript or (sophisticated) Javascript coders. But you can't deny the fact that JS is currently the most accessible programming language out there. Anyone can learn to code it, and lots do.

    So the fact is that there are likely to be unsophisticated JS coders simply by dint of the fact that there will be *more JS coders*. This isn't an insult to anyone, it's just common sense.

    Another problem is that JS's accessibility means it's likely to become a dominant language even outside of the browser. So not only will we be enabling a lot of bad code today, we could easily find ourselves locked into these decisions for decades. This is hardly an insult to Javascript — I'd describe it as the price of success.

  7. I know, but this highlights a fundamental problem with the design philosophy. Instead of designing an API for the next ten years, W3C designed to specs that reflect the *last* ten (twenty) years of crypto libraries. But much worse, they explicitly designed to the lowest common denominator. Once this decision has been taken, all of the other problems are inevitable.

    And keep in mind, I'm not even suggesting that the designers abandon support for PKCS#1v1.5. But why is RSASSA-PKCS#1v1.5 on the “Recommended” list? This makes no sense at all. It's toxic.

    I've made a decision to stay away from W3C groups. The problem here isn't that they *lack* for cryptographic expertise. The problem is that they know, but they do it anyway.

  8. Subordonating security decisions to PHP's support is beyond madness. Security is not negociable. Neither is PHP's continuated brokenness.

  9. The signature system RSASSA-PKCS1-v1_5 isn't that bad. I wouldn't recommend it, but I'm not aware of any practical problems either.
    Get rid of it, but no need for panic. If you need it for some legacy systems, so be it.

    The big practical problem is the encryption system RSAES-PKCS1-v1_5, which is vulnerable to many practical attacks. Really hard to use in a way that doesn't break your application. Get rid of it ASAP.

    Related question on crypto.SE:

  10. Javascript isn't just used as part of websites. It's used for server applications(node.js), browser plugins, client apps,… Having a compatible crypto api for these is certainly nice.

    The problem you mention is real, but needs to be solved independently from creating a crypto api.

  11. Yes, the problem with RSASSA-PKCS1-v1_5 isn't its brokenness, its the fact that it's obsolete and should be phased out (or at very least, not *recommended*). PSS has a reduction to the RSA problem and it's not that hard to implement (doesn't require randomness either, that's optional).

    PKCS1-v1_5 encryption padding is very dangerous. Ironically you're better off just encrypting a random integer, then hashing that through a KDF to get a symmetric encryption key vs. using PKCS#1v1_5.

    These are all things that could be done for you by a high-level encryption API, something that doesn't exist in this spec.

  12. I've been following (somewhat erratically) the WebCrypto list for a few months now. It's like watching whales trying to knit. A whole standards committee, all with their own ideas of how a crypto API is supposed to work and what it should do, trying to do a design-by-committee API. Reading the list archives is like watching a re-run of the train wreck that was CDSA in slow-motion, one freeze-frame at a time. If WebCrypto fails it'll be nothing to do with doing crypto in JS, it's from getting a committee to invent yet another crypto API that's meant to be all things to all people.

    The horror. The horror.

  13. It gives me peace of mind, that even after the encryption on nuke security codes are broken (because developers used a vulnerable recommended method), we will still have beer to drink.

    The problem that I saw a lot back home, was that a lot of developers chose some snake-oil crypto to encrypt stuff. Even when they had made good choices (like AES256) the key was stored in plaintext somewhere insecure. Isn't it time for some solid “key management” protocols?

  14. > Javascript isn't just used as part of websites. It's used for server applications(node.js), browser plugins, client apps,… Having a compatible crypto api for these is certainly nice.

    I agree with you. But the W3C proposal begins, “This specification describes a JavaScript API for performing basic cryptographic operations in web applications…” which tends to suggest websites, at least to me. None of your uses are mentioned. Whereas I think this API, if added to web browsers, should only be exposed to browser plugins/apps and not to any web site, for the reason I originally mentioned.

  15. Assuming you could, what would be the main cipher combinations you'd allow for the following common scenarios, with the idea that data stored on disk will likely use block ciphers while those over a network use stream ciphers?

    1) Encrypting data to be stored on disk.
    2) Encrypting data to be transferred over a network.
    3) Digital signature of data that is stored on disk.
    4) Digital signature of data that is transferred over a network.

    It seems that there is lots of hand-wringing about modes, like AES+CBC but is that only when it's being used to secure communications over a network (in which counters for “authenticated encryption” is required). Is there an AES+CBC issue for data stored on disk rather than as a sequence of messages sent over a network communications protocol?

Comments are closed.