/
password.go
112 lines (100 loc) · 2.2 KB
/
password.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
package poplop
import (
"bytes"
"crypto/md5"
"crypto/sha512"
"encoding/ascii85"
"encoding/base64"
"errors"
"io"
"strconv"
"strings"
"unicode"
)
type Scheme struct {
Name string `json:"name"`
Counter int `json:"counter,omitempty"`
Username string `json:"username,omitempty"`
URL string `json:"url,omitempty"`
Notes string `json:"notes,omitempty"`
Forbidden string `json:"forbidden,omitempty"`
MaxLength int `json:"max_length,omitempty"`
Legacy bool `json:"legacy,omitempty"`
// "!\"#$%&'()*+,-./:;<=>?@[\\]^_'",
}
func (n Scheme) mapping() func(rune) rune {
return func(r rune) rune {
if strings.ContainsRune(n.Forbidden, r) {
return -1
}
return r
}
}
func (n Scheme) Hash(master string) (string, error) {
if n.Legacy {
return oplop(master, n)
}
return poplop(master, n)
}
func oplop(master string, n Scheme) (string, error) {
h := md5.New()
io.WriteString(h, master)
io.WriteString(h, n.Name)
if n.Counter > 0 {
io.WriteString(h, strconv.Itoa(n.Counter))
}
str := requireDigit(base64.RawStdEncoding.EncodeToString(h.Sum(nil)))
if n.Forbidden != "" {
str = strings.Map(n.mapping(), str)
}
length := 8
if n.MaxLength > 0 && n.MaxLength < length {
length = n.MaxLength
}
if len(str) < length {
return "", errors.New("hash not long enough")
}
return str[:length], nil
}
func requireDigit(pass string) string {
var digits bytes.Buffer
for i, r := range pass {
if unicode.IsDigit(r) {
if i < 8 {
return pass
}
digits.WriteRune(r)
} else {
if digits.Len() > 0 {
return digits.String() + pass
}
}
}
if digits.Len() > 0 {
return digits.String() + pass
}
return "1" + pass
}
func poplop(master string, n Scheme) (string, error) {
h := sha512.New()
io.WriteString(h, master)
io.WriteString(h, n.Name)
if n.Counter > 0 {
io.WriteString(h, strconv.Itoa(n.Counter))
}
hash := h.Sum(nil)
dst := make([]byte, ascii85.MaxEncodedLen(len(hash)))
ascii85.Encode(dst, hash)
str := string(dst)
if n.Forbidden != "" {
str = strings.Map(n.mapping(), str)
}
length := 20
if n.MaxLength > 0 {
length = n.MaxLength
}
if len(str) < length {
return "", errors.New("hash not long enough " + str)
}
return str[:length], nil
}