/
crypto.go
151 lines (129 loc) · 3.81 KB
/
crypto.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
// Package secret contains utilities for encrypting and decrypting
// data with secret keys; it is aimed primarily at password-based
// encryption. Encryption keys are typically derived from Scrypt
// (using 32768, 8, and 4 as the parameters) to obtain a key suitable
// for use with NaCl's secretbox (XSalsa20 and Poly1305).
package secret
import (
"errors"
"io/ioutil"
"github.com/kisom/cryptutils/common/util"
"golang.org/x/crypto/nacl/secretbox"
"golang.org/x/crypto/scrypt"
)
const (
// ScryptStandard mode uses N=2^20, r=8, p=2
ScryptStandard ScryptMode = iota
// ScryptInteractive mode uses N=2^14, r=8, p=1
ScryptInteractive
)
// KeySize contains the size (in bytes) of a NaCl secretbox key.
const (
KeySize = 32
SaltSize = 32
nonceSize = 24
)
// ScryptMode represents the work factor to be used for passphrases.
type ScryptMode int
type scryptParams struct {
N int
r int
p int
}
// Scrypt work factors
var (
scryptStandard = scryptParams{1048576, 8, 2}
scryptInteractive = scryptParams{16384, 8, 1}
)
var scryptMode = map[ScryptMode]scryptParams{
ScryptStandard: scryptStandard,
ScryptInteractive: scryptInteractive,
}
// GenerateKey returns a randomly generated secretbox key. Typically,
// you should use DeriveKey to get a key from a passphrase
// instead. Returns nil on failure.
func GenerateKey() *[KeySize]byte {
var key [KeySize]byte
rb := util.RandBytes(KeySize)
if rb == nil || len(rb) != KeySize {
return nil
}
defer util.Zero(rb)
copy(key[:], rb)
return &key
}
// DeriveKeyStrength applies Scrypt using the given work parameters
// to generate an encryption key from a passphrase and salt.
func DeriveKeyStrength(passphrase []byte, salt []byte, m ScryptMode) *[KeySize]byte {
s := scryptMode[m]
rawKey, err := scrypt.Key(passphrase, salt, s.N, s.r, s.p, KeySize)
if err != nil {
return nil
}
var key [KeySize]byte
copy(key[:], rawKey)
util.Zero(rawKey)
return &key
}
// DeriveKey applies Scrypt with very strong parameters to generate an
// encryption key from a passphrase and salt.
func DeriveKey(passphrase []byte, salt []byte) *[KeySize]byte {
return DeriveKeyStrength(passphrase, salt, ScryptStandard)
}
// Encrypt generates a random nonce and encrypts the input using
// NaCl's secretbox package. The nonce is prepended to the ciphertext.
func Encrypt(key *[KeySize]byte, in []byte) ([]byte, bool) {
var out = make([]byte, nonceSize)
nonce := util.NewNonce()
if nonce == nil {
return nil, false
}
copy(out, nonce[:])
out = secretbox.Seal(out, in, nonce, key)
return out, true
}
// Decrypt extracts the nonce from the ciphertext, and attempts to
// decrypt with NaCl's secretbox.
func Decrypt(key *[KeySize]byte, in []byte) ([]byte, bool) {
if len(in) < nonceSize {
return nil, false
}
var nonce [nonceSize]byte
copy(nonce[:], in)
return secretbox.Open(nil, in[nonceSize:], &nonce, key)
}
// DecryptFile recovers a secured blob from a file, returning a byte
// slice for parsing by the caller.
func DecryptFile(filename string, passphrase []byte) (data []byte, err error) {
data, err = ioutil.ReadFile(filename)
if err != nil {
return
}
salt := data[:SaltSize]
data = data[SaltSize:]
key := DeriveKey(passphrase, salt)
data, ok := Decrypt(key, data)
if !ok {
err = errors.New("password: failed to decrypt password store")
}
return
}
// EncryptFile securely stores the encoded blob under the filename.
func EncryptFile(filename string, passphrase, encoded []byte) (err error) {
salt := util.RandBytes(SaltSize)
if salt == nil {
err = errors.New("password: failed to generate new salt")
return
}
defer util.Zero(encoded)
key := DeriveKey(passphrase, salt)
data, ok := Encrypt(key, encoded)
if !ok {
data = nil
err = errors.New("password: failed to encrypt data")
return
}
data = append(salt, data...)
err = ioutil.WriteFile(filename, data, 0600)
return
}