github.com/decred/politeia@v1.4.0/politeiad/backendv2/tstorebe/store/mysql/encrypt.go (about) 1 // Copyright (c) 2020-2022 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package mysql 6 7 import ( 8 "bytes" 9 "context" 10 "database/sql" 11 "encoding/binary" 12 "encoding/json" 13 14 "github.com/decred/politeia/util" 15 "github.com/marcopeereboom/sbox" 16 "github.com/pkg/errors" 17 "golang.org/x/crypto/argon2" 18 ) 19 20 const ( 21 // encryptionKeyParamsKey is the kv store key for the encryption 22 // key params that are saved on initial key derivation. 23 encryptionKeyParamsKey = "store-mysql-encryptionkeyparams" 24 ) 25 26 // encryptionKeyParams is saved to the kv store on initial derivation of the 27 // encryption key. It contains the params that were used to derive the key and 28 // a SHA256 digest of the key. Subsequent derivations will use the existing 29 // params to derive the key and will use the digest to verify that the 30 // encryption key has not changed. 31 type encryptionKeyParams struct { 32 Digest []byte `json:"digest"` // SHA256 digest 33 Params util.Argon2Params `json:"params"` 34 } 35 36 // argon2idKey derives an encryption key using the provided parameters and the 37 // Argon2id key derivation function. The derived key is set to be the 38 // encryption key on the mysql context. 39 func (s *mysqlCtx) argon2idKey(password string, ap util.Argon2Params) { 40 k := argon2.IDKey([]byte(password), ap.Salt, ap.Time, ap.Memory, 41 ap.Threads, ap.KeyLen) 42 copy(s.key[:], k) 43 util.Zero(k) 44 } 45 46 // deriveEncryption derives a 32 byte key from the provided password using the 47 // Aragon2id key derivation function. A random 16 byte salt is created the 48 // first time the key is derived. The salt and the other argon2id params are 49 // saved to the kv store. Subsequent calls to this fuction will pull the 50 // existing salt and params from the kv store and use them to derive the key, 51 // then will use the saved encryption key digest to verify that the key has 52 // not changed. 53 func (s *mysqlCtx) deriveEncryptionKey(password string) error { 54 log.Infof("Deriving encryption key") 55 56 // Check if the key params already exist in the kv store. Existing 57 // params means that the key has been derived previously. These 58 // params will be used if found. If no params exist then new ones 59 // will be created and saved to the kv store for future use. 60 blobs, err := s.Get([]string{encryptionKeyParamsKey}) 61 if err != nil { 62 return err 63 } 64 var ( 65 save bool 66 ekp encryptionKeyParams 67 ) 68 b, ok := blobs[encryptionKeyParamsKey] 69 if ok { 70 log.Debugf("Encryption key params found in kv store") 71 err = json.Unmarshal(b, &ekp) 72 if err != nil { 73 return err 74 } 75 } else { 76 log.Infof("Encryption key params not found; creating new ones") 77 ekp = encryptionKeyParams{ 78 Params: util.NewArgon2Params(), 79 } 80 save = true 81 } 82 83 // Derive key 84 s.argon2idKey(password, ekp.Params) 85 86 // Check if the params need to be saved 87 keyDigest := util.Digest(s.key[:]) 88 if save { 89 // This was the first time the key was derived. Save the params 90 // to the kv store. 91 ekp.Digest = keyDigest 92 b, err := json.Marshal(ekp) 93 if err != nil { 94 return err 95 } 96 kv := map[string][]byte{ 97 encryptionKeyParamsKey: b, 98 } 99 err = s.Put(kv, false) 100 if err != nil { 101 return err 102 } 103 104 log.Infof("Encryption key params saved to kv store") 105 } else { 106 // This was not the first time the key was derived. Verify that 107 // the key has not changed. 108 if !bytes.Equal(ekp.Digest, keyDigest) { 109 return errors.Errorf("attempting to use different encryption key") 110 } 111 } 112 113 return nil 114 } 115 116 var emptyNonce = [24]byte{} 117 118 func (s *mysqlCtx) getDBNonce(ctx context.Context, tx *sql.Tx) ([24]byte, error) { 119 // Get nonce value 120 nonce, err := s.nonce(ctx, tx) 121 if err != nil { 122 return emptyNonce, err 123 } 124 125 log.Tracef("Encrypting with nonce: %v", nonce) 126 127 // Prepare nonce 128 b := make([]byte, 8) 129 binary.LittleEndian.PutUint64(b, uint64(nonce)) 130 n, err := sbox.NewNonceFromBytes(b) 131 if err != nil { 132 return emptyNonce, err 133 } 134 return n.Current(), nil 135 } 136 137 func (s *mysqlCtx) getTestNonce(ctx context.Context, tx *sql.Tx) ([24]byte, error) { 138 nonce, err := util.Random(8) 139 if err != nil { 140 return emptyNonce, err 141 } 142 n, err := sbox.NewNonceFromBytes(nonce) 143 if err != nil { 144 return emptyNonce, err 145 } 146 return n.Current(), nil 147 } 148 149 func (s *mysqlCtx) getNonce(ctx context.Context, tx *sql.Tx) ([24]byte, error) { 150 if s.testing { 151 return s.getTestNonce(ctx, tx) 152 } 153 return s.getDBNonce(ctx, tx) 154 } 155 156 func (s *mysqlCtx) encrypt(ctx context.Context, tx *sql.Tx, data []byte) ([]byte, error) { 157 nonce, err := s.getNonce(ctx, tx) 158 if err != nil { 159 return nil, err 160 } 161 return sbox.EncryptN(0, &s.key, nonce, data) 162 } 163 164 func (s *mysqlCtx) decrypt(data []byte) ([]byte, uint32, error) { 165 return sbox.Decrypt(&s.key, data) 166 } 167 168 // isEncrypted returns whether the provided blob has been prefixed with an sbox 169 // header, indicating that it is an encrypted blob. 170 func isEncrypted(b []byte) bool { 171 return bytes.HasPrefix(b, []byte("sbox")) 172 }