Golang Project Structure

Tips and tricks for writing and structuring Go code

How to Generate Safe and Secure Passwords

Language

  • unknown

by

There are many services online that will claim to create unique, reliable and secure passwords for you, but can you trust other people’s code? It’s hard to know. Often you can trust the largest and most popular websites, but sometimes there are bugs or even intentional backdoors that will expose your generated passwords to the world.

A locked vault, traditionally used to keep money safe in the banking industry, can be seen as a metaphor for secure passwords.
Security is critically important.

If you write your own code, however, you can be sure that no one else will have access to the passwords that your program produces — unless you decide to give them away. Building your own secure password generator is certainly an interesting exercise in itself, if you want to practice your coding chops, but you’ll also end up with something that’s valuable in the real world. So that’s what I’m going to help you to do in this post.

A Random String of Letters

The simplest sort of relatively secure password is just made up of random letters — you have a one in twenty-six chance of picking any individual letter, which means that if you generate a password with a length of just ten letters, there are 141,167,095,653,376 different possibilities to choose from (26 to the power of 10). So it’s hard to imagine someone guessing that kind of secure password by pure chance.

The example below takes this approach, adding randomly selected letters to a slice of runes, then concatenating them together as a string:

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func randomRune() rune {
	i := rand.Intn(26)

	return rune('A' + i)
}

func init() {
	rand.Seed(time.Now().UnixNano())
}

func main() {
	const passwordLen = 10
	passwordRunes := make([]rune, passwordLen)

	for i := range passwordRunes {
		passwordRunes[i] = randomRune()
	}

	fmt.Println(string(passwordRunes))
}

The basic rune arithmetic in the randomRune function is similar to what we did when we worked with runes to create a Caesar cipher.

You can also see how we use an init function, which always runs before the main function, in order to seed the pseudo-random-number generator with the current time in nanoseconds: this ensures that we won’t get the exact same runes generated every time the program runs.

Adding More Characters

However, just using one type of character is probably a bad idea, if we want to avoid insecure passwords. Why use just upper-case letters when we can use lower-case ones and digits too? The goal is to make the final word as hard to guess (or attack by brute force) as possible, so it’s never a bad thing to have a wide and diverse range of characters used.

Digits in various sizes.
Numbers make the world go round.

In the code below, we have an equal chance of choosing between the three main categories of characters. A random floating-point value is generated and its value determines which category we shall pick our rune from. It’s important to note that this approach gives greater weight to digits, because there are only 10 of them while there are 26 characters in both the upper- and lower-case letters, yet we give each category an equal chance of being chosen. (The example under the next heading will solve this problem.)

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func randomRune() rune {
	f := rand.Float64()
	var min, max rune

	switch {
	case f < float64(1)/3:
		min, max = 'a', 'z'
	case f < float64(2)/3:
		min, max = 'A', 'Z'
	default:
		min, max = '0', '9'
	}

	delta := int(max - min)
	i := rand.Intn(delta + 1)

	return min + rune(i)
}

func init() {
	rand.Seed(time.Now().UnixNano())
}

func main() {
	const passwordLen = 32
	passwordRunes := make([]rune, passwordLen)

	for i := range passwordRunes {
		passwordRunes[i] = randomRune()
	}

	fmt.Println(string(passwordRunes))
}

If you look at the randomRune function, you can also see above how we calculate delta, which is the difference between the rune with the locally biggest integer value and the one with the locally smallest value. This variable is then used as the input to the pseudo-random-number generator. In the case of either upper- or lower-case letters, delta + 1 will equal 26; in the case of digits, it will be 10. This ensures that our returned rune will be between min and max.

Let’s Support Some Symbols

Instead of needlessly creating three separate categories of rune, it would be better if we treated them all equally, making sure that there’s the same chance of any single rune in our set appearing as another. So we’re going to create a collection to store the runes that are available to be used in our password: there are various kinds of data structure that we could use, but it seems simplest — and perhaps best — to use a slice for this purpose.

A computer keyboard in the dark with illuminated keys.
A secure password should use as many different characters as possible, to make sure that it’s harder for an adversary to guess. Not all of those characters have to correspond to a key on a computer keyboard: Unicode has many thousands of different characters, most of which cannot be directly typed.

So, in the code below, we do a lot more work in the init function — and a lot less in the randomRune function. Before the main function runs, we fill our availableRunes slice with the runes we want to use. This allows us to add arbitrary symbols, which may have any Unicode value, as well as letters and numbers, which just happen to have contiguous Unicode values. The larger the number of runes that we use, the more that we’re continuing to increase the extreme improbability of someone being able to crack our secure passwords.

package main

import (
	"fmt"
	"math/rand"
	"time"
	"unicode"
)

var availableRunes []rune

func randomRune() rune {
	i := rand.Intn(len(availableRunes))

	return availableRunes[i]
}

func init() {
	rand.Seed(time.Now().UnixNano())

	for r := 'A'; r <= 'Z'; r++ {
		availableRunes = append(availableRunes, r)
		availableRunes = append(availableRunes, unicode.ToLower(r))
	}

	for r := '0'; r <= '9'; r++ {
		availableRunes = append(availableRunes, r)
	}

	availableRunes = append(availableRunes, []rune{
		'!', '?', '-', '_', '=', '@', '$',
		'#', '(', ')', '[', ']', '{', '}',
	}...)
}

func main() {
	const passwordLen = 32
	passwordRunes := make([]rune, passwordLen)

	for i := range passwordRunes {
		passwordRunes[i] = randomRune()
	}

	fmt.Println(string(passwordRunes))
}

Our randomRune function now just has to select a random index from 0 to len(availableRunes)-1 (remember that the parameter given to the rand.Intn function determines the first positive integer that will never be chosen, not the last one that will). It then uses this index to return a random rune from our available set.

Structuring Our Code: Object-Oriented Programming

We’ve completed the necessary functionality now, since we already have a program that will produce a secure password of any length. But the code could be organized better. For example, in the previous example, if we want to change the length of our password, we have to change the passwordLen constant value, however, things would clearly be better if all of the data and methods relating to password generation were tied together. We can do this with the use of a struct.

package main

import (
	"fmt"
	"math/rand"
	"strings"
	"time"
	"unicode"
)

type PasswordGeneratorOptions struct {
	Len                   int
	AllowUpperCaseLetters bool
	AllowLowerCaseLetters bool
	AllowDigits           bool
}

type PasswordGenerator struct {
	availableRunes []rune
	options        *PasswordGeneratorOptions
}

func NewPasswordGenerator(options *PasswordGeneratorOptions) *PasswordGenerator {
	pg := &PasswordGenerator{
		options: options,
	}

	pg.initAvailableRunes()

	return pg
}

func (pg *PasswordGenerator) initAvailableRunes() {
	if pg.options.AllowUpperCaseLetters {
		for r := 'A'; r <= 'Z'; r++ {
			pg.availableRunes = append(pg.availableRunes, r)
		}
	}

	if pg.options.AllowLowerCaseLetters {
		for r := 'a'; r <= 'z'; r++ {
			pg.availableRunes = append(pg.availableRunes, r)
		}
	}

	if pg.options.AllowDigits {
		for r := '0'; r <= '9'; r++ {
			pg.availableRunes = append(pg.availableRunes, r)
		}
	}
}

func (pg *PasswordGenerator) generateRune() rune {
	i := rand.Intn(len(pg.availableRunes))

	return pg.availableRunes[i]
}

func (pg *PasswordGenerator) Generate() string {
	var builder strings.Builder

	for i := pg.options.Len - 1; i >= 0; i-- {
		builder.WriteRune(pg.generateRune())
	}

	return builder.String()
}

func init() {
	rand.Seed(time.Now().UnixNano())
}

func main() {
	pg := NewPasswordGenerator(&PasswordGeneratorOptions{
		Len:                   32,
		AllowUpperCaseLetters: true,
		AllowLowerCaseLetters: true,
		AllowDigits:           true,
	})

	fmt.Println(pg.Generate())
}

You can see above how I also created a struct that allows us to pass options into the constructor function, returning the struct that we will use to generate our secure passwords. Now, in the main function, we only need to call the constructor and call the Generate method on it. All of the implementation details are hidden and abstracted away from the end user, making it much nicer to work with.

Whenever you write code that other people may use, you should try to make sure that it’s not necessary for them to know any unnecessary details — just keep the public functions as simple to understand as possible and they’ll thank you for saving them time. Your code should be a black box that has obvious inputs and outputs.

Use My Package on Github to Generate Secure Passwords

Inspired by this blog post, I’ve created a package that you can import into your own projects. It allows you to decide exactly which runes you want to be included or excluded from your randomly generated password; you can also set the length, since longer passwords — all other things being equal — tend to be more secure.

Here’s an example that makes use of all the options available:

package main

import (
	"fmt"

	passwordGenerator "github.com/theTardigrade/golang-passwordGenerator"
)

func main() {
	pg := passwordGenerator.New(
		passwordGenerator.Options{
			Len:                     128,
			IncludeUpperCaseLetters: true,
			IncludeLowerCaseLetters: true,
			IncludeDigits:           true,
			IncludeRunesList: []rune{
				'!', '?', '-', '_', '=', '@', '$',
				'#', '(', ')', '[', ']', '{', '}',
				'<', '>', '+', '/', '*', '\\', '/',
				':', ';', '&', '\'', '"', '%', '^',
				'🙂', '🙃',
			},
			ExcludeAmbiguousRunes: true,
			ExcludeRunesList:      []rune{'X', 'x'},
		},
	)

	password, err := pg.Generate()
	if err != nil {
		panic(err)
	}

	fmt.Printf("Password:\n\t%s\n", password)
}

You can also call the pg.GenerateMany(500) function, where 500 is the number of different secure passwords that you want to generate at once; it will return a slice containing the results and an error value. This allows you to create passwords for multiple people or websites at once — or simply to be given a list of potential passwords from which you can choose.

Within the two password-generation methods, I make use of the "crypto/rand" package, which provides more cryptographically secure sources of pseudo-random numbers than the "math/rand" package that we used in this blog post.

My code is open-source, so you can view or modify it whenever you want. Or you can simply clone the repository and use it to create your own long and impenetrably secure passwords. It would also, of course, be very easy for you to create your own command-line program that imports my package, enabling you to call it whenever you need to register for a new service and enter a new password.

Leave a Reply

Your email address will not be published.

  1. ayan says:

    why name your package golang-passwordGenerator only to have to rename it on import?

    why camel case your package name?

    1. The package name is always “passwordGenerator”. However, the Github repository is “golang-passwordGenerator”, because that makes it easy for me to distinguish between future repositories, which may be “javascript-passwordGenerator” or “rust-passwordGenerator” etc.

      Using camel case comes natural to me. Official Go packages encourage the use of a single word as the name, without camel or snake case, but it seems to me that using more than one word can be usefully descriptive for non-official packages.

      It’s all just a matter of style anyway. You’re welcome to call your packages whatever you like!

  2. ayan says:

    i’m sorry — i recognize that i can call my packages whatever i want.

    it just seemed a little unusual for a package to be renamed by design.

    that’s all.

    1. Not a problem. Sorry if I sounded snarky. It’s a very reasonable point you made. Thanks for commenting!