How to prevent cryptographic pitfalls by design [video]
fosdem.org
fosdem.org
Some of the problems that invariably crops up with libraries that hide implementation details:
a. If the algorithms ever need swapping, you might not even find out because your code compiles cleanly (assuming you don't obsessively read changelogs for libraries); only when you go live after the update you see that suddenly all data is garbled and spend a few hours debugging in your own code. How do you replace what's inside something like crypto_box while also breaking existing implementations so that they don't even build?
b. Interoperability across programming languages becomes difficult. AES-256-CTR with HMAC-SHA256 authentication is something you can scramble to get algorithms together because you know them. But crypto_box is just opaque; you'll be stuck reading source code and shoddily porting it to $language_that_you_must_use because policy or technical requirements or whatever dictate you must not use native extensions to bind to your library of choice. If your library of choice is also not written in C (though libsodium, which is what the presented library wraps, is), but rather in Java or some other even higher level language, you'll be completely locked in a monoculture and dependent on that particular library.
They mean well, but the interoperability problem seems to a very unsolved problem. The modern non-FIPS world appears to slowly be gravitating towards de-facto standardizing libsodium, though.
If you "scramble to get algorithms together because you know them" and use AES-256-CTR with HMAC-SHA256, in my experience auditing crypto code, you're very likely to have scrambled together at leave two game-over crypto flaws that libraries like Tink and libsodium avoid for you.
crypto.secret_box.encrypt
is almost always better to use than a combination of crypto.primitives.aes.ctr
and crypto.primitives.hmac.sha256
Aside from the obvious argument that rolling your own cryptography is dangerous, at a more abstract level you just generally don't want to work with raw primitives in most types of software. Specialization isn't just the lifeblood of the economy, it's also the way software productivity works. If your core competency isn't X, why should you be re-implementing X using its raw primitives? You should generally be importing known-good library implementations if possible. Even in adversarial scenarios (like cryptography) this principle remains the same.Given that I don't really understand the parent commenter's point. You touched on this a little with your example of a Markdown library - as software engineers we use things which are substantially black boxes all the time. It seems strange to me that you'd want to do surgery on the raw primitives of a library instead of updating it to suit your needs. In this case, that means just use a reputable crypto library and update it when there's a reported security vulnerability.
You don't even need to buy into, "Don't roll your own crypto" to lean into this.
Which is, I think, a silly argument; if someone finds a stack overflow in a Markdown library, I still need the maintainers of that library to tell me about it --- nobody is going to tell me about it directly. I have more information to act on in the secret_box scenario, not less.
But I was a little confused by the argument too, so maybe I'm not making that much sense either.
You don't. Abstractions like NaCl crypto_box aren't updated, they are replaced entirely (think crypto_box_v2).
> Interoperability across programming languages becomes difficult.
Specifically regarding NaCl protocols, they are quite simple and can be implemented from primitives fairly quickly.
That being said, I think replacing all instances of crypto_box to crypto_box_v2 is much better than #defining
The #define trick only makes problem b) bigger -- I think that if/when a new cryptobox is needed the upgrade strategy will be the following:
1) the new crypto_box will be named crypto_box_v2
2) create a new api endpoint using crypto_box_v2 that isn't being used. If you semver your api endpoints, that means doing a major version bump since it won't be backwards compatible with the older crypto. Does this suck? yes.
3) migrate traffic to the new api endpoint
4) EOL the old api endpoint
When you think about it, it's the only way to do it and cipher negotiation is only masking steps 1-4 (i.e doing it for you, but in an opaque way)
We found that there is so much mis-information available online it's too easy to shoot yourself in the foot with cryptography so we're trying to make tools that make strong crypto easy to implement.
"Security at the expense of usability, comes at the expense of security."
Misuse-resistant cryptography API design is a particular hobby horse of mine. (My other hobby horse is software update security.)
A lot of the arguments in favor of cipher agility (a.k.a. runtime negotiation and risk of downgrade attacks) come from well-intended people who haven't internalized the past few decades of real world cryptographic failures.
I liken it to building a brick wall that's expected to hold up a roof.
Would you rather have a wall made by carefully placing bricks and mortar in place once, and if need be, rebuild the brick wall later with better materials?
Or would a lattice of mortar that lets you hot-swap bricks as needed be a preferable design?
The first one is versioned protocols. The latter is what keeps people typing AES into their code.