How to Generate Safe and Secure Passwords
Language
- unknown
by James Smith (Golang Project Structure Admin)
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.
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.
Table of Contents
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.
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.
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.
why name your package golang-passwordGenerator only to have to rename it on import?
why camel case your package name?
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!
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.
Not a problem. Sorry if I sounded snarky. It’s a very reasonable point you made. Thanks for commenting!