decred.org/dcrdex@v1.0.5/dex/encrypt/encrypt.go (about) 1 // This code is available on the terms of the project LICENSE.md file, 2 // also available online at https://blueoakcouncil.org/license/1.0.0. 3 4 package encrypt 5 6 import ( 7 "crypto/rand" 8 "fmt" 9 "runtime" 10 11 "decred.org/dcrdex/dex/encode" 12 "golang.org/x/crypto/argon2" 13 "golang.org/x/crypto/chacha20poly1305" 14 "golang.org/x/crypto/poly1305" 15 ) 16 17 // Crypter is an interface for an encryption key and encryption/decryption 18 // algorithms. Create a Crypter with the NewCrypter function. 19 type Crypter interface { 20 // Encrypt encrypts the plaintext. 21 Encrypt(b []byte) ([]byte, error) 22 // Decrypt decrypts the ciphertext created by Encrypt. 23 Decrypt(b []byte) ([]byte, error) 24 // Serialize serializes the Crypter. Use the Deserialize function to create 25 // a Crypter from the resulting bytes. Deserializing requires the password 26 // used to create the Crypter. 27 Serialize() []byte 28 // Close zeros the encryption key. The Crypter is useless after closing. 29 Close() 30 } 31 32 const ( 33 // defaultTime is the default time parameter for argon2id key derivation. 34 defaultTime = 1 35 // defaultMem is the default memory parameter for argon2id key derivation. 36 defaultMem = 64 * 1024 37 // KeySize is the size of the encryption key. 38 KeySize = 32 39 // SaltSize is the size of the argon2id salt. 40 SaltSize = 16 41 ) 42 43 // Big-endian encoder. 44 var intCoder = encode.IntCoder 45 46 // Key is 32 bytes. 47 type Key [KeySize]byte 48 49 // Salt is randomness used as part of key derivation. This is different from the 50 // salt generated during xchacha20poly1305 encryption, which is shorter. 51 type Salt [SaltSize]byte 52 53 // newSalt is the constructor for a salt based on randomness from crypto/rand. 54 func newSalt() Salt { 55 var s Salt 56 _, err := rand.Read(s[:]) 57 if err != nil { 58 panic("newSalt: " + err.Error()) 59 } 60 return s 61 } 62 63 // argonParams is a set of parameters for key derivation. 64 type argonParams struct { 65 time uint32 66 memory uint32 67 threads uint8 68 } 69 70 // NewCrypter derives an encryption key from a password string. 71 func NewCrypter(pw []byte) Crypter { 72 return newArgonPolyCrypter(pw) 73 } 74 75 // Deserialize deserializes the Crypter for the password. 76 func Deserialize(pw, encCrypter []byte) (Crypter, error) { 77 ver, pushes, err := encode.DecodeBlob(encCrypter) 78 if err != nil { 79 return nil, err 80 } 81 switch ver { 82 case 0: 83 return decodeArgonPoly_v0(pw, pushes) 84 default: 85 return nil, fmt.Errorf("unknown Crypter version %d", ver) 86 } 87 } 88 89 // argonPolyCryper is an encryption algorithm based on argon2id for key 90 // derivation and xchacha20poly1305 for symmetric encryption. 91 type argonPolyCrypter struct { 92 key Key 93 tag [poly1305.TagSize]byte 94 salt Salt 95 params *argonParams 96 } 97 98 // newArgonPolyCrypter is the constructor for an argonPolyCrypter. 99 func newArgonPolyCrypter(pw []byte) *argonPolyCrypter { 100 salt := newSalt() 101 threads := uint8(runtime.NumCPU()) 102 103 keyB := argon2.IDKey(pw, salt[:], defaultTime, defaultMem, threads, KeySize*2) 104 // The argon2id key is split into two keys, The encryption key is the first 32 105 // bytes. 106 var encKey Key 107 copy(encKey[:], keyB[:KeySize]) 108 // MAC key is the second 32 bytes. 109 var polyKey [KeySize]byte 110 copy(polyKey[:], keyB[KeySize:]) 111 112 c := &argonPolyCrypter{ 113 key: encKey, 114 salt: salt, 115 params: &argonParams{ 116 time: defaultTime, 117 memory: defaultMem, 118 threads: threads, 119 }, 120 } 121 // Use the mac key and the serialized parameters to generate the 122 // authenticator. 123 poly1305.Sum(&c.tag, c.serializeParams(), &polyKey) 124 return c 125 } 126 127 // Encrypt encrypts the plaintext. 128 func (c *argonPolyCrypter) Encrypt(plainText []byte) ([]byte, error) { 129 boxer, err := chacha20poly1305.NewX(c.key[:]) 130 if err != nil { 131 return nil, fmt.Errorf("aead error: %w", err) 132 } 133 nonce := make([]byte, boxer.NonceSize()) 134 _, err = rand.Read(nonce) 135 if err != nil { 136 return nil, fmt.Errorf("nonce generation error: %w", err) 137 } 138 cipherText := boxer.Seal(nil, nonce, plainText, nil) 139 return encode.BuildyBytes{0}.AddData(nonce).AddData(cipherText), nil 140 } 141 142 // Decrypt decrypts the ciphertext created by Encrypt. 143 func (c *argonPolyCrypter) Decrypt(encrypted []byte) ([]byte, error) { 144 ver, pushes, err := encode.DecodeBlob(encrypted) 145 if err != nil { 146 return nil, fmt.Errorf("DecodeBlob: %w", err) 147 } 148 if ver != 0 { 149 return nil, fmt.Errorf("only version 0 encryptions are known. got version %d", ver) 150 } 151 if len(pushes) != 2 { 152 return nil, fmt.Errorf("expected 2 pushes. got %d", len(pushes)) 153 } 154 boxer, err := chacha20poly1305.NewX(c.key[:]) 155 if err != nil { 156 return nil, fmt.Errorf("aead error: %w", err) 157 } 158 nonce, cipherText := pushes[0], pushes[1] 159 if len(nonce) != boxer.NonceSize() { 160 return nil, fmt.Errorf("incompatible nonce length. expected %d, got %d", boxer.NonceSize(), len(nonce)) 161 } 162 plainText, err := boxer.Open(nil, nonce, cipherText, nil) 163 if err != nil { 164 return nil, fmt.Errorf("aead.Open: %w", err) 165 } 166 return plainText, nil 167 } 168 169 // Serialize serializes the argonPolyCrypter. 170 func (c *argonPolyCrypter) Serialize() []byte { 171 return c.serializeParams().AddData(c.tag[:]) 172 } 173 174 // serializeParams serializes the argonPolyCrypter parameters, without the 175 // poly1305 auth tag. 176 func (c *argonPolyCrypter) serializeParams() encode.BuildyBytes { 177 return encode.BuildyBytes{0}. 178 AddData(c.salt[:]). 179 AddData(encode.Uint32Bytes(c.params.time)). 180 AddData(encode.Uint32Bytes(c.params.memory)). 181 AddData([]byte{c.params.threads}) 182 } 183 184 // Close zeros the key. The argonPolyCrypter is useless after closing. 185 func (c *argonPolyCrypter) Close() { 186 for i := range c.key { 187 c.key[i] = 0 188 } 189 } 190 191 // decodeArgonPoly_v0 decodes an argonPolyCrypter from the pushes extracted from 192 // a version 0 blob. 193 func decodeArgonPoly_v0(pw []byte, pushes [][]byte) (*argonPolyCrypter, error) { 194 if len(pushes) != 5 { 195 return nil, fmt.Errorf("decodeArgonPoly_v0 expected 5 pushes, but got %d", len(pushes)) 196 } 197 saltB, tagB := pushes[0], pushes[4] 198 timeB, memB, threadsB := pushes[1], pushes[2], pushes[3] 199 200 if len(saltB) != SaltSize { 201 return nil, fmt.Errorf("expected salt of length %d, got %d", SaltSize, len(saltB)) 202 } 203 var salt Salt 204 copy(salt[:], saltB) 205 206 if len(threadsB) != 1 { 207 return nil, fmt.Errorf("threads parameter encoding length is incorrect. expected 1, got %d", len(threadsB)) 208 } 209 210 if len(tagB) != poly1305.TagSize { 211 return nil, fmt.Errorf("mac authenticator of incorrect length. wanted %d, got %d", poly1305.TagSize, len(tagB)) 212 } 213 var polyTag [poly1305.TagSize]byte 214 copy(polyTag[:], tagB) 215 216 // Create the argonPolyCrypter. 217 params := &argonParams{ 218 time: intCoder.Uint32(timeB), 219 memory: intCoder.Uint32(memB), 220 threads: threadsB[0], 221 } 222 c := &argonPolyCrypter{ 223 salt: salt, 224 tag: polyTag, 225 params: params, 226 } 227 228 // Prepare the key. 229 keyB := argon2.IDKey(pw, c.salt[:], params.time, params.memory, params.threads, KeySize*2) 230 231 // Check the poly1305 auth to verify the password. 232 polyKeyB := keyB[KeySize:] 233 var polyKey [KeySize]byte 234 copy(polyKey[:], polyKeyB) 235 if !poly1305.Verify(&c.tag, c.serializeParams(), &polyKey) { 236 return nil, fmt.Errorf("incorrect password") 237 } 238 239 // Copy the key. 240 copy(c.key[:], keyB[:KeySize]) 241 242 return c, nil 243 }