/
hierogolyph.go
194 lines (161 loc) · 5.03 KB
/
hierogolyph.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
package hierogolyph
import (
"fmt"
"strings"
"github.com/evalphobia/hierogolyph/hasher"
"github.com/evalphobia/hierogolyph/hsm"
)
// Hierogolyph treats encryption and decryption.
type Hierogolyph struct {
Config
Password string
Salt string
EncryptionKey string // generated by password and salt, used for encryption/decryption and verifying password.
}
// CreateHierogolyph creates new Hierogolyph from given password, which is used for encryption.
// (after the first encryption, don't use this constructor.)
func CreateHierogolyph(password string, conf Config) (Hierogolyph, error) {
salt, err := getRandomString(20)
if err != nil {
return Hierogolyph{}, err
}
h := Hierogolyph{
Config: conf,
Password: password,
Salt: salt,
}
err = h.SetEncryptionKey()
if err != nil {
return Hierogolyph{}, err
}
return h, nil
}
// SetEncryptionKey sets an encryption key generated from password and salt.
func (h *Hierogolyph) SetEncryptionKey() error {
ek, err := h.createEncryptionKey()
if err != nil {
return err
}
h.EncryptionKey = ek
return nil
}
// Unlock creates Content Encryption Key.
func (h Hierogolyph) Unlock() (cek string, err error) {
z1, z2 := createDigests(h.Password, h.Salt, h.Config.Hasher)
maskedCipherText, err := decodeBase64(h.EncryptionKey)
if err != nil {
return "", err
}
// get XOR between Z1 and R'
encryptedSecretR := xor(maskedCipherText, z1)
secretR, err := h.Config.HSM.Decrypt(encryptedSecretR)
if err != nil {
return "", err
}
return createCEK(z2, secretR), nil
}
// Encrypt encrypts given plainText.
func (h Hierogolyph) Encrypt(plainText string) (cipherText string, err error) {
cek, err := h.Unlock()
if err != nil {
return "", err
}
fingerPrint := HashHMAC(plainText, h.Config.HMACKey)
fingerprintedText := fmt.Sprintf("%s.%s", encodeBase64String(plainText), encodeBase64String(fingerPrint))
cipherText, err = h.Config.Cipher.Encrypt(fingerprintedText, []byte(cek))
if err != nil {
return "", err
}
encrypted := encodeBase64String(cipherText)
return fmt.Sprintf("%s.%s", encodeBase64String(h.EncryptionKey), encrypted), nil
}
// Decrypt decrypts given cipherText.
func (h Hierogolyph) Decrypt(cipherText string) (plainText string, err error) {
encryptionKey, encryptedText, err := decodeCipherText(cipherText)
if err != nil {
return "", err
}
h.EncryptionKey = encryptionKey
cek, err := h.Unlock()
if err != nil {
return "", err
}
fingerprintedText, err := h.Config.Cipher.Decrypt(encryptedText, []byte(cek))
if err != nil {
return "", err
}
parts := strings.Split(fingerprintedText, ".")
if len(parts) != 2 {
return "", fmt.Errorf("fingerprintedText=[%s] must have one dot `.`", fingerprintedText)
}
plainText, err = decodeBase64(parts[0])
if err != nil {
return "", err
}
fingerPrint, err := decodeBase64(parts[1])
if err != nil {
return "", err
}
expected := HashHMAC(plainText, h.Config.HMACKey)
if fingerPrint != expected {
return "", fmt.Errorf("HMAC finger print error: expected=[%s], actual=[%s]", expected, fingerPrint)
}
return plainText, nil
}
// createEncryptionKey creates encryption key from password and salt.
func (h *Hierogolyph) createEncryptionKey() (string, error) {
secretR, err := getRandomBytes(32)
if err != nil {
return "", err
}
conf := h.Config
z1, _ := createDigests(h.Password, h.Salt, conf.Hasher)
return createEncryptionKey(z1, string(secretR), conf.HSM)
}
// decodeCipherText decodes from cipherText and returns encryptionKey and encryptedText.
func decodeCipherText(cipherText string) (encryptionKey, encryptedText string, err error) {
parts := strings.Split(cipherText, ".")
if len(parts) != 2 {
return "", "", fmt.Errorf("cipherText=[%s] must have one dot `.`", cipherText)
}
encryptionKey, err = decodeBase64(parts[0])
if err != nil {
return "", "", err
}
encryptedText, err = decodeBase64(parts[1])
if err != nil {
return "", "", err
}
return encryptionKey, encryptedText, nil
}
// createDigests creates 32byte string pair from given password and salt by hashing.
func createDigests(password, salt string, hasher hasher.Hasher) (z1, z2 string) {
digest := hasher.Hash(password, salt)
return digest[0:32], digest[32:64]
}
// createEncryptionKey creates EncryptionKey from Z1 and R with HSM eryption.
func createEncryptionKey(z1, secretR string, hsm hsm.HSM) (encryptionKey string, err error) {
encryptedSecretR, err := hsm.Encrypt(secretR)
if err != nil {
return "", err
}
// get XOR between Z1 and R'
maskedCipherText := xor(string(encryptedSecretR), z1)
return encodeBase64(maskedCipherText), nil
}
// createCEK returns Content Encryption Key from Z2 and R.
func createCEK(z2, secretR string) (cek string) {
return HashSHA256(z2 + secretR)
}
// xor gets XOR bytes between 'a' and 'b'.
// The results is based on 'a's length.
// If 'a' is longer than 'b', 'b' will be padded by 0.
func xor(a, b string) []byte {
byteSize := len(a)
paddedB := paddingLeft(string(b), byteSize, "0")
result := make([]byte, byteSize)
for i := 0; i < byteSize; i++ {
result[i] = paddedB[i] ^ a[i]
}
return result
}