Coding with security in mind: interfaces

At SpiderOak we do a lot of coding in Go. I personally really like Go because it feels like Python with the type safety guard rails and almost magical concurrency helpers. But the point of this post is not to do yet another deep dive into why Go might be better than some other language, or how we use it at SpiderOak. The goal is to talk briefly about subtleties in coding that mean the difference between a secure environment and an insecure one.

Hashing

DISCLAIMER: This finding is not mine, but Frank Sievertsen's.

Hashing is an operation in use in many different scenarios, some not even really security related. A popular hashing algorithm is SHA 256, let's take that as an example.

Here's how you calculate the SHA 256 sum of a sequence of bytes in Go:

sum := sha256.Sum256([]byte("hello world\n"))

Super simple.

Ok, so what's the problem? Nothing with that line above, but how about this code:

h := sha256.New()
sum := h.Sum([]byte("hello world\n"))

If you read the documentation closely, you'll see that that's wrong. And a lot of people would argue that you need to read the documentation in a lot of detail, specially when you are using cryptographic primitives.

But... if we all did everything the way we have to do it, this world would be extremely different.

In case the problem above is not clear, the issue is that sha256.New() returns something that implements the hash.Hash interface. That interface has a Sum([]byte) []byte method, but here's the documentation for it:

type Hash interface {
        ...
        // Sum appends the current hash to b and returns the resulting slice.
        // It does not change the underlying hash state.
        Sum(b []byte) []byte
        ...
}

So we are adding what we want to hash to what we think is the hash sum of it. Not good at all.

You could say that's a silly mistake, and in some ways you'd be correct, but when it comes to cryptography, I prefer to design things trying to prevent misuse as much as possible.

Of course, there's no easy way to detect really bad use, but since appending a byte slice is not a complicated thing to write, maybe the helper in Sum([]byte) []byte is not really needed.

Encryption

A really well known person in the cryptography community is djb. I know close to nothing about the mathematical background behind basic cryptography, and even less about djb's work, but one thing I really like about the way djb designs his cryptographic primitives is that they tend to be really hard to implement them in an unsafe way (keywords here are: side channel attack resistant). I won't dive into the specifics; maybe that'll be a future post although there's a lot of material online already.

NaCL is among the many gems that came from djb et al. It's an encryption library that's amazingly fast and really secure.

One of the main reasons it's secure is the abstractions it works with. When you use it, you don't care about specifics: you care about the functionality and trust the writers of the library to make the right call for you because, chances are, they know better.

This library introduces the abstraction of "box" and "secret box". The idea is that a "box" is something you can "seal" in a way that only somebody else can "open". If the box is tampered with, the receiver can detect this.

A "secret box" is similar, but the way the keys work is different. For more details, please see the official documentation.

Since NaCL uses primitives that are easy to implement securely, there are a lot of implementations. In our case, we'll focus on 2: Go's official implementation and libsodium (actually, the Go wrappers for libsodium).

NaCL itself is implemented in the most efficient way possible, so there's quite a bit of assembler code in it. This is the reason why libsodium exists, it's a compatible library that is implemented in a more portable way.

Let's see how you would generate a key pair to be used when sealing/opening boxes in Go's official implementation:

publicKey, privateKey, err := box.GenerateKey(crypto_rand.Reader)

Super simple, great! That's the point!

Let's assume you start implementing whatever you're working on and you realize that for some reason you'd rather migrate to libsodium. You open up your favorite search engine, type "libsodium go" and chances are you'll find this version of it.

So you change your key generation:

publicKey, privateKey, err := cryptobox.CryptoBoxKeyPair()

And that's it! ... right?

WRONG. CryptoBoxKeyPair returns first the secret key, so it should be like this:

privateKey, publicKey, err := cryptobox.CryptoBoxKeyPair()

One option to address this is to make use of Go's type system, return a PublicKey and PrivateKey type, which can be []byte underneath, but they will be checked when they are used.

PyNaCL, the python bindings for libsodium (not NaCL), does this differently:

from nacl.public import PrivateKey

sk = PrivateKey.generate()
pk = sk.public_key

This is a lot harder to get wrong.

Tweetnacl.js returns an object with publicKey and privateKey as members.

Finally, we have Tink that is developed specially to "(...) provide simple and misuse-proof APIs for common cryptographic tasks".

Let's look at how this library let's you generate a key and encrypt data (example copied from the README):

    import com.google.crypto.tink.Aead;
    import com.google.crypto.tink.KeysetHandle;
    import com.google.crypto.tink.aead.AeadFactory;
    import com.google.crypto.tink.aead.AeadKeyTemplates;

    // 1. Generate the key material.
    KeysetHandle keysetHandle = KeysetHandle.generateNew(
        AeadKeyTemplates.AES128_GCM);

    // 2. Get the primitive.
    Aead aead = AeadFactory.getPrimitive(keysetHandle);

    // 3. Use the primitive.
    byte[] ciphertext = aead.encrypt(plaintext, aad);

That looks quite amazing. Inspecting the Go version of it, key generation for ECDSA at first hand looks too complicated, but closer inspection shows that it doesn't simply use []byte and they introduce the idea of a key manager.

If we look at the interfaces it presents to the user, it's pretty straight forward:

type Aead interface {
	// Encrypt encrypts {@code plaintext} with {@code additionalData} as additional
	// authenticated data. The resulting ciphertext allows for checking
	// authenticity and integrity of additional data ({@code additionalData}),
	// but does not guarantee its secrecy.
	Encrypt(plaintext []byte, additionalData []byte) ([]byte, error)

	// Decrypt decrypts {@code ciphertext} with {@code additionalData} as additional
	// authenticated data. The decryption verifies the authenticity and integrity
	// of the additional data, but there are no guarantees wrt. secrecy of that data.
	Decrypt(ciphertext []byte, additionalData []byte) ([]byte, error)
}

Clearly there are a lot of ways to implement the same thing. The goal is perfection, but since that is unreachable by definition, you want to shoot for pushing the bar as high up as you can to make it harder to misuse.

So... if you are a user of libraries implementing cryptographic libraries, read the documentation really carefully!

If you are implementing a cryptography library, please have the requirement "hard to misuse" in the same level as "provide secure primitives".

Coding with security in mind: interfaces
Share this
All content is licensed with: