github.com/decred/dcrlnd@v0.7.6/chanbackup/crypto.go (about)

     1  package chanbackup
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/rand"
     6  	"crypto/sha256"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  
    11  	"github.com/decred/dcrlnd/keychain"
    12  	"golang.org/x/crypto/chacha20poly1305"
    13  )
    14  
    15  // TODO(roasbeef): interface in front of?
    16  
    17  // baseEncryptionKeyLoc is the KeyLocator that we'll use to derive the base
    18  // encryption key used for encrypting all static channel backups. We use this
    19  // to then derive the actual key that we'll use for encryption. We do this
    20  // rather than using the raw key, as we assume that we can't obtain the raw
    21  // keys, and we don't want to require that the HSM know our target cipher for
    22  // encryption.
    23  //
    24  // TODO(roasbeef): possibly unique encrypt?
    25  var baseEncryptionKeyLoc = keychain.KeyLocator{
    26  	Family: keychain.KeyFamilyStaticBackup,
    27  	Index:  0,
    28  }
    29  
    30  // genEncryptionKey derives the key that we'll use to encrypt all of our static
    31  // channel backups. The key itself, is the sha2 of a base key that we get from
    32  // the keyring. We derive the key this way as we don't force the HSM (or any
    33  // future abstractions) to be able to derive and know of the cipher that we'll
    34  // use within our protocol.
    35  func genEncryptionKey(keyRing keychain.KeyRing) ([]byte, error) {
    36  	//  key = SHA256(baseKey)
    37  	baseKey, err := keyRing.DeriveKey(
    38  		baseEncryptionKeyLoc,
    39  	)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  
    44  	encryptionKey := sha256.Sum256(
    45  		baseKey.PubKey.SerializeCompressed(),
    46  	)
    47  
    48  	// TODO(roasbeef): throw back in ECDH?
    49  
    50  	return encryptionKey[:], nil
    51  }
    52  
    53  // encryptPayloadToWriter attempts to write the set of bytes contained within
    54  // the passed byes.Buffer into the passed io.Writer in an encrypted form. We
    55  // use a 24-byte chachapoly AEAD instance with a randomized nonce that's
    56  // pre-pended to the final payload and used as associated data in the AEAD. We
    57  // use the passed keyRing to generate the encryption key, see genEncryptionKey
    58  // for further details.
    59  func encryptPayloadToWriter(payload bytes.Buffer, w io.Writer,
    60  	keyRing keychain.KeyRing) error {
    61  
    62  	// First, we'll derive the key that we'll use to encrypt the payload
    63  	// for safe storage without giving away the details of any of our
    64  	// channels.  The final operation is:
    65  	//
    66  	//  key = SHA256(baseKey)
    67  	encryptionKey, err := genEncryptionKey(keyRing)
    68  	if err != nil {
    69  		return err
    70  	}
    71  
    72  	// Before encryption, we'll initialize our cipher with the target
    73  	// encryption key, and also read out our random 24-byte nonce we use
    74  	// for encryption. Note that we use NewX, not New, as the latter
    75  	// version requires a 12-byte nonce, not a 24-byte nonce.
    76  	cipher, err := chacha20poly1305.NewX(encryptionKey)
    77  	if err != nil {
    78  		return err
    79  	}
    80  	var nonce [chacha20poly1305.NonceSizeX]byte
    81  	if _, err := rand.Read(nonce[:]); err != nil {
    82  		return err
    83  	}
    84  
    85  	// Finally, we encrypted the final payload, and write out our
    86  	// ciphertext with nonce pre-pended.
    87  	ciphertext := cipher.Seal(nil, nonce[:], payload.Bytes(), nonce[:])
    88  
    89  	if _, err := w.Write(nonce[:]); err != nil {
    90  		return err
    91  	}
    92  	if _, err := w.Write(ciphertext); err != nil {
    93  		return err
    94  	}
    95  
    96  	return nil
    97  }
    98  
    99  // decryptPayloadFromReader attempts to decrypt the encrypted bytes within the
   100  // passed io.Reader instance using the key derived from the passed keyRing. For
   101  // further details regarding the key derivation protocol, see the
   102  // genEncryptionKey method.
   103  func decryptPayloadFromReader(payload io.Reader,
   104  	keyRing keychain.KeyRing) ([]byte, error) {
   105  
   106  	// First, we'll re-generate the encryption key that we use for all the
   107  	// SCBs.
   108  	encryptionKey, err := genEncryptionKey(keyRing)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	// Next, we'll read out the entire blob as we need to isolate the nonce
   114  	// from the rest of the ciphertext.
   115  	packedBackup, err := ioutil.ReadAll(payload)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  	if len(packedBackup) < chacha20poly1305.NonceSizeX {
   120  		return nil, fmt.Errorf("payload size too small, must be at "+
   121  			"least %v bytes", chacha20poly1305.NonceSizeX)
   122  	}
   123  
   124  	nonce := packedBackup[:chacha20poly1305.NonceSizeX]
   125  	ciphertext := packedBackup[chacha20poly1305.NonceSizeX:]
   126  
   127  	// Now that we have the cipher text and the nonce separated, we can go
   128  	// ahead and decrypt the final blob so we can properly serialized the
   129  	// SCB.
   130  	cipher, err := chacha20poly1305.NewX(encryptionKey)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  	plaintext, err := cipher.Open(nil, nonce, ciphertext, nonce)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	return plaintext, nil
   140  }