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 }