github.com/core-coin/go-core/v2@v2.1.9/accounts/keystore/passphrase.go (about) 1 // Copyright 2014 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-core library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 /* 18 19 This key store behaves as KeyStorePlain with the difference that 20 the private key is encrypted and on disk uses another JSON encoding. 21 22 The crypto is documented at https://github.com/core/wiki/wiki/Web3-Secret-Storage-Definition 23 24 */ 25 26 package keystore 27 28 import ( 29 "bytes" 30 "crypto/aes" 31 "crypto/cipher" 32 "crypto/rand" 33 "encoding/hex" 34 "encoding/json" 35 "fmt" 36 "io" 37 "io/ioutil" 38 "os" 39 "path/filepath" 40 41 "github.com/pborman/uuid" 42 "golang.org/x/crypto/pbkdf2" 43 "golang.org/x/crypto/scrypt" 44 "golang.org/x/crypto/sha3" 45 46 "github.com/core-coin/go-core/v2/accounts" 47 "github.com/core-coin/go-core/v2/common" 48 "github.com/core-coin/go-core/v2/crypto" 49 ) 50 51 const ( 52 keyHeaderKDF = "scrypt" 53 54 // StandardScryptN is the N parameter of Scrypt encryption algorithm, using 256MB 55 // memory and taking approximately 1s CPU time on a modern processor. 56 StandardScryptN = 1 << 18 57 58 // StandardScryptP is the P parameter of Scrypt encryption algorithm, using 256MB 59 // memory and taking approximately 1s CPU time on a modern processor. 60 StandardScryptP = 1 61 62 // LightScryptN is the N parameter of Scrypt encryption algorithm, using 4MB 63 // memory and taking approximately 100ms CPU time on a modern processor. 64 LightScryptN = 1 << 12 65 66 // LightScryptP is the P parameter of Scrypt encryption algorithm, using 4MB 67 // memory and taking approximately 100ms CPU time on a modern processor. 68 LightScryptP = 6 69 70 scryptR = 8 71 scryptDKLen = 32 72 ) 73 74 type keyStorePassphrase struct { 75 keysDirPath string 76 scryptN int 77 scryptP int 78 // skipKeyFileVerification disables the security-feature which does 79 // reads and decrypts any newly created keyfiles. This should be 'false' in all 80 // cases except tests -- setting this to 'true' is not recommended. 81 skipKeyFileVerification bool 82 } 83 84 func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string) (*Key, error) { 85 // Load the key from the keystore and decrypt its contents 86 keyjson, err := ioutil.ReadFile(filename) 87 if err != nil { 88 return nil, err 89 } 90 key, err := DecryptKey(keyjson, auth) 91 if err != nil { 92 return nil, err 93 } 94 // Make sure we're really operating on the requested key (no swap attacks) 95 if key.Address != addr { 96 return nil, fmt.Errorf("key content mismatch: have account %x, want %x", key.Address, addr) 97 } 98 return key, nil 99 } 100 101 // StoreKey generates a key, encrypts with 'auth' and stores in the given directory 102 func StoreKey(dir, auth string, scryptN, scryptP int) (accounts.Account, error) { 103 _, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP, false}, rand.Reader, auth) 104 return a, err 105 } 106 107 func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) error { 108 keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP) 109 if err != nil { 110 return err 111 } 112 // Write into temporary file 113 tmpName, err := writeTemporaryKeyFile(filename, keyjson) 114 if err != nil { 115 return err 116 } 117 if !ks.skipKeyFileVerification { 118 // Verify that we can decrypt the file with the given password. 119 _, err = ks.GetKey(key.Address, tmpName, auth) 120 if err != nil { 121 msg := "An error was encountered when saving and verifying the keystore file. \n" + 122 "This indicates that the keystore is corrupted. \n" + 123 "The corrupted file is stored at \n%v\n" + 124 "Please file a ticket at:\n\n" + 125 "https://github.com/core-coin/go-core/v2/issues." + 126 "The error was : %s" 127 //lint:ignore ST1005 This is a message for the user 128 return fmt.Errorf(msg, tmpName, err) 129 } 130 } 131 return os.Rename(tmpName, filename) 132 } 133 134 func (ks keyStorePassphrase) JoinPath(filename string) string { 135 if filepath.IsAbs(filename) { 136 return filename 137 } 138 return filepath.Join(ks.keysDirPath, filename) 139 } 140 141 // Encryptdata encrypts the data given as 'data' with the password 'auth'. 142 func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) { 143 144 salt := make([]byte, 32) 145 if _, err := io.ReadFull(rand.Reader, salt); err != nil { 146 panic("reading from crypto/rand failed: " + err.Error()) 147 } 148 derivedKey, err := scrypt.Key(auth, salt, scryptN, scryptR, scryptP, scryptDKLen) 149 if err != nil { 150 return CryptoJSON{}, err 151 } 152 encryptKey := derivedKey[:16] 153 154 iv := make([]byte, aes.BlockSize) // 16 155 if _, err := io.ReadFull(rand.Reader, iv); err != nil { 156 panic("reading from crypto/rand failed: " + err.Error()) 157 } 158 cipherText, err := aesCTRXOR(encryptKey, data, iv) 159 if err != nil { 160 return CryptoJSON{}, err 161 } 162 mac := crypto.SHA3(derivedKey[16:32], cipherText) 163 164 scryptParamsJSON := make(map[string]interface{}, 5) 165 scryptParamsJSON["n"] = scryptN 166 scryptParamsJSON["r"] = scryptR 167 scryptParamsJSON["p"] = scryptP 168 scryptParamsJSON["dklen"] = scryptDKLen 169 scryptParamsJSON["salt"] = hex.EncodeToString(salt) 170 cipherParamsJSON := cipherparamsJSON{ 171 IV: hex.EncodeToString(iv), 172 } 173 174 cryptoStruct := CryptoJSON{ 175 Cipher: "aes-128-ctr", 176 CipherText: hex.EncodeToString(cipherText), 177 CipherParams: cipherParamsJSON, 178 KDF: keyHeaderKDF, 179 KDFParams: scryptParamsJSON, 180 MAC: hex.EncodeToString(mac), 181 } 182 return cryptoStruct, nil 183 } 184 185 // EncryptKey encrypts a key using the specified scrypt parameters into a json 186 // blob that can be decrypted later on. 187 func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) { 188 cryptoStruct, err := EncryptDataV3(key.PrivateKey.PrivateKey()[:], []byte(auth), scryptN, scryptP) 189 if err != nil { 190 return nil, err 191 } 192 encryptedKeyJSONV3 := encryptedKeyJSONV3{ 193 hex.EncodeToString(key.Address[:]), 194 cryptoStruct, 195 key.Id.String(), 196 version, 197 } 198 return json.Marshal(encryptedKeyJSONV3) 199 } 200 201 // DecryptKey decrypts a key from a json blob, returning the private key itself. 202 func DecryptKey(keyjson []byte, auth string) (*Key, error) { 203 // Parse the json into a simple map to fetch the key version 204 m := make(map[string]interface{}) 205 if err := json.Unmarshal(keyjson, &m); err != nil { 206 return nil, err 207 } 208 var ( 209 keyBytes, keyId []byte 210 err error 211 ) 212 k := new(encryptedKeyJSONV3) 213 if err := json.Unmarshal(keyjson, k); err != nil { 214 return nil, err 215 } 216 keyBytes, keyId, err = decryptKeyV3(k, auth) 217 // Handle any decryption errors and return the key 218 if err != nil { 219 return nil, err 220 } 221 key, err := crypto.UnmarshalPrivateKey(keyBytes) 222 if err != nil { 223 return nil, err 224 } 225 226 return &Key{ 227 Id: keyId, 228 Address: key.Address(), 229 PrivateKey: key, 230 }, nil 231 } 232 233 func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) { 234 if cryptoJson.Cipher != "aes-128-ctr" { 235 return nil, fmt.Errorf("cipher not supported: %v", cryptoJson.Cipher) 236 } 237 mac, err := hex.DecodeString(cryptoJson.MAC) 238 if err != nil { 239 return nil, err 240 } 241 242 iv, err := hex.DecodeString(cryptoJson.CipherParams.IV) 243 if err != nil { 244 return nil, err 245 } 246 247 cipherText, err := hex.DecodeString(cryptoJson.CipherText) 248 if err != nil { 249 return nil, err 250 } 251 252 derivedKey, err := getKDFKey(cryptoJson, auth) 253 if err != nil { 254 return nil, err 255 } 256 257 calculatedMAC := crypto.SHA3(derivedKey[16:32], cipherText) 258 if !bytes.Equal(calculatedMAC, mac) { 259 return nil, ErrDecrypt 260 } 261 262 plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv) 263 if err != nil { 264 return nil, err 265 } 266 return plainText, err 267 } 268 269 func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) { 270 if keyProtected.Version != version { 271 return nil, nil, fmt.Errorf("version not supported: %v", keyProtected.Version) 272 } 273 keyId = uuid.Parse(keyProtected.Id) 274 plainText, err := DecryptDataV3(keyProtected.Crypto, auth) 275 if err != nil { 276 return nil, nil, err 277 } 278 return plainText, keyId, err 279 } 280 281 func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) { 282 authArray := []byte(auth) 283 salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string)) 284 if err != nil { 285 return nil, err 286 } 287 dkLen := ensureInt(cryptoJSON.KDFParams["dklen"]) 288 289 if cryptoJSON.KDF == keyHeaderKDF { 290 n := ensureInt(cryptoJSON.KDFParams["n"]) 291 r := ensureInt(cryptoJSON.KDFParams["r"]) 292 p := ensureInt(cryptoJSON.KDFParams["p"]) 293 return scrypt.Key(authArray, salt, n, r, p, dkLen) 294 295 } else if cryptoJSON.KDF == "pbkdf2" { 296 c := ensureInt(cryptoJSON.KDFParams["c"]) 297 prf := cryptoJSON.KDFParams["prf"].(string) 298 if prf != "hmac-sha256" { 299 return nil, fmt.Errorf("unsupported PBKDF2 PRF: %s", prf) 300 } 301 key := pbkdf2.Key(authArray, salt, c, dkLen, sha3.New256) 302 return key, nil 303 } 304 305 return nil, fmt.Errorf("unsupported KDF: %s", cryptoJSON.KDF) 306 } 307 308 // TODO: can we do without this when unmarshalling dynamic JSON? 309 // why do integers in KDF params end up as float64 and not int after 310 // unmarshal? 311 func ensureInt(x interface{}) int { 312 res, ok := x.(int) 313 if !ok { 314 res = int(x.(float64)) 315 } 316 return res 317 } 318 319 func aesCTRXOR(key, inText, iv []byte) ([]byte, error) { 320 // AES-128 is selected due to size of encryptKey. 321 aesBlock, err := aes.NewCipher(key) 322 if err != nil { 323 return nil, err 324 } 325 stream := cipher.NewCTR(aesBlock, iv) 326 outText := make([]byte, len(inText)) 327 stream.XORKeyStream(outText, inText) 328 return outText, err 329 }