This repository was archived by the owner on Apr 4, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathapi.go
More file actions
111 lines (87 loc) · 2.67 KB
/
api.go
File metadata and controls
111 lines (87 loc) · 2.67 KB
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
package mpassgo
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/binary"
"errors"
"golang.org/x/crypto/scrypt"
"unicode/utf8"
)
var ErrInvalidTemplate = errors.New("invalid template provided")
var seedPrefix = []byte("com.lyndir.masterpassword")
// A convenience function that takes all needed inputs and generates the password from there.
func GetPassword(name, website, masterPass []byte, counter int, set PasswordType) ([]byte, error) {
mKey, err := GetMasterKey(name, masterPass)
if err != nil {
return nil, err
}
defer zeroSlice(mKey)
sKey := GetSiteKey(mKey, website, counter)
defer zeroSlice(sKey)
template := GetTemplate(sKey, set)
password := SiteKeyToPassword(sKey, template)
return password, nil
}
// Gets the key for a given password.
// A side effect of using this function is that the password
// slice gets zero'd. This is done to discourage API users from
// using the master password anywhere else. Both the name and
// password should be UTF8 encoded.
func GetMasterKey(name, password []byte) ([]byte, error) {
seedLen := len(seedPrefix) + 4 + len(name)
defer func() { seedLen = 0 }()
seed := bytes.NewBuffer(make([]byte, 0, seedLen))
defer zeroBuf(seed)
seed.Write(seedPrefix)
seed.Write(convertNum(len(name)))
seed.Write(name)
return scrypt.Key(password, seed.Bytes(), 32768, 8, 2, 64)
}
// Gets the seed provided a key and a counter. The website
// should be UTF8 encoded.
func GetSiteKey(mKey, website []byte, counter int) []byte {
seedLen := len(seedPrefix) + 4 + len(website) + 4
defer func() { seedLen = 0 }()
seed := bytes.NewBuffer(make([]byte, 0, seedLen))
defer zeroBuf(seed)
seed.Write(seedPrefix)
seed.Write(convertNum(len(website)))
seed.Write(website)
seed.Write(convertNum(counter))
hasher := hmac.New(sha256.New, mKey)
hasher.Write(seed.Bytes())
return hasher.Sum(nil)
}
// Figures out what the password template is going to be.
func GetTemplate(sKey []byte, templates PasswordType) []byte {
return templates[sKey[0]%byte(len(templates))]
}
// Encodes the seed into the template, generating the password.
// Panics if an invalid template string is provided.
func SiteKeyToPassword(sKey []byte, template []byte) []byte {
pass := make([]byte, len(template))
for i, e := range template {
r, _ := utf8.DecodeRune([]byte{e})
runes := runeMap[r]
if runes == "" {
panic(ErrInvalidTemplate)
}
pass[i] = runes[sKey[i+1]%byte(len(runes))]
}
return pass
}
func zeroSlice(slice []byte) {
for i := range slice {
slice[i] = 0
}
}
func zeroBuf(buf *bytes.Buffer) {
buf.Reset()
buf.Write(make([]byte, buf.Cap()))
}
func convertNum(i int) []byte {
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, uint32(i))
return b
}