github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/accounts/keystore/presale.go (about) 1 // This file is part of the go-sberex library. The go-sberex library is 2 // free software: you can redistribute it and/or modify it under the terms 3 // of the GNU Lesser General Public License as published by the Free 4 // Software Foundation, either version 3 of the License, or (at your option) 5 // any later version. 6 // 7 // The go-sberex library is distributed in the hope that it will be useful, 8 // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 10 // General Public License <http://www.gnu.org/licenses/> for more details. 11 12 package keystore 13 14 import ( 15 "crypto/aes" 16 "crypto/cipher" 17 "crypto/sha256" 18 "encoding/hex" 19 "encoding/json" 20 "errors" 21 "fmt" 22 23 "github.com/Sberex/go-sberex/accounts" 24 "github.com/Sberex/go-sberex/crypto" 25 "github.com/pborman/uuid" 26 "golang.org/x/crypto/pbkdf2" 27 ) 28 29 // creates a Key and stores that in the given KeyStore by decrypting a presale key JSON 30 func importPreSaleKey(keyStore keyStore, keyJSON []byte, password string) (accounts.Account, *Key, error) { 31 key, err := decryptPreSaleKey(keyJSON, password) 32 if err != nil { 33 return accounts.Account{}, nil, err 34 } 35 key.Id = uuid.NewRandom() 36 a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: keyStore.JoinPath(keyFileName(key.Address))}} 37 err = keyStore.StoreKey(a.URL.Path, key, password) 38 return a, key, err 39 } 40 41 func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error) { 42 preSaleKeyStruct := struct { 43 EncSeed string 44 EthAddr string 45 Email string 46 BtcAddr string 47 }{} 48 err = json.Unmarshal(fileContent, &preSaleKeyStruct) 49 if err != nil { 50 return nil, err 51 } 52 encSeedBytes, err := hex.DecodeString(preSaleKeyStruct.EncSeed) 53 if err != nil { 54 return nil, errors.New("invalid hex in encSeed") 55 } 56 if len(encSeedBytes) < 16 { 57 return nil, errors.New("invalid encSeed, too short") 58 } 59 iv := encSeedBytes[:16] 60 cipherText := encSeedBytes[16:] 61 /* 62 pyethsaletool generates the encryption key from password by 63 2000 rounds of PBKDF2 with HMAC-SHA-256 using password as salt (:(). 64 16 byte key length within PBKDF2 and resulting key is used as AES key 65 */ 66 passBytes := []byte(password) 67 derivedKey := pbkdf2.Key(passBytes, passBytes, 2000, 16, sha256.New) 68 plainText, err := aesCBCDecrypt(derivedKey, cipherText, iv) 69 if err != nil { 70 return nil, err 71 } 72 ethPriv := crypto.Keccak256(plainText) 73 ecKey := crypto.ToECDSAUnsafe(ethPriv) 74 75 key = &Key{ 76 Id: nil, 77 Address: crypto.PubkeyToAddress(ecKey.PublicKey), 78 PrivateKey: ecKey, 79 } 80 derivedAddr := hex.EncodeToString(key.Address.Bytes()) // needed because .Hex() gives leading "0x" 81 expectedAddr := preSaleKeyStruct.EthAddr 82 if derivedAddr != expectedAddr { 83 err = fmt.Errorf("decrypted addr '%s' not equal to expected addr '%s'", derivedAddr, expectedAddr) 84 } 85 return key, err 86 } 87 88 func aesCTRXOR(key, inText, iv []byte) ([]byte, error) { 89 // AES-128 is selected due to size of encryptKey. 90 aesBlock, err := aes.NewCipher(key) 91 if err != nil { 92 return nil, err 93 } 94 stream := cipher.NewCTR(aesBlock, iv) 95 outText := make([]byte, len(inText)) 96 stream.XORKeyStream(outText, inText) 97 return outText, err 98 } 99 100 func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) { 101 aesBlock, err := aes.NewCipher(key) 102 if err != nil { 103 return nil, err 104 } 105 decrypter := cipher.NewCBCDecrypter(aesBlock, iv) 106 paddedPlaintext := make([]byte, len(cipherText)) 107 decrypter.CryptBlocks(paddedPlaintext, cipherText) 108 plaintext := pkcs7Unpad(paddedPlaintext) 109 if plaintext == nil { 110 return nil, ErrDecrypt 111 } 112 return plaintext, err 113 } 114 115 // From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes 116 func pkcs7Unpad(in []byte) []byte { 117 if len(in) == 0 { 118 return nil 119 } 120 121 padding := in[len(in)-1] 122 if int(padding) > len(in) || padding > aes.BlockSize { 123 return nil 124 } else if padding == 0 { 125 return nil 126 } 127 128 for i := len(in) - 1; i > len(in)-int(padding)-1; i-- { 129 if in[i] != padding { 130 return nil 131 } 132 } 133 return in[:len(in)-int(padding)] 134 }