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  }