My mind goes about ten different ways when I want to answer this. I have a lot to say about this, because it is an unassumingly hefty question. The important parts are:
The answer to the problem of secrets leaking is that it really is an application-level (or even os- and machine- level) problem. Libraries can’t solve it; cryptographic purity is not unlike functional purity - the surrounding context must restrain itself from reaching in, and there is very little that the cryptographic primitives themselves can do about it. Cryptographic purity is something that people are only recently willing to make the orders-of-magnitude-efficiency tradeoff required to do it properly, and although some day, we’ll give a yawn as we spin up full homomorphic encryption sandboxes for everything, right now our existing computer ecosystems from the transistors up are still built to take advantage of sharing and shortcuts for efficiency above all else, because our architectures are still built based on an era where fundamentally, that is what it took.
It is.
That’s the thousand-foot perspective, anyway.
More realistically, Haskell’s crypto problem in particular is that it is easy for things to accidentally stay alive due to buildup of thunks; C and C++ are just as vulnerable to forgetting to free
something if you’re handling allocations manually, which you’ll notice that cryptographic primitives require you do.
People used to write assembly and punchcards by hand, people used to write threading and graphics by hand, we still do cryptography ‘by hand’, in large part because our systems haven’t been adapted to these needs yet.
Cryptographic operations should be performed as atomically as possible, with everything zeroed and destroyed immediately after it is no longer necessary, rather than relying on the garbage collection.
Long-lived cryptographic data should be kept to a minimum, and should only be things like immediate session keys rather than the passwords used to generate said keys. It is better to ask for a password or cert store access again, than keep it in memory for the lifetime of the app. More and more, the OS is handling this sort of thing. For the duration that it is in memory, it should be treated specially, as to avoid accidentally printing it into something.
In part, this is why I was using memory
, for ScrubbedBytes
, but I’ll have to re-implement something like it for the botan
bindings.
A lot of the practical answers to this will lie in how we design our higher-level API, which will probably involve constructs akin to withStoredPrivateKey keyRef $ \ key -> ...
, and we’ll have to build stores and atomic cryptographic operations to guide the user away from ever handling raw / exposed keys and such.