Password Authentication for Go Web Servers

23 Apr 2015

The stackoverflow question "How are people managing authentication in Go?" has had a few thousand views. Go's framework's (beego, goji, revel, martini, negroni, gin) do not have anything you should use built-in. Other languages have a common capability for a "classic" password authentication scheme.

A "classic" scheme requires a username (could be email address) and password, that is stored in a database (with the password hashed). A cookie is stored in the user's browser to identify them, so they don't need to retype their credentials, and that cookie information is verified on the server. This post will look at what is available in Go (golang).

This will not get into the debate about not using passwords at all (such as using password reset emails only, using client certificates, or using things like OAuth). It's mostly just to help those auditing existing systems.

Checklist

Things to look for in classic web authentication systems for Go.

Server uses TLS

Not Go specific, but required. Go has capabilities to run as a TLS server, but just put a load balancer and reverse proxy in front (like nginx).

Passwords are hashed, salted, and the hashing is work intensive

This ensures if an attacker gains access to the database, that they cannot log in as other users or compromise those user's accounts on other sites. The salting (adding random data to a password) and added work is given by using bcrypt, scrypt or PBKDF2 which have both these traits built in and have a "cost" parameter to increase the work. Those three algorithms are pretty equivalent from an auditing perspective, but if you're building new system, my preference is for bcrypt.

Some more recent systems also add "pepper" to their password hashing and salting. For example, here is pepper being used in Ruby on Rail's Devise authentication solution. A pepper is similar to a salt in that you are simply concat'ing a string to something and then hashing it, but this is not stored in the database and is constant across all passwords. The benefit of this is added hassle for attackers that successfully pull off a SQL injection attack. Another similar solution (in terms of threats it works aganst) is to use a separate database for authentication data so an injection attack on one won't impact another.

The bcrypt library you should use can be found at golang.org/x/crypto/bcrypt. You may see imports that reference it's old location at code.google.com/p/go.crypto/bcrypt. You'll generate a hash with:

hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)

The bcrypt library automatically creates a salt. If you want a pepper too, use:

bcrypt.GenerateFromPassword([]byte(password + "my secret pepper"), bcrypt.DefaultCost) // TODO MUST replace pepper string

The output hash will look like:

$2a$10$zg0oPq.BwHZb8IRdkBqm2ubhN9b.w5wDYVmKKfKYgvxXY2eumdrJS

where the $10 identifies the cost, and the garble after the third $ is the base64 encoded salt and hash. Using this bcrypt library will take care of most of the mistakes you might make. For example, it generates a random salt for you here.

So include in your database a field for passwordHash that is 60 characters. You should not have a passwordSalt field. Ensure that for user registration and logon that the plain-text password supplied by the user is not used anywhere else (hash it and forget it).

Data about who the user is will be stored in a cookie that is cryptographically "signed" with a secret from the server. This is done via an HMAC-SHA256 signature. Ensure this secret value is truly random and secret.

Using github.com/gorilla/sessions takes care of this for you as it will use github.com/gorilla/securecookie behind the scenes which adds the HMAC here, where the hash defaults to a sha256, and the cookie's default age is 30 days. You need to specify the secret. The code should have a line like:

cookiejar := sessions.NewCookieStore([]byte(your_secret_key))

I've seen some authentication systems that only keep usernames in the cookie, and because it is signed, an attacker can't just change the username and login in as a different user. However, if the attacker discovers (or cracks) the signing secret, then they can, so you should also include some secret that is specific to that user. For example, Django hashes the user's password hash with a salt (so yes, two salts involved since the password hash involves a salt) and stores that in the cookie (here is the cookie storage, and here is the creation of the user secret). So even if an attacker knows the secret key used for signing cookies, they can't log in as other users.

All authentication comparisons are time constant

This is to prevent timing attacks. Honestly, I'm unconvinced this is a real threat in most scenarios, but better safe than sorry. For checking the password hashes that used bcrypt, you should use bcrypt.CompareHashAndPassword which uses subtle.ConstantTimeCompare behind the scenes. Use subtle.ConstantTimeCompare anywhere else security values are checked (for example, gorilla/securecookie uses this to check the HMAC signatures).

For those that like rosetta code, here are some constant time comparisons in different languages:

Because I go full tin-foil hat some times, I checked the generated x86-64 code for subtle.ConstantTimeCompare, since I was worried about compiler optimizations. This is not an issue as Go doesn't do these types of optimizations, but in languages like C you would want a pragma to disable optimizations or use the volatile keyword as that is what _tscmp uses. It's not well-known, but Go actually does have some pragma's such as go:nosplit, go:noescape, go:nowritebarrier, go:linkname and some others. These aren't really documented so you shouldn't use them. An example of their use in the run-time is here and you can see the lexer parsing for them here.

As long as I'm admitting my trust issues to the world, you may also be interested that Go gets random correctly as you can see here for Windows where it calls the OS API's CryptAcquireContext and CryptGenRandom. It pulls from /dev/urandom on other OS's, except Plan 9. I don't know much about Plan 9, but if you're a cryppie and know that OS, take a peak.

Other features

  • Brute force protection: Although bcrypt has a cost function to make it slower, an attacker could just multi-thread his brute-forcing code that tries to logon to your server with multiple credentials at once. So you'll want to do some sort of throttling, or add time-outs for IP's with too many failures, or add a CAPTCHA. You want to avoid locking out legitimate users though.

  • Password reset emails: When a user forgets their password, a URL with a special token will be emailed to them. Ensure this token is disabled after it is first used, and/or after a few hours. Apply all the same precautions to this as you would other security information.

  • Two-factor authentication: This will also help with any brute-forcing. If an attacker get's access to your database though, then it's no longer useful, as you will have likely have stored your 2FA seed in your DB. That assumes you're using something like Google authenticator as opposed to SMS messages. If you use SMS, make sure an attacker doesn't burn your bank account by causing a billion SMS's to be sent.

Current Go Options

HTTP Basic Auth

To start, let me correct what I said earlier: Technically, beego does have some authentication functionality in github.com/astaxie/beego/plugins/auth/basic.go, but you shouldn't use it for a real site. One reason why is it requires your application to have code like

beego.InsertFilter("*", beego.BeforeRouter,auth.Basic("username","secretpassword"))

The problem here is that you are required to include the plain-text password in your application, and web servers should never store plain-text passwords. Likewise, there is http middleware that can be used with goji and others at github.com/goji/httpauth which again requires the passwords to be stored in plain-text. These are just Basic HTTP Authentication as defined in the original HTTP RFC 1945, without the use of cookies or session management.

Other options

There is a project github.com/apexskier/httpauth/ which get's a lot things right if you want to look at it for how some of the functions discussed above are used, but it's only a starting point for your web authentication needs, and it needs to store a user secret in the cookie along with just the username.

A more recent, and much more extensive, project is authboss, but I have not looked at it too closely and it may be doing too much for your needs.