Screaming at the Silicon

Archive - Fedi

Creating a Diceware Password Generator in Go

This post was initially written sometime in early 2020 as a way to work through a Go hobby project that has since been archived.

Everywhere you turn you are asked to create new login credentials and best practices recommend unique passwords for each service. Password managers have largely solved this problem but sometimes you need a unique, strong, and memorable password that doesn’t require digging through your password vault. My solution to this was to use the diceware method. I found myself doing it so often manually that I ended up writing a small program that I could call from my command line. This tutorial implements a very slim version of my open source gophrase project Getting Started

Just as a heads up, it should be noted that this tutorial was developed inside of Pop!_OS. Because of this, you will need to be aware that you might have to modify some of the instructions in this tutorial to conform to your specific development environment. I develop in Goland which I find does a decent job of managing packages. Goland can be accessed for free by students, but for others seeking a free solution I recommend Visual Studio Code, which is a comparable product..

Alternatively, if you are using a text editor like Sublime make sure you label the top of your Go files with the proper package name (ex: package main) and also managing your imports.

If you would like to see what the final product looks like there is a branch of the tutorial, but I would recommend working through the tutorial and troubleshooting on your own.

You can also view the full gophrase repository.

Before we begin, this tutorial assumes that you have Go setup on your machine and understand the basics of getting that working in your environment. If you have not reached that point, I encourage following the Get Started With Go tutorial.

Project Setup

  1. From the root directory of your project run go mod init.
  2. Create three directories: assets, cmd, and internal
  3. In your cmd directory, create a directory with the project name.
  4. Then, inside of that directory, create a main.go file.

Notes on File Organization

For example projects, it’s generally fine to leave all the files in the root of the directory or even storing all of the functions within the main.go file; however, for production projects you will want to organize your projects differently. I recommend reading the golang-standards repository, which I use as a model for my projects. You might also want to read this brief explainer on go folder structure.

Picking A Word List

Choose a word list from this repository of JSON-formatted word lists. You can find more helpful information in Electronic Frontier Foundation explainer, which outlines the differences between these lists. After you’ve selected your word list, place your chosen JSON file in the assets directory. Inside of your internal directory create a wordlist.go file.

You will need a way to load the JSON file into our binary at compile time. For this, we're going to use packr.

In your terminal, run go get github.com/gobuffalo/packr and then import it into the file.

func getCorpus() []byte {                       
   box := packr.New("assets", "../assets")                     
   fileLocation, err := box.Find("eff_short_wordlist_2_0.json")                             
   if err != nil {                       
      log.Fatal(err)                       
   } 
    return fileLocation                       
}

Note: As of Go 1.16 embedding files into the binary is something that has first class support through the embed library. You can use packr to accomplish this or you can reference an example I wrote here

Code Breakdown

Line 1 of the code is our method signature. When the function getCorpus() is called it is expected to return an array of bytes.
Line 2 of the function uses packr to define where our static assets are located.
Line 3 is the declared variable fileLocation. What is happening here is that the code loads the json into a byte array which will become our return value.
In line 4, we check for an error because packr's Find() function requires an error to be returned.
In line 5, when Find() returns an error, the error is printed to the console.
In line 7, a byte array is returned as required by the method signature.

Select A Word from Your Wordlist

Here we build the function that does our word selection.

func GetWord(key int) string {                       
   words := make(map[int]string)                       
   corpus := getCorpus()                       
   err := json.Unmarshal(corpus, &words)                       
   if err != nil {                       
      log.Fatal(err)                       
   }                       
   return words[key]                       
}

Code Breakdown

Line 1 is, again, the method signature. It requires a key and outputs a string. The key is used to locate the associated word within our word list. This function is PascalCased instead of camelCased because in Golang, in order to make a method or variable usable across packages, you must capitalize the first word in the name.

Line 2 declares a words variable which is an array with an integer index. This allows us to locate the words by passing in a value (ex: 0,1,2,3,etc).

Line 3 calls getCorpus() which returns a byte array.

Line 4 unmarshals the byte array into a pointer to the words variable declared in line 2. Here there is a & before the words variable because the variable needs to be de-referenced to pass it to the json.Unmarshal() function.

Lines 5 -7 check and log errors if they appear.

In line 8, we return a word from the corpus based on the key integer that was passed into the function.

Create A Random Key

In order to select a random word, a four-digit key is needed to create a random key to feed into the GetWord() function. Inside of your internal directory create a generate.go file.

func makeKey() int {                       
   var key int                       
   onlyOnce.Do(func(){                       
      rand.Seed(time.Now().UnixNano())                       
   })                       
   die := []int{1,2,3,4,5,6}                       
   for i := 0; i < 4; i++ {                       
      key = (key*10) + die[rand.Intn(len(die))]                       
   }                       
   return key                       
}

Code Breakdown

This method signature requires an integer to be returned, which will be the key we use to locate words from the corpus.

In line 2, the variable key is defined as an integer.

In line 3-5, the randomness seed is generated.

In line 6, the variable die is instantiated as an array of integers. This variable name is derived from the fact that the method of generating these passwords is based on rolling literal dice. For coding purposes, though, we only need a singular die.

In lines 5-7, a for loop generates a random four-digit key.

In line 8, the key variable is generated.

In line 10, the key variable is returned.

Notes

If the word list you selected has five-digit keys associated with the words, then in the for loop in line 7 be sure to change i < 4 to i < 5. Alternatively, you can modify this to accept a key value of arbitrary length. Additionally, I wrote this function using Golang’s math/rand package. For production environments it is recommend to use the crypto/rand package. This is discussed in the gophrase README

Generate Passphrase

Now we get to actually generate our passphrase. Place this next function inside of generate.go.

func GeneratePassword(wordCount int) string {                  
   var password []string                       
   for i := 1; i <= wordCount; i++ {                       
      key := makeKey()                       
      word := GetWord(key)                       
      password = append(password, word)                       
   }                       
   return strings.Join(password[:], "")                       
}

Code Breakdown

Line 1 method signature is exported and requires an integer to determine how many words will be in our passphrase and then returns our passphrase as a string.

Line 2 declares a password variable as a string array.

Line 3 begins a for loop that ends after i is greater than or equal to the wordCount integer passed into the function.

Line 4 declares the variable key and then gets a random key from the makeKey() function.

Line 5 declares a variable word that is set by the GetWord() function. The word returned is based off of the key we generated in line 4.

Line 6 appends the word variable to the password array we generated in line 2.

Line 8 returns the password array as a single string. In order to satisfy the requirements of the method signature, a string must be returned so the strings library is called to concatenate the array into a single string.

At this point all of the back-end logic of the program is written, but how do we actually use it? The following steps focus on creating a command line interface for interacting with the program.

Create the Command Line Interface

Inside of the internal directory, create a command.go file.

For the command line interface, I will be using a library I greatly enjoy called urfav/cli. This library makes creating command line tools a breeze.

In your terminal run go get github.com\urface\cli

var Commands = []*cli.Command{                       
   {                       
      Name:    "GeneratePassword",                       
      Aliases: []string{"gen"},                       
      Usage:   "gen  <int>",                       
      Action: func(c *cli.Context) error {                       
      i, err := strconv.Atoi(c.Args().Get(0))                         
      if err != nil {                       
          log.Fatal(err)                       
      }                       
      password := GeneratePassword(i)                       
      fmt.Println(password)                       
      return nil                       
     },                       
   },                    
}

In this section a variable that contains all of the commands is created. So far, only one command is needed. The variable is an array of pointers to the Command struct.

Code Breakdown

The first variable in the struct is the name of the command. The second variable will be its alias, the third will be an example of usage, and the fourth is the actual action. Action is a function, so it’s worth breaking down further.

Line 6 is an in-line function declaration which takes in c which is a pointer to the command line context — i.e. whatever is typed into the terminal — and it returns an error.

In line 7 i is declared and the string entered in the terminal is converted to an integer. This number represents how long we want the passphrase to be. Also note the error handling in lines 8-10.

In line 11 password is defined as the string that is returned from GeneratePassword()

In line 12 the passphrase is printed to the terminal

In line 13 nil is returned because there are no errors and the function requires an error to be returned.

Create Your main.go File

Place the following code block in your main.go file.

func main() {                       
   app := *cli.NewApp()                       
   app.Name = internal.APP_NAME                       
   app.Usage = internal.APP_USAGE                       
   app.UsageText = internal.APP_USAGETEXT                       
   app.Version = internal.APP_VERSION                          
   app.HideHelp = false                       
   app.HideVersion = false                       
   app.Commands = internal.Commands                        
   err := app.Run(os.Args)                       
   if err != nil {                       
     log.Fatal(err)                       
   }                       
}

Code Breakdown

The method signature is simply main() which is required of all Go programs.

In line 2 the app variable is created and it’s a pointer the urfav/cli’s NewApp() function.

In lines 3 - 7, a bunch of variables are defined. In the code block there are several constants being called that have not been set up yet. You can also choose to put strings for each of these, but if the constant method is preferable do the following:

Create a constant.go file

Define the strings in there. Below is an example.

const APP_NAME = "Go Phrase"                      
const APP_USAGE = "CLI for generating secure, memorable passwords"                       
const APP_VERSION = "v0.0.1 | release: 00.4.18.20"                       
constAPP_USAGETEXT="gophrase <int>"

Back in the main function , set both HideHelp and HideVersion to false. We want both of those to be visible

In line 10, the application app.Commands variable is assigned the from the value of the variable internal.Commands.

In lines 11-14, the application is run and the program checks for errors.

At this point, we have written everything needed to get this running, so let’s try it out.

Run the Program

In your terminal, switch to the project’s cmd/project directory and run go build.

After build is complete run./gophrase gen 3. It should return a random, three word passphrase.

Your binary may have a different name depending on what your project name is.

The rest of the steps that follow describe how to make this binary work outside of your project directory. If this isn’t your goal, feel free to continue to the conclusion.

Package Static Assets

If we want to add the binary to our path so we can generate memorable passwords from the command line, we will need to embed our JSON file as a static asset. To do that we use packr.

In your terminal, run go get github.com/gobuffalo/packr.

In your terminal run packr2

It may be necessary to run the above command from inside of the internal directory.

Now switch to the cmd/project directory and run go build.

run packr2 clean

Test the Binary

Move the binary outside of your project repository and verify that it works by running ./gophrase gen 3 Make sure that when running that command you are in the same directory as the binary file.

Add to Path

Ensure that you have a ~/bin directory and place the gophrase binary there. Once that is done the program should be callable no matter which directory you’re sitting in within the terminal.

Conclusion

We now have a working binary that we can add to our path to quickly generate memorable passwords. If you liked this project, want to download or fork it, or are just curious about how it has evolved since this tutorial was written, you can find the gophrase source code at github.

References

Here is a list of references that have been linked throughout the tutorial. If any of the links have since broken, I hope the citations below can help locate any missing resources.

Bonneau, Joseph. 2018. “EFF's New Wordlists for Random Passphrases.” Electronic Frontier Foundation. February 21. https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases.

Copes, Flavio. 2017. “Generating Random Numbers and Strings in Go.” Flavio Copes. flaviocopes.com. July 22. https://flaviocopes.com/go-random/.

“The Diceware Passphrase Home Page.” 2020. Diceware Passphrase Home. Accessed April 18. http://world.std.com/~reinhold/diceware.html.