github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/blockchain/pseudohsm/keystore_passphrase.go (about) 1 /* 2 This key store behaves as KeyStorePlain with the difference that 3 the private key is encrypted and on disk uses another JSON encoding. 4 */ 5 6 package pseudohsm 7 8 import ( 9 "bytes" 10 "crypto/aes" 11 "crypto/cipher" 12 "crypto/sha256" 13 "encoding/hex" 14 "encoding/json" 15 "fmt" 16 "io/ioutil" 17 "path/filepath" 18 19 "github.com/bytom/bytom/crypto" 20 "github.com/bytom/bytom/crypto/ed25519/chainkd" 21 "github.com/bytom/bytom/crypto/randentropy" 22 "github.com/pborman/uuid" 23 "golang.org/x/crypto/pbkdf2" 24 "golang.org/x/crypto/scrypt" 25 ) 26 27 const ( 28 keyHeaderKDF = "scrypt" 29 30 // StandardScryptN n,r,p = 2^18, 8, 1 uses 256MB memory and approx 1s CPU time on a modern CPU. 31 StandardScryptN = 1 << 18 32 // StandardScryptP fit above 33 StandardScryptP = 1 34 35 // LightScryptN n,r,p = 2^12, 8, 6 uses 4MB memory and approx 100ms CPU time on a modern CPU. 36 LightScryptN = 1 << 12 37 //LightScryptP fit above 38 LightScryptP = 6 39 40 scryptR = 8 41 scryptDKLen = 32 42 ) 43 44 type keyStorePassphrase struct { 45 keysDirPath string 46 scryptN int 47 scryptP int 48 } 49 50 func (ks keyStorePassphrase) GetKey(alias string, filename, auth string) (*XKey, error) { 51 // Load the key from the keystore and decrypt its contents 52 keyjson, err := ioutil.ReadFile(filename) 53 if err != nil { 54 return nil, err 55 } 56 key, err := DecryptKey(keyjson, auth) 57 if err != nil { 58 return nil, err 59 } 60 // Make sure we're really operating on the requested key (no swap attacks) 61 if key.Alias != alias { 62 return nil, fmt.Errorf("key content mismatch: have account %x, want %x", key.Alias, alias) 63 } 64 return key, nil 65 } 66 67 func (ks keyStorePassphrase) StoreKey(filename string, key *XKey, auth string) error { 68 keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP) 69 if err != nil { 70 return err 71 } 72 return writeKeyFile(filename, keyjson) 73 } 74 75 func (ks keyStorePassphrase) JoinPath(filename string) string { 76 if filepath.IsAbs(filename) { 77 return filename 78 } 79 return filepath.Join(ks.keysDirPath, filename) 80 } 81 82 // EncryptKey encrypts a key using the specified scrypt parameters into a json 83 // blob that can be decrypted later on. 84 func EncryptKey(key *XKey, auth string, scryptN, scryptP int) ([]byte, error) { 85 authArray := []byte(auth) 86 salt := randentropy.GetEntropyCSPRNG(32) 87 derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptR, scryptP, scryptDKLen) 88 if err != nil { 89 return nil, err 90 } 91 encryptKey := derivedKey[:16] 92 keyBytes := key.XPrv[:] 93 94 iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 16 95 cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv) 96 if err != nil { 97 return nil, err 98 } 99 mac := crypto.Sha256(derivedKey[16:32], cipherText) 100 scryptParamsJSON := make(map[string]interface{}, 5) 101 scryptParamsJSON["n"] = scryptN 102 scryptParamsJSON["r"] = scryptR 103 scryptParamsJSON["p"] = scryptP 104 scryptParamsJSON["dklen"] = scryptDKLen 105 scryptParamsJSON["salt"] = hex.EncodeToString(salt) 106 107 cipherParamsJSON := cipherparamsJSON{ 108 IV: hex.EncodeToString(iv), 109 } 110 cryptoStruct := cryptoJSON{ 111 Cipher: "aes-128-ctr", 112 CipherText: hex.EncodeToString(cipherText), 113 CipherParams: cipherParamsJSON, 114 KDF: "scrypt", 115 KDFParams: scryptParamsJSON, 116 MAC: hex.EncodeToString(mac), 117 } 118 encryptedKeyJSON := encryptedKeyJSON{ 119 cryptoStruct, 120 key.ID.String(), 121 key.KeyType, 122 version, 123 key.Alias, 124 hex.EncodeToString(key.XPub[:]), 125 } 126 return json.Marshal(encryptedKeyJSON) 127 } 128 129 // DecryptKey decrypts a key from a json blob, returning the private key itself. 130 func DecryptKey(keyjson []byte, auth string) (*XKey, error) { 131 // Parse the json into a simple map to fetch the key version 132 m := make(map[string]interface{}) 133 if err := json.Unmarshal(keyjson, &m); err != nil { 134 return nil, err 135 } 136 // Depending on the version try to parse one way or another 137 var ( 138 keyBytes, keyID []byte 139 err error 140 ) 141 k := new(encryptedKeyJSON) 142 if err := json.Unmarshal(keyjson, k); err != nil { 143 return nil, err 144 } 145 146 keyBytes, keyID, err = decryptKey(k, auth) 147 // Handle any decryption errors and return the key 148 if err != nil { 149 return nil, err 150 } 151 var xprv chainkd.XPrv 152 copy(xprv[:], keyBytes[:]) 153 xpub := xprv.XPub() 154 155 //key := crypto.ToECDSA(keyBytes) 156 return &XKey{ 157 ID: uuid.UUID(keyID), 158 XPrv: xprv, 159 XPub: xpub, 160 KeyType: k.Type, 161 Alias: k.Alias, 162 }, nil 163 } 164 165 func decryptKey(keyProtected *encryptedKeyJSON, auth string) (keyBytes []byte, keyID []byte, err error) { 166 if keyProtected.Version != version { 167 return nil, nil, fmt.Errorf("Version not supported: %v", keyProtected.Version) 168 } 169 170 if keyProtected.Type != keytype { 171 return nil, nil, fmt.Errorf("Key type not supported: %v", keyProtected.Type) 172 } 173 174 if keyProtected.Crypto.Cipher != "aes-128-ctr" { 175 return nil, nil, fmt.Errorf("Cipher not supported: %v", keyProtected.Crypto.Cipher) 176 } 177 178 keyID = uuid.Parse(keyProtected.ID) 179 mac, err := hex.DecodeString(keyProtected.Crypto.MAC) 180 if err != nil { 181 return nil, nil, err 182 } 183 184 iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV) 185 if err != nil { 186 return nil, nil, err 187 } 188 189 cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText) 190 if err != nil { 191 return nil, nil, err 192 } 193 194 derivedKey, err := getKDFKey(keyProtected.Crypto, auth) 195 if err != nil { 196 return nil, nil, err 197 } 198 199 calculatedMAC := crypto.Sha256(derivedKey[16:32], cipherText) 200 201 if !bytes.Equal(calculatedMAC, mac) { 202 return nil, nil, ErrDecrypt 203 } 204 205 plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv) 206 if err != nil { 207 return nil, nil, err 208 } 209 return plainText, keyID, err 210 } 211 212 func getKDFKey(cryptoJSON cryptoJSON, auth string) ([]byte, error) { 213 authArray := []byte(auth) 214 salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string)) 215 if err != nil { 216 return nil, err 217 } 218 dkLen := ensureInt(cryptoJSON.KDFParams["dklen"]) 219 220 if cryptoJSON.KDF == "scrypt" { 221 n := ensureInt(cryptoJSON.KDFParams["n"]) 222 r := ensureInt(cryptoJSON.KDFParams["r"]) 223 p := ensureInt(cryptoJSON.KDFParams["p"]) 224 return scrypt.Key(authArray, salt, n, r, p, dkLen) 225 226 } else if cryptoJSON.KDF == "pbkdf2" { 227 c := ensureInt(cryptoJSON.KDFParams["c"]) 228 prf := cryptoJSON.KDFParams["prf"].(string) 229 if prf != "hmac-sha256" { 230 return nil, fmt.Errorf("Unsupported PBKDF2 PRF: %s", prf) 231 } 232 key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New) 233 return key, nil 234 } 235 236 return nil, fmt.Errorf("Unsupported KDF: %s", cryptoJSON.KDF) 237 } 238 239 // TODO: can we do without this when unmarshalling dynamic JSON? 240 // why do integers in KDF params end up as float64 and not int after 241 // unmarshal? 242 func ensureInt(x interface{}) int { 243 res, ok := x.(int) 244 if !ok { 245 res = int(x.(float64)) 246 } 247 return res 248 } 249 250 func aesCTRXOR(key, inText, iv []byte) ([]byte, error) { 251 // AES-128 is selected due to size of encryptKey. 252 aesBlock, err := aes.NewCipher(key) 253 if err != nil { 254 return nil, err 255 } 256 stream := cipher.NewCTR(aesBlock, iv) 257 outText := make([]byte, len(inText)) 258 stream.XORKeyStream(outText, inText) 259 return outText, err 260 } 261 262 func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) { 263 aesBlock, err := aes.NewCipher(key) 264 if err != nil { 265 return nil, err 266 } 267 decrypter := cipher.NewCBCDecrypter(aesBlock, iv) 268 paddedPlaintext := make([]byte, len(cipherText)) 269 decrypter.CryptBlocks(paddedPlaintext, cipherText) 270 plaintext := pkcs7Unpad(paddedPlaintext) 271 if plaintext == nil { 272 return nil, ErrDecrypt 273 } 274 return plaintext, err 275 } 276 277 // From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes 278 func pkcs7Unpad(in []byte) []byte { 279 if len(in) == 0 { 280 return nil 281 } 282 283 padding := in[len(in)-1] 284 if int(padding) > len(in) || padding > aes.BlockSize { 285 return nil 286 } else if padding == 0 { 287 return nil 288 } 289 290 for i := len(in) - 1; i > len(in)-int(padding)-1; i-- { 291 if in[i] != padding { 292 return nil 293 } 294 } 295 return in[:len(in)-int(padding)] 296 }