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