But why? Well, there are limitations to the old solution of using guarded heap allocations for everything.
- A minimum of three memory pages have to be allocated for each value: two guard pages sandwiching data pages.
- Some systems impose an upper limit on the amount of memory that an individual process is able to prevent from being swapped out to disk.
So it is worth looking into the use of encryption to protect information. After all, ciphertext does not have to be treated with much care and authentication guarantees immutability for free. The problem of recovering a secret is shifted to recovering the key that protects it.
But there is the obvious problem. Where and how do you store the encryption key?
Suppose is the key we want to protect and is some random bytes sourced from a suitable CSPRNG. We initialise the two partitions:
storing each inside its own guarded heap allocation. Then every milliseconds we overwrite each value with:
and so on. Then by the properties of XOR,
It is clear from this that our iterative overwriting steps do not affect the value of , and the proof also gives us a way of retrieving . My own implementation of the protocol in fairly idiomatic Go code is available here.
An issue with the Boojum scheme is that it has a relatively high overhead from two guarded allocations using six memory pages in total, and we have to compute and write 64 bytes every milliseconds. However we only store a single global key, and the overhead can be tweaked by scaling as needed. Its value at the time of writing is 8 milliseconds.
The authors of the Boojum claim that it defends against cold boot attacks, and I would speculate that there is also some defence against side-channel attacks due to the fact that is split across two different locations in memory and each partition is constantly changing. Those attacks usually have an error rate and are relatively slow.
OpenBSD added a somewhat related mitigation to their SSH implementation that stores a 16 KiB (static) “pre-key” that is hashed to derive the final key when it is needed. I investigated incorporating it somehow but decided against it. Both schemes have a weak point when the key is in “unlocked” form so mimimising this window of opportunity is ideal.
In memguard the key is initialised when the program starts and then hangs around in the background—constantly flickering—until it is needed. When some data needs to be encrypted or decrypted, the key is unlocked and used for the operation and then it is destroyed.
The documentation provides a relatively intuitive guide to the package’s functionality. The
Enclave stores ciphertext, the
LockedBuffer stores plaintext, and
core.Coffer implements the Boojum. Examples are available in the examples sub-package.
The most pressing issue at the moment is that the package relies on cryptographic primitives implemented by the Go standard library which does not secure its own memory and may leak values that are passed to it. There has been some discussion about this but for now it seems as though rewriting crucial security APIs to use specially allocated memory is the only feasible solution.
If you have any ideas and wish to contribute, please do get in touch or open a pull request.