Golang Password Encryption For Apps and Websites

As one of the first exercises I’ve conducted in Golang as part of getting used to the core language/framework I’ve implemented a simple password authentication package. It uses Bcrypt for actually hashing the password with 64 bit salting created via Dev/Rand which is just standard practice really. This was more an exercise in building something practical and getting a handle on the syntax of doing some slightly more advanced things.

One thing I’ve noticed with Go, especially using this handy dandy Go Plugin, is that it’s forced me to write really clean code(at least I think so). I’ve really been trying to emphasize this lately, but Go seems to really take it to the next level, at least so far with this really simple bit of code. Anyway, I wrote the whole thing to be tested so each function has a well defined responsibility that is easy to call and check.

package authentication
// This will handle all aspects of authenticating users in our system
// For password managing/salting I used:
// http://austingwalters.com/building-a-web-server-in-go-salting-passwords/
import (
"code.google.com/p/go.crypto/bcrypt"
"crypto/rand"
"log"
"strings"
)
const (
SaltLength = 64
// On a scale of 3 – 31, how intense Bcrypt should be
EncryptCost = 14
)
// This is returned when a new hash + salt combo is generated
type Password struct {
hash string
salt string
}
// this handles taking a raw user password and making in into something safe for
// storing in our DB
func hashPassword(salted_pass string) string {
hashed_pass, err := bcrypt.GenerateFromPassword([]byte(salted_pass), EncryptCost)
if err != nil {
log.Fatal(err)
}
return string(hashed_pass)
}
// Handles merging together the salt and the password
func combine(salt string, raw_pass string) string {
// concat salt + password
pieces := []string{salt, raw_pass}
salted_password := strings.Join(pieces, "")
return salted_password
}
// Generates a random salt using DevNull
func generateSalt() string {
// Read in data
data := make([]byte, SaltLength)
_, err := rand.Read(data)
if err != nil {
log.Fatal(err)
}
// Convert to a string
salt := string(data[:])
return salt
}
// Handles create a new hash/salt combo from a raw password as inputted
// by the user
func CreatePassword(raw_pass string) *Password {
password := new(Password)
password.salt = generateSalt()
salted_pass := combine(password.salt, raw_pass)
password.hash = hashPassword(salted_pass)
return password
}
// Checks whether or not the correct password has been provided
func PasswordMatch(guess string, password *Password) bool {
salted_guess := combine(password.salt, guess)
// compare to the real deal
if bcrypt.CompareHashAndPassword([]byte(password.hash), []byte(salted_guess)) != nil {
return false
}
return true
}

view raw
authenticator.go
hosted with ❤ by GitHub

and the corresponding GoConvey tests:

package authentication
import (
"code.google.com/p/go.crypto/bcrypt"
. "github.com/smartystreets/goconvey/convey"
"log"
"strings"
"testing"
)
func TestSpec(t *testing.T) {
Convey("Authentication Testing", t, func() {
Convey("generateSalt()", func() {
salt := generateSalt()
So(salt, ShouldNotBeBlank)
So(len(salt), ShouldEqual, SaltLength)
})
Convey("combine()", func() {
salt := generateSalt()
password := "boomchuckalucka"
expectedLength := len(salt) + len(password)
combo := combine(salt, password)
So(combo, ShouldNotBeBlank)
So(len(combo), ShouldEqual, expectedLength)
So(strings.HasPrefix(combo, salt), ShouldBeTrue)
})
Convey("hashPassword()", func() {
combo := combine(generateSalt(), "hershmahgersh")
hash := hashPassword(combo)
So(hash, ShouldNotBeBlank)
cost, err := bcrypt.Cost([]byte(hash))
if err != nil {
log.Print(err)
}
So(cost, ShouldEqual, EncryptCost)
})
Convey("CreatePassword()", func() {
passString := "mmmPassword1"
password := CreatePassword(passString)
pass_struct := new(Password)
So(password, ShouldHaveSameTypeAs, pass_struct)
So(password.hash, ShouldNotBeBlank)
So(password.salt, ShouldNotBeBlank)
So(len(password.salt), ShouldEqual, SaltLength)
})
Convey("comparePassword", func() {
password := "megaman49"
passwordMeta := CreatePassword(password)
So(PasswordMatch(password, passwordMeta), ShouldBeTrue)
So(PasswordMatch("lolfail", passwordMeta), ShouldBeFalse)
So(PasswordMatch("Megaman49", passwordMeta), ShouldBeFalse)
})
})
}

Boom, a tested, working password authentication package! Not bad for an hour’s work.

As always, this is my first crack based on what I know, and what I read out there on the interwebs today about Golang best practices. Let me know if you see anything blatantly wrong here and I will make it not so blatantly wrong in case any poor fool uses my code.