├── README.md └── priv-key-from-geth └── main.go /README.md: -------------------------------------------------------------------------------- 1 | ## INFURA Tutorials 2 | 3 | Please visit our wiki at [https://github.com/INFURA/tutorials/wiki](https://github.com/INFURA/tutorials/wiki). 4 | -------------------------------------------------------------------------------- /priv-key-from-geth/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/ecdsa" 8 | "crypto/sha256" 9 | "encoding/hex" 10 | "encoding/json" 11 | "errors" 12 | "fmt" 13 | "io/ioutil" 14 | 15 | "github.com/ethereum/go-ethereum/common" 16 | "github.com/ethereum/go-ethereum/crypto" 17 | "github.com/pborman/uuid" 18 | "golang.org/x/crypto/pbkdf2" 19 | "golang.org/x/crypto/scrypt" 20 | ) 21 | 22 | const ( 23 | version = 3 24 | ) 25 | 26 | var ErrDecrypt = errors.New("could not decrypt key with given passphrase") 27 | 28 | type Key struct { 29 | Id uuid.UUID // Version 4 "random" for unique id not derived from key data 30 | // to simplify lookups we also store the address 31 | Address common.Address 32 | // we only store privkey as pubkey/address can be derived from it 33 | // privkey in this struct is always in plaintext 34 | PrivateKey *ecdsa.PrivateKey 35 | } 36 | 37 | type encryptedKeyJSONV3 struct { 38 | Address string `json:"address"` 39 | Crypto cryptoJSON `json:"crypto"` 40 | Id string `json:"id"` 41 | Version int `json:"version"` 42 | } 43 | 44 | type encryptedKeyJSONV1 struct { 45 | Address string `json:"address"` 46 | Crypto cryptoJSON `json:"crypto"` 47 | Id string `json:"id"` 48 | Version string `json:"version"` 49 | } 50 | 51 | type cryptoJSON struct { 52 | Cipher string `json:"cipher"` 53 | CipherText string `json:"ciphertext"` 54 | CipherParams cipherparamsJSON `json:"cipherparams"` 55 | KDF string `json:"kdf"` 56 | KDFParams map[string]interface{} `json:"kdfparams"` 57 | MAC string `json:"mac"` 58 | } 59 | 60 | type cipherparamsJSON struct { 61 | IV string `json:"iv"` 62 | } 63 | 64 | func main() { 65 | filename := "" 66 | password := "" 67 | 68 | key, err := GetKey(filename, password) 69 | if err != nil { 70 | fmt.Printf("Error: %v", err) 71 | } 72 | fmt.Printf("Address: %x\nPrivate Key: %x", key.Address, key.PrivateKey.D) 73 | } 74 | 75 | func GetKey(filename, auth string) (*Key, error) { 76 | // Load the key from the keystore and decrypt its contents 77 | keyjson, err := ioutil.ReadFile(filename) 78 | if err != nil { 79 | return nil, err 80 | } 81 | key, err := DecryptKey(keyjson, auth) 82 | if err != nil { 83 | return nil, err 84 | } 85 | return key, nil 86 | } 87 | 88 | // DecryptKey decrypts a key from a json blob, returning the private key itself. 89 | func DecryptKey(keyjson []byte, auth string) (*Key, error) { 90 | // Parse the json into a simple map to fetch the key version 91 | m := make(map[string]interface{}) 92 | if err := json.Unmarshal(keyjson, &m); err != nil { 93 | return nil, err 94 | } 95 | // Depending on the version try to parse one way or another 96 | var ( 97 | keyBytes, keyId []byte 98 | err error 99 | ) 100 | if version, ok := m["version"].(string); ok && version == "1" { 101 | k := new(encryptedKeyJSONV1) 102 | if err := json.Unmarshal(keyjson, k); err != nil { 103 | return nil, err 104 | } 105 | keyBytes, keyId, err = decryptKeyV1(k, auth) 106 | } else { 107 | k := new(encryptedKeyJSONV3) 108 | if err := json.Unmarshal(keyjson, k); err != nil { 109 | return nil, err 110 | } 111 | keyBytes, keyId, err = decryptKeyV3(k, auth) 112 | } 113 | // Handle any decryption errors and return the key 114 | if err != nil { 115 | return nil, err 116 | } 117 | key := crypto.ToECDSA(keyBytes) 118 | return &Key{ 119 | Id: uuid.UUID(keyId), 120 | Address: crypto.PubkeyToAddress(key.PublicKey), 121 | PrivateKey: key, 122 | }, nil 123 | } 124 | 125 | func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) { 126 | if keyProtected.Version != version { 127 | return nil, nil, fmt.Errorf("Version not supported: %v", keyProtected.Version) 128 | } 129 | 130 | if keyProtected.Crypto.Cipher != "aes-128-ctr" { 131 | return nil, nil, fmt.Errorf("Cipher not supported: %v", keyProtected.Crypto.Cipher) 132 | } 133 | 134 | keyId = uuid.Parse(keyProtected.Id) 135 | mac, err := hex.DecodeString(keyProtected.Crypto.MAC) 136 | if err != nil { 137 | return nil, nil, err 138 | } 139 | 140 | iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV) 141 | if err != nil { 142 | return nil, nil, err 143 | } 144 | 145 | cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText) 146 | if err != nil { 147 | return nil, nil, err 148 | } 149 | 150 | derivedKey, err := getKDFKey(keyProtected.Crypto, auth) 151 | if err != nil { 152 | return nil, nil, err 153 | } 154 | 155 | calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText) 156 | if !bytes.Equal(calculatedMAC, mac) { 157 | return nil, nil, ErrDecrypt 158 | } 159 | 160 | plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv) 161 | if err != nil { 162 | return nil, nil, err 163 | } 164 | return plainText, keyId, err 165 | } 166 | 167 | func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) { 168 | keyId = uuid.Parse(keyProtected.Id) 169 | mac, err := hex.DecodeString(keyProtected.Crypto.MAC) 170 | if err != nil { 171 | return nil, nil, err 172 | } 173 | 174 | iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV) 175 | if err != nil { 176 | return nil, nil, err 177 | } 178 | 179 | cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText) 180 | if err != nil { 181 | return nil, nil, err 182 | } 183 | 184 | derivedKey, err := getKDFKey(keyProtected.Crypto, auth) 185 | if err != nil { 186 | return nil, nil, err 187 | } 188 | 189 | calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText) 190 | if !bytes.Equal(calculatedMAC, mac) { 191 | return nil, nil, ErrDecrypt 192 | } 193 | 194 | plainText, err := aesCBCDecrypt(crypto.Keccak256(derivedKey[:16])[:16], cipherText, iv) 195 | if err != nil { 196 | return nil, nil, err 197 | } 198 | return plainText, keyId, err 199 | } 200 | 201 | func getKDFKey(cryptoJSON cryptoJSON, auth string) ([]byte, error) { 202 | authArray := []byte(auth) 203 | salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string)) 204 | if err != nil { 205 | return nil, err 206 | } 207 | dkLen := ensureInt(cryptoJSON.KDFParams["dklen"]) 208 | 209 | if cryptoJSON.KDF == "scrypt" { 210 | n := ensureInt(cryptoJSON.KDFParams["n"]) 211 | r := ensureInt(cryptoJSON.KDFParams["r"]) 212 | p := ensureInt(cryptoJSON.KDFParams["p"]) 213 | return scrypt.Key(authArray, salt, n, r, p, dkLen) 214 | 215 | } else if cryptoJSON.KDF == "pbkdf2" { 216 | c := ensureInt(cryptoJSON.KDFParams["c"]) 217 | prf := cryptoJSON.KDFParams["prf"].(string) 218 | if prf != "hmac-sha256" { 219 | return nil, fmt.Errorf("Unsupported PBKDF2 PRF: %s", prf) 220 | } 221 | key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New) 222 | return key, nil 223 | } 224 | 225 | return nil, fmt.Errorf("Unsupported KDF: %s", cryptoJSON.KDF) 226 | } 227 | 228 | // TODO: can we do without this when unmarshalling dynamic JSON? 229 | // why do integers in KDF params end up as float64 and not int after 230 | // unmarshal? 231 | func ensureInt(x interface{}) int { 232 | res, ok := x.(int) 233 | if !ok { 234 | res = int(x.(float64)) 235 | } 236 | return res 237 | } 238 | 239 | func aesCTRXOR(key, inText, iv []byte) ([]byte, error) { 240 | // AES-128 is selected due to size of encryptKey. 241 | aesBlock, err := aes.NewCipher(key) 242 | if err != nil { 243 | return nil, err 244 | } 245 | stream := cipher.NewCTR(aesBlock, iv) 246 | outText := make([]byte, len(inText)) 247 | stream.XORKeyStream(outText, inText) 248 | return outText, err 249 | } 250 | 251 | func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) { 252 | aesBlock, err := aes.NewCipher(key) 253 | if err != nil { 254 | return nil, err 255 | } 256 | decrypter := cipher.NewCBCDecrypter(aesBlock, iv) 257 | paddedPlaintext := make([]byte, len(cipherText)) 258 | decrypter.CryptBlocks(paddedPlaintext, cipherText) 259 | plaintext := pkcs7Unpad(paddedPlaintext) 260 | if plaintext == nil { 261 | return nil, ErrDecrypt 262 | } 263 | return plaintext, err 264 | } 265 | 266 | // From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes 267 | func pkcs7Unpad(in []byte) []byte { 268 | if len(in) == 0 { 269 | return nil 270 | } 271 | 272 | padding := in[len(in)-1] 273 | if int(padding) > len(in) || padding > aes.BlockSize { 274 | return nil 275 | } else if padding == 0 { 276 | return nil 277 | } 278 | 279 | for i := len(in) - 1; i > len(in)-int(padding)-1; i-- { 280 | if in[i] != padding { 281 | return nil 282 | } 283 | } 284 | return in[:len(in)-int(padding)] 285 | } 286 | --------------------------------------------------------------------------------