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