-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpassgen.go
204 lines (172 loc) · 4.91 KB
/
passgen.go
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// Friend!
// Copyright 2016 Joubin Houshyar. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// package passgen provides a basic secure password generator. The
// generated passwords are not memorable but are be highly secure.
//
// The generated passwords are of arbitrary length, and conform to
// a basic set of password policies.
package passgen
import (
"crypto/sha512"
"fmt"
"io"
"math/rand"
"time"
)
/// generator ///////////////////////////////////////////////////////////
// password policies
const (
Printable = "p" // any printable character in range (33, 126)
Alpha = "a" // mixed case roman alphabet letters
Numeric = "n" // numeric digits in range (0, 9)
Alphanumeric = "an" // Alpha and Numeric policies combined.
)
// type Spec encapsulates Generator initialization spec.
type Spec struct {
Policy string
SeedPhrase string // optional - may be zerovalue/""
SpecialChars string // optional - may be zerovalue/""
NoRep bool // disallow repeated sequences
}
// Password generator type
type Generator struct {
spec Spec
random io.ByteReader
filter Filter
}
// Creates a new Generator with given policy. The provided generator uses
// the OS provided entropy source /dev/random if parameter 'seedPhrase' is
// zerovalue (""). If a seedPhrase is provided, then an OS agnostic entropy
// source is used. The provided seedPhrase must be 8 or more characters in
// length.
//
// Note that OS based generator must be disposed to close the underlying OS
// file. See Generator.Dispose()
func New(spec Spec) (*Generator, error) {
filter, e := newFilter(spec)
if e != nil {
return nil, fmt.Errorf("filter init - %s", e)
}
random, e := getRandomSource(spec.SeedPhrase)
if e != nil {
return nil, fmt.Errorf("entropy source init - %x", e)
}
return &Generator{spec, random, filter}, nil
}
// Generates a password of the specified length. An error condition by
// this method is typically unexpected and should be trated as a system
// level fault.
func (p *Generator) Generate(size int) (string, error) {
var password = make([]byte, size)
var last uint8 = 127 // DEL can be used as zerovalue
for i := 0; i < size; {
b, e := p.random.ReadByte()
if e != nil {
return "", fmt.Errorf("unexpected error reading from random source - %s", e)
}
c := uint8(b) % 94
c += 33
if p.filter.accept(c) {
if !p.spec.NoRep || last != c {
password[i] = byte(c)
last = c
i++
}
}
}
return string(password), nil
}
/// random source ///////////////////////////////////////////////////////
// Returns an entorpy source supporting the io.ReadCloser. If seedPhrase
// is true, will use a cryptographic hash based source. Otherwise the OS
// provided /dev/random is used.
func getRandomSource(seedPhrase string) (io.ByteReader, error) {
return newEntropySource(seedPhrase)
}
// seedPhrase entropy source type
type entropy struct {
prng *rand.Rand
pool [64]byte
offset int
}
func newEntropySource(seedPhrase string) (io.ByteReader, error) {
prng, e := newRand(seedPhrase)
if e != nil {
return nil, e
}
return &entropy{prng: prng, offset: 64}, nil
}
// use time and provided seedPhrase to source a new prng.
func newRand(seedPhrase string) (*rand.Rand, error) {
if len(seedPhrase) < 8 {
return nil, fmt.Errorf("'seedPhrase' must be at least 8 characters")
}
b := []byte(seedPhrase)
seed := time.Now().UnixNano()
shift := []uint{0, 8, 16, 24, 32, 40, 48, 56}
for i, c := range b {
c0 := ^int64(c) << shift[i%8]
seed ^= c0 | (seed >> shift[i%8])
}
seed = (seed << 33) | (seed >> 31)
return rand.New(rand.NewSource(seed)), nil
}
// support for io.ByteReader
func (p *entropy) ReadByte() (b byte, err error) {
if p.offset == len(p.pool) {
p.pool = sha512.Sum512([]byte(fmt.Sprintf("%d%v", p.prng.Int63(), time.Now())))
p.offset = 0
}
b = p.pool[p.offset]
p.offset++
return
}
/// filter //////////////////////////////////////////////////////////////
type Filter [256]bool
func (filter Filter) accept(c uint8) bool {
return filter[c]
}
func (filter *Filter) initAlpha() {
for i := 65; i < 91; i++ {
filter[i] = true
filter[i+32] = true
}
}
func (filter *Filter) initNumeric() {
for i := 48; i < 58; i++ {
filter[i] = true
}
}
func (filter *Filter) initAlphaNumeric() {
filter.initAlpha()
filter.initNumeric()
}
func (filter *Filter) addExtended(specialChars string) {
for _, b := range specialChars {
c := uint8(b)
filter[c] = true
}
}
func (filter *Filter) initPrintable() {
for i := 33; i < 127; i++ {
filter[i] = true
}
}
func newFilter(spec Spec) (filter Filter, err error) {
switch spec.Policy {
case Printable:
filter.initPrintable()
case Alpha:
filter.initAlpha()
case Numeric:
filter.initNumeric()
case Alphanumeric:
filter.initAlphaNumeric()
default:
err = fmt.Errorf("unknown policy flag %q", spec.Policy)
}
filter.addExtended(spec.SpecialChars)
return
}