forked from pbthorste/avtool
-
Notifications
You must be signed in to change notification settings - Fork 1
/
decrypt.go
153 lines (136 loc) · 3.92 KB
/
decrypt.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
package avtool
import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"golang.org/x/crypto/pbkdf2"
"io/ioutil"
"strings"
)
// DecryptFileOptions is the interface used to pass data to the DecryptFile method
type DecryptFileOptions struct {
Filename string
Password *[]byte
}
// DecryptFile reads content of filename, decrypts it and returns string
func DecryptFile(opts *DecryptFileOptions) (result string, err error) {
data, err := ioutil.ReadFile(opts.Filename)
if err != nil {
return "", err
}
result, err = Decrypt(&DecryptOptions{
Data: &data,
Password: opts.Password,
})
return
}
// DecryptOptions is the interface used to pass data to the Decrypt method
type DecryptOptions struct {
Data *[]byte
Password *[]byte
}
// Decrypt a string containing the ansible vault
func Decrypt(opts *DecryptOptions) (result string, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("ERROR: %v", r)
}
}()
data := replaceCarriageReturn(string(*opts.Data))
body := splitHeader([]byte(data))
salt, cryptedHmac, ciphertext, err := decodeData(body)
if err != nil {
return "", err
}
key1, key2, iv := genKeyInitctr(opts.Password, salt)
err = checkDigest(key2, cryptedHmac, ciphertext)
if err != nil {
return "", err
}
aesCipher, err := aes.NewCipher(key1)
if err != nil {
return "", err
}
aesBlock := cipher.NewCTR(aesCipher, iv)
plaintext := make([]byte, len(ciphertext))
aesBlock.XORKeyStream(plaintext, ciphertext)
padding := int(plaintext[len(plaintext)-1])
result = string(plaintext[:len(plaintext)-padding])
return
}
// replaceCarriageReturn in order to support vault files with windows line endings
func replaceCarriageReturn(data string) string {
return strings.ReplaceAll(data, "\r", "")
}
/*
See _split_header function
https://github.com/ansible/ansible/blob/0b8011436dc7f842b78298848e298f2a57ee8d78/lib/ansible/parsing/vault/__init__.py#L288
*/
func splitHeader(data []byte) string {
contents := string(data)
lines := strings.Split(contents, "\n")
header := strings.Split(lines[0], ";")
cipherName := strings.TrimSpace(header[2])
if cipherName != "AES256" {
panic(fmt.Errorf("unsupported cipher: %s", cipherName))
}
body := strings.Join(lines[1:], "")
return body
}
/*
See decrypt function (in class VaultAES256)
https://github.com/ansible/ansible/blob/0b8011436dc7f842b78298848e298f2a57ee8d78/lib/ansible/parsing/vault/__init__.py#L741
*/
func decodeData(body string) (salt, cryptedHmac, cipherText []byte, err error) {
decoded, err := hex.DecodeString(body)
if err != nil {
return nil, nil, nil, err
}
elements := strings.SplitN(string(decoded), "\n", 3)
salt, err = hex.DecodeString(elements[0])
if err != nil {
return nil, nil, nil, err
}
cryptedHmac, err = hex.DecodeString(elements[1])
if err != nil {
return nil, nil, nil, err
}
cipherText, err = hex.DecodeString(elements[2])
if err != nil {
return nil, nil, nil, err
}
return
}
/*
See function _gen_key_initctr (in class VaultAES256)
https://github.com/ansible/ansible/blob/0b8011436dc7f842b78298848e298f2a57ee8d78/lib/ansible/parsing/vault/__init__.py#L685
*/
func genKeyInitctr(password *[]byte, salt []byte) (key1, key2, iv []byte) {
keyLength := 32
ivLength := 16
key := pbkdf2.Key(*password, salt, 10000, 2*keyLength+ivLength, sha256.New)
key1 = key[:keyLength]
key2 = key[keyLength:(keyLength * 2)]
iv = key[(keyLength * 2) : (keyLength*2)+ivLength]
return
}
/*
See decrypt function (in class VaultAES256)
https://github.com/ansible/ansible/blob/0b8011436dc7f842b78298848e298f2a57ee8d78/lib/ansible/parsing/vault/__init__.py#L741
*/
func checkDigest(key2, cryptedHmac, ciphertext []byte) error {
hmacDecrypt := hmac.New(sha256.New, key2)
_, err := hmacDecrypt.Write(ciphertext)
if err != nil {
return err
}
expectedMAC := hmacDecrypt.Sum(nil)
if !hmac.Equal(cryptedHmac, expectedMAC) {
return errors.New("digests do not match - exiting")
}
return nil
}