
Show HN: Securely Handle Encryption Keys in Go - libeclipse
https://github.com/libeclipse/memguard
======
e79
Looking at this more closely, this takes arbitrary buffers of data and uses
syscalls such as mlock to prevent paging memory to disk, as well as cleans up
at the end by zero'ing out the buffers for you.

Has this been audited in any way? Is there a garuntee that the Go runtime
won't, say, keep a duplicate of the buffer you copy() from in memory somewhere
that can be paged?

Additionally, a technical description of how this works in the README would be
nice for those that aren't familiar with how the memory gets locked.

Neat project. Curious to see how this develops.

~~~
stouset
The linked approach does not work for exactly the reasons you specify. Go can
and will move and copy memory around as it sees fit, which defeats the purpose
of this library. You _must_ manage the memory yourself to have any chance of
doing this reliably.

[https://news.ycombinator.com/item?id=14174500](https://news.ycombinator.com/item?id=14174500)

~~~
empath75
Does hashicorp's vault have similar problems?

~~~
cheeseprocedure
Yes, but it's (reasonably) excluded from their threat model:
[https://github.com/hashicorp/vault/issues/1446#issuecomment-...](https://github.com/hashicorp/vault/issues/1446#issuecomment-221625921)

------
amenghra
Prior art:
[https://github.com/stouset/go.secrets](https://github.com/stouset/go.secrets)
(with a technical description at
[https://github.com/stouset/go.secrets/blob/master/secrets.go...](https://github.com/stouset/go.secrets/blob/master/secrets.go#L4))

~~~
encryptThrow32
go.secrets uses the libsodium sodium_memzero to clear the bytes, whereas
memguard sets each byte to 0.

I feel more comfortable with the first, but can't exactly explain why.
memguard seems better organised in the repo like a ready to go package; I
think go.secrets would be a better solution if it was organised as well as
memguard.

~~~
stouset
Author of `go.secrets` here (it's defunct, btw). Memguard, at first glance,
will _not_ actually do anything. Why? Because go's garbage collector can and
will move and copy memory as it sees fit. `mlock` on a go-managed buffer just
prevents the original memory location from being swapped out to disk, but does
nothing to the copies that the go runtime will periodically create.

This is why I used libsodium (I also implemented this same concept, much more
maturely for Rust[1]). If you want this approach to work, you _have_ to manage
the memory yourself.

In the Rust version, I also use Rust's ownership rules to automatically
`mprotect` with `PROT_NONE` when it's not in use, `PROT_READ` when it's being
borrowed immutably, and `PROT_WRITE` when it's being borrowed mutably, all
with static compilation guarantees. Plus libsodium creates guard pages before
and after the allocation (ensuring no underflows or overflows either into or
out of the allocated memory space), and also places a canary before the
allocated region that panics when the memory is freed if the canary has been
modified. It's far, far more than a simple `mlock`.

I have a rewrite half-in-progress[2] that handles stack-allocated secrets with
fewer guarantees (`mlock` and zero-on-free) but that's more appropriate for
short-lived stack secrets.

[1]: [https://github.com/stouset/secrets](https://github.com/stouset/secrets)

[2]: [https://github.com/stouset/secrets/tree/stack-
secrets](https://github.com/stouset/secrets/tree/stack-secrets)

~~~
nemo1618
You could evade the runtime relocation by copying the data into a new buffer
allocated by mmap.

~~~
stouset
You could, but then you still have the original data in the original location
which needs to be scrubbed. And go does not, to my knowledge, provide
semantics that allow for doing this in a way that should be considered
cryptographically reliable.

The go runtime might optimize away your memzero, or it could have created
other copies that you don't have a handle to.

In the Rust version of my library (and maybe in the go version, it's been ages
since I worked on it), I go out of my way to make it difficult to copy data
from runtime-managed memory into a secret buffer. You _can_ do this, and it
makes a best-effort attempt at zeroing the data when you do, but you lose a
lot of hard guarantees when you do.

~~~
dimitri1995
Not really, you're suppose to use memguard to create a slice then an address
is returned; there is no original data.

~~~
stouset
The GP specifically mentioned copying data into an `mmap`'d buffer. My point
is that copying presupposes secrets already in managed memory, and at that
point you've already lost.

------
nemo1618
Why is Protect a non-blocking call? That means that after I call Protect on
some data, it's not actually protected yet.

~~~
libeclipse
Hey there! Thanks​ for this advice, I'll definitely consider this in the next
release. Like I said in other parts of this thread, the project is in very
early stages right now and, as I understand from reading a lot of the replies,
I seem to have missed a lot.

All of this stuff will be fixed soon.

~~~
stouset
I would strongly encourage you to just wrap `libsodium`. The authors have
thought about this problem a lot, and have done the hard work for you — there
is a surprising amount being done behind the scenes to give the kinds of
guarantees cryptographic keys warrant.

If CGo isn't acceptable, at least use their implementation to guide the design
your go-native version.

------
__michaelg
This is fun in theory, but doesn't really give you a lot in practice. A few
random points:

* On many server systems swap is already disabled today (for availability reasons), so you don't really win anything. (Although what you're doing also won't hurt, so it's fine.)

* On many desktop systems on the other hand, swap is not the only reason for memory to end up on disk, mainly due to hibernation modes. This doesn't just include OS-implemented hibernation modes, but also firmware-provided ones (e.g. Intel's RST.)

* If you're running in a VM (in the cloud?), the hypervisor pretty much doesn't care what you lock in memory.

* If your process crashes you may end up with a crash dump, depending on the system's configuration. (On Linux you can avoid that using prctl's PR_SET_DUMPABLE option.)

Even if none of that is a problem for you, you still need to fill and use the
values you protected. Where do you get the keys from? Is that path protected
as well? This might be fine if you can generate them in place, but even then
it's pretty hard. The same is true for key usage: How do you make sure that
the key doesn't end up in another portion of your memory? It's almost certain
that it ends up either on the stack or somewhere else sooner or later...

------
zimbatm
Hashicorp Vault seems to be locking _all_ the memory:
[https://github.com/hashicorp/vault/blob/c44f1c9817955d4c7cd5...](https://github.com/hashicorp/vault/blob/c44f1c9817955d4c7cd5822a19fb492e1c2d0c54/helper/mlock/mlock_unix.go)

They also set the ipc_lock capability before starting the process in docker:
[https://github.com/hashicorp/docker-
vault/blob/e8edfef53deb6...](https://github.com/hashicorp/docker-
vault/blob/e8edfef53deb64e1898fea66e5fb7488b7dc3154/0.6.3/docker-
entrypoint.sh#L83)

------
ziikutv
I have no technical expertise to chime in. But I just wanted to say, that I
like how nicely formatted the README is.

I think the only feedback would be to show sample usages and adding a bit more
technical information*

[*] That would be understandable to people with basic encryption knowledge.

------
libeclipse
To anyone reading this in the future, all of the issues raised in this thread
have been handled in v0.2.0.

