github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/blockchain/pseudohsm/keystore_passphrase.go (about)

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