aboutsummaryrefslogtreecommitdiffstats
path: root/go/makepass.go
blob: 542b63e22988f70ec2aa0627456843a2e22a30af (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package main

import (
	"bufio"
	"fmt"
	"golang.org/x/term"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

// Basic constant definitions
const (
	normNum = 10 // Number of normal passwords
	specNum = 6  // Number of special passwords
	passNum = 6  // Number of passphrases

	maxLength = 255 // Maximum length of passwords
	rangeMax  = 42  // Maximum range for password length
	rangeMin  = 8   // Minimum range for password length
	passWords = 8   // Number of words in passphrases

	lower = "abcdefghijklmnopqrstuvwxyz"
	upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	digit = "0123456789"
	other = "!$%&#/()=?+-_,.;:<>[]{}|@*" // Special characters for special passwords

	defaultFile = "/usr/share/dict/words" // Default path to the file of words used for passphrases
)

// Variables for different types of passwords
var (
	alpha    []string = strings.Split(lower+upper, "")             // Array of alphabets (lowercase and uppercase)
	alnum    []string = append(alpha, strings.Split(digit, "")...) // Array of alphanumeric characters
	every    []string = append(alnum, strings.Split(other, "")...) // Array of all characters (alphabet, digit and special)
	normal   []string = append(alnum, "-", "_")                    // Characters for normal passwords
	special  []string = every                                      // Characters for special passwords
	wordlist string   = defaultFile                                // Path to a dictionary to use for passphrases
	length   int      = 0                                          // Length of passwords
)

func main() {
	//
	// Error handling
	//
	var err error

	// Checking command line argument and environment variable for password length
	if len(os.Args) == 2 {
		length, err = strconv.Atoi(os.Args[1])
	} else if len(os.Getenv("MAKEPASS_DEFAULT_LENGTH")) > 0 {
		length, err = strconv.Atoi(os.Getenv("MAKEPASS_DEFAULT_LENGTH"))
	}

	// If there is an error in conversion, or if the length is not within limits, exit the program with an error message
	if err != nil || (length > maxLength || length < 0) {
		fmt.Printf("Error: Argument must be a whole number between 0 and %d.\n", maxLength)
		os.Exit(1)
	}

	// Get wordlist from env
	if len(os.Getenv("MAKEPASS_WORDLIST")) > 0 {
		wordlist = os.Getenv("MAKEPASS_WORDLIST")
	}

	// Check if the wordlist file exists
	if _, err := os.Stat(wordlist); os.IsNotExist(err) {
		fmt.Println("The file", wordlist, "does not exist")
		os.Exit(1)
	}

	//
	// move on
	//

	// initialise the random seed
	// TODO: Get seed from /dev/random, like we do in the perl- and zsh-versions?
	rand.Seed(time.Now().UnixNano())

	// get screen width
	termWidth, _, err := term.GetSize(0)

	// Read wordlist from file
	file, err := os.Open(wordlist)
	if err != nil {
		fmt.Printf("failed to open file: %s", err)
	}
	scanner := bufio.NewScanner(file)
	scanner.Split(bufio.ScanLines)
	var text []string
	for scanner.Scan() {
		text = append(text, scanner.Text())
	}
	file.Close()

	//
	// print passwords
	//

	// Generate and print normal and special passwords
	printColumns("Normal passwords:", 10, normal, termWidth)
	fmt.Println("")
	printColumns("Passwords with special characters:", 6, special, termWidth)
	fmt.Println("")

	// Generate and print passphrases
	for i := 0; i < passNum; i++ {
		fmt.Println(passphrase(&text))
	}
}

// Function to generate a random string of given length from given characters
func randstring(chars []string) string {
	l := length
	if length == 0 { // Random length if not specified
		l = rand.Intn(rangeMax-rangeMin) + rangeMin
	}
	var str strings.Builder

	// Add random characters to str
	for i := 1; i <= l; i++ {
		if i == 1 || i == l { // don't want passwords to start or end with special characters
			str.WriteString(alnum[rand.Intn(len(alnum))])
		} else {
			str.WriteString(chars[rand.Intn(len(chars))])
		}
	}
	return str.String()
}

// Function to generate and print passwords
func printColumns(title string, num int, chars []string, termWidth int) {

	// column width
	colWidth := rangeMax + 2
	if length > 0 {
		colWidth = length + 2
	}

	// number of colums
	columns := termWidth / colWidth
	if columns == 0 {
		columns = 1
	}

	// Print title
	fmt.Println(title)

	// Print passwords in columns
	for i := 1; i <= num; i++ {
		fmt.Printf("%-[1]*[2]s", colWidth, randstring(chars))
		if i%columns == 0 || (i == num && i%columns > 0) { // Add newlines
			fmt.Println("")
		}
	}
}

// Function to generate passphrases
func passphrase(arrh *[]string) string {
	text := *arrh
	var str strings.Builder
	for i := 0; i < passWords; i++ {
		str.WriteString(text[rand.Intn(len(text))]) // Write a random word to the passphrase
		if i != passWords-1 {
			str.WriteString("-") // Add hyphen between words
		}
	}
	return str.String()
}