Wrapping packages to isolate code responsibility

When writing code in Go, or really any language, you will often find yourself using other packages to get things done. For example, you might use the net/http package as a basis for building a web server rather than writing all that code yourself.

From time to time you might notice that the third party package doesn’t quite behave in a manner that is aligned with how you intend to use the package. One example of this is the golang.org/x/crypto/bcrypt package; When I use it, I am often dealing with strings in my own application, but every method in the crypto/bcrypt package expects a byte slice as input. While this isn’t necessarily a problem, it can lead to code duplication that is unnecessary.

Another frequent occurrence is wanting to add some functionality on top of what is being offered by another package. Looking at the crypto/bcrypt package once again, this is a package that handles hashing and salting passwords, but it would be especially great if the package took care of peppering our passwords as well.

In this article we are going to explore a technique that can be used to simplifying your code that interacts with third party libraries by wrapping those packages into your own package. We will be doing this using the crypto/bcrypt package, but the same pattern can be applied to other packages and types.

Using the bcrypt package

Before we can jump into wrapping the crypto/bcrypt package we first need to take a look at how you might use it inside of an existing application. To do this, we are going to look at two different functions provided by the crypto/bcrypt package:

We are also going to assume that our application is making use of a pepper variable, which is similar to a salt but is application-wide and is intended to help prevent attackers from getting passwords when they only get access to a database and not the actual source code. We will also assume that we are dealing with strings, so both the incoming raw password and the final hashed password will need to be strings.

Putting that all together, you might end up with some code like below to generate your hashed password.

rawPassword := "some-pw"
pepper := "some-pepper"

hashedBytes, err := bcrypt.GenerateFromPassword(
  []byte(rawPassword + pepper), bcrypt.DefaultCost)
if err != nil {
  // This really shouldn't happen unless we provide an invalid bcrypt cost or
  // are doing something else very wrong
}

// Use the hashedBytes, or convert to a string
hashedString := string(hashedBytes)

And then you might write something like the code below to compare your password to a hashed one. For simplicity’s sake, we are just assigning the hashed password to a string, but in most practical cases you would get this from a datastore of some sort.

rawPw := "some-pw"
pepper := "some-pepper"
hashedPw := "$2a$10$aSjYOncUeas4wV3e5vjb8eBT4lKrev3vMjvTATAYlGqY3m6RHnHFa"
err := bcrypt.CompareHashAndPassword(
  []byte(hashedPw),
  []byte(rawPw+pepper))
if err != nil {
  // Passwords don't match - tell the user the login was invalid!
}
// The passwords match if no error!

Now that we know what we are working with we can start to look for ways to wrap the crypto/bcrypt package and simplify our code.

Figure out what common tasks you do a lot

The first thing I suggest doing is looking for common tasks that you are performing in order to utilize the package. In this case, it looks like we are converting between byte slices and strings a good bit. Another thing we are doing is messing with a pepper a lot - when we create and compare passwords we are appending a pepper to the strings every time. One final thing that we might consider is changing the return value of the the CompareHashAndPassword() function; I often end up treating this value as a true/false statement (checking if it is nil), so we will be updating that function to return a bool rather than an error.

Now that we know have an idea of what code we are writing a lot we can start to look at how we might simplify this.

Support those common tasks in your own package

The first thing we are going to do is declare a new type inside of our own hash package. In many cases you won’t need to create a new type and can instead just offer functions to get the job done, but in this case I feel that a struct is more appropriate because we are going to store a pepper in the struct. Doing this will make it much easier to just pass around a copy of our BCrypt object to code that needs to handle passwords, and they won’t have to deal with a pepper directly.

package hash

// We will use this shortly, so I am showing it for now.
import "golang.org/x/crypto/bcrypt"

func NewBCrypt(pepper string) BCrypt {
  return BCrypt{[]byte(pepper)}
}

type BCrypt struct {
  pepper []byte
}

I also created a NewBCrypt() function to be used when constructing a new BCrypt object, which handles converting the incoming pepper string into a byte slice. Again, this is useful because in my application I typically tend to be working with strings in stead of byte slices for my pepper.

Next we will implement a function used to compare a raw password with a hashed password. In the crypto/bcrypt package this function is titled CompareHashAndPassword(), but we are free to name it whatever we want, and I tend to prefer using something a little shorter like Equal(). As I mentioned before, this will return a boolean instead of an error.

func (bc BCrypt) Equal(hash, rawPw string) bool {
  err := bcrypt.CompareHashAndPassword(
    []byte(hash),
    append([]byte(rawPw), bc.pepper...))
  if err == nil {
    return true
  }
  if err == bcrypt.ErrMismatchedHashAndPassword {
    return false
  }
  // If this happens it means our password hash isn't valid
  // or some other unrecoverable error happened.
  panic(err)
}

Obviously this isn’t the most performant code in the world because it has to convert strings to bytes, but if we are already working with strings in our application chances are we would need to do this anyway, and this does make dealing with our strings much easier.

Moving on, we are going to create a method used to hash our passwords - aptly named Hash(). Once again, this will deal with strings instead of byte slices, but in this case we will still return any errors that occur because our upstrem code will likely need to know about them.

func (bc BCrypt) Hash(input string) (string, error) {
	bytes, err := bcrypt.GenerateFromPassword(
		append([]byte(input), bc.pepper...),
		bcrypt.DefaultCost)
  if err != nil {
    return "", err
  }
  return string(bytes), nil
}

Now that we have our new BCrypt type written, let’s go back to our original code and see how we might use this to simplify our original code.

What does it look like to use our new type?

The first thing we need to do is construct a new instance of our BCrypt type. That will give us the ability to simply pass this around instead of passing around our pepper.

bc := NewBCrypt("some-pepper")

After that we can utilize the bc object to hash our password.

rawPw := "some-pw"
hashedPw, err := bc.Hash(rawPw)
if err != nil {
  panic(err)
}

And finally we can use the bc object to compare our hashed password with a raw password to determine if the user has provided us with valid credentials.

if bc.Equal(hashedPw, rawPw) {
  fmt.Println("They match!")
}

Putting it all together we get something like the code below.

package main

import (
	"fmt"

	"calhoun.io/hash"
)

func main() {
	bc := hash.NewBCrypt("some-pepper")
	rawPw := "some-pw"
	hashedPw, err := bc.Hash(rawPw)
	if err != nil {
		panic(err)
	}

	if bc.Equal(hashedPw, rawPw) {
		fmt.Println("They match!")
	}
}

Now that is much cleaner. Our code no longer has to think about a bcrypt cost, adding a pepper to passwords, or even converting strings to byte slices. This is really important for maintaining code in because it means that any future changes that might alter the cost you are using for bcrypt won’t need to be spread across multiple files, but instead could be a single change to the BCrypt type. You could also make the cost an argument when calling NewBCrypt.

You can do this for many different packages

As I said before, you can do this for a large variety of packages. For example, you might find that you need to use HMAC in your code, but you need to store a base64 encoded version of the HMACs rather than a byte slice. That is easily achieved with the code below.

package hash

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"hash"
)

func NewHMAC(key string) HMAC {
	h := hmac.New(sha256.New, []byte(key))
	return HMAC{h}
}

type HMAC struct {
	hash.Hash
}

func (h HMAC) Equal(hash, raw string) bool {
	hashMac, err := base64.URLEncoding.DecodeString(hash)
	if err != nil {
		// Invalid MAC provided
		return false
	}
	rawMac := h.Bytes([]byte(raw))
	return hmac.Equal(hashMac, rawMac)
}

func (h HMAC) Bytes(input []byte) []byte {
	h.Reset()
	h.Write(input)
	return h.Sum(nil)
}

func (h HMAC) String(input string) string {
	// Leverage the existing Bytes() method
	return base64.URLEncoding.EncodeToString(h.Bytes([]byte(input)))
}

Or you might have a custom type and decide that you want to make sorting slices of that type easier. No problemo! We don’t even have to export a new type to achieve this.

package sort

import (
	"sort"

	"calhoun.io/things"
)

// calhoun.io/things defines the Dog type:
//
// type Dog struct {
//   Age int
// }

func Dogs(a []things.Dog) {
	sort.Sort(dogs(a))
}

type dogs []things.Dog

func (d dogs) Len() int {
	return len(d)
}

func (d dogs) Less(i, j int) bool {
	return d[i].Age < d[j].Age
}

func (d dogs) Swap(i, j int) {
	d[i], d[j] = d[j], d[i]
}

In summary…

The next time you finish writing some code, I encourage you to take a moment to consider how you could clean up your code and make it more maintainable.

While this pattern not always be a great fit, it is a great way to help isolate responsibilities inside your application, which typically leads to a more maintainable codebase.

Learn Web Development with Go!

Sign up for my mailing list and I'll send you a FREE sample from my course - Web Development with Go. The sample includes 19 screencasts and the first few chapters from the book.

You will also receive emails from me about Go coding techniques, upcoming courses (including FREE ones), and course discounts.

Avatar of Jon Calhoun
Written by
Jon Calhoun

Jon Calhoun is a full stack web developer who teaches about Go, web development, algorithms, and anything programming. If you haven't already, you should totally check out his Go courses.

Previously, Jon worked at several statups including co-founding EasyPost, a shipping API used by several fortune 500 companies. Prior to that Jon worked at Google, competed at world finals in programming competitions, and has been programming since he was a child.

Related articles

Spread the word

Did you find this page helpful? Let others know about it!

Sharing helps me continue to create both free and premium Go resources.

Want to discuss the article?

See something that is wrong, think this article could be improved, or just want to say thanks? I'd love to hear what you have to say!

You can reach me via email or via twitter.

Recent Articles All Articles Mini-Series Progress Updates Tags About Me Go Courses

©2024 Jonathan Calhoun. All rights reserved.