Securing a go REST API - Part 3: Passwords, Tokens and Secrets

This is part 3 of a multipart series on how to secure your API in golang. This post will talk about dealing with passwords, tokens and generally secrets. We won’t go into the cryptographic details here and instead focus on the best practices because I want this to be a short and concise guide rather than another blog post about hashes and rainbow tables.

Hash your passwords

This post is not going into the details of why you should hash your passwords, but you need to look for mostly two things:

  1. When dealing with user-specified data, always use cryptographic salt.
  2. Use a hashing algorithm designed for passwords, specifically argon2.

Let’s start with bullet point number one and let’s focus on passwords even though it applies to all user-specified (or user-generated) secrets. Let’s say someone uses the password password12. Since hashing the same data will always yield the same result the hash of password12 will always be the same, e.g. with SHA2:

>$ sha256sum <(echo -n password12)
b3d17ebbe4f2b75d27b6309cfaae1487b667301a73951e7d523a039cd2dfe110

Or b3d17eb for short. Now, a smart hacker will precompute all well known passwords or even precompute just a huge amount of hashse in rainbow tables. If they find the hash b3d17eb in their data, they know the user used password12. We want to prevent that, so instead of hashing the password, we generate some random data and hash it together with the password. The random data is called salt and needs be read from a cryptographically secure RNG. We want to use a unique salt for each password, so in go we would generate a new salt like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// important: use crypto/rand and not math/rand
import "crypto/rand"

func genSalt() ([]byte, error) {
	var salt [32]byte
	if _, err := rand.Read(salt[:]); err != nil {
		// ...
	}
	return salt[:], nil
}

If our salt is for example fj39e43, we would hash the following data:

>$ sha256sum <(echo -n fj39e43password12)
a2aa997ea3af0f7a1aacd4d67098a727d7e42427186aa5e9ac5591bc19e4f4f1

As you can see, the hash is now different and no one will know password12 was used as a secret. We store the hash together with the salt in the database and that’s it. The second point is emphasizes more which algorithm to use. We used SHA2 as an example, but it is a bad hashing algorithm for passwords: It’s too fast. Ideally, you want an algorithm that’s slow, but not too slow for your servers. To make it short: The algorithm you are looking for is called argon2 and since we have an implementation, there is no need to use a different one. In go, it is very easy to use.

1
2
3
4
5
6
// important: use crypto/rand and not math/rand
import "golang.org/x/crypto/argon2"

func hash(data []byte, salt []byte) []byte {
	return argon2.IDKey(data, salt, 1, 64*1024, 4, 32)
}

As you can see, we pass a variety of parameters into the function. There is also a different funciton called Key which uses the argon2 varian argon2i while the function IDKey uses argon2id. The draft recommends using argon2id if unsure. The parameters you see above are also recommended parameters, but you should check the docs, the reference or the RFC.

Hash your secrets

I want to make the point that you should probably hash everything that is a secret. That also includes secret data generated by you, e.g. session keys. However, for this type of data, since it is (hopefully) generated with cryptographically secure RNGs and of sufficient length, SHA2 will suffice as a hashing algorithm and you don’t have to use argon2.

Some people say, session keys or one time use tokens don’t need to be hashed. It’s true that the risk is much lower and probably the damage an attack can deal is also lower, but since hashing is so cheap, you should do it anyways.

Don’t expose your tokens

Something I see often in REST APIs is that people use secret data in their GET requests. Suppose we have a route to lookup some type of tokens for our admin panel and the route would look like this: GET /tokens/:secret. Since it adheres to the RESTful principles to use a GET for a retrieval, lots of people use it like this.

However, this might leak your secrets through browsing history or other means since the route GET takes is not really considered something secret. Instead, you should think of it differently: You are not requesting a resource, you are requesting an action, namely the lookup of a resource. And your action will be triggered by post: POST /tokens/lookup. This allows you to put the secret inside the request body and not leak it somewhere else.

Continue with Part 4: CSRF.