github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/crypto/keys/mintkey/mintkey.go (about)

     1  package mintkey
     2  
     3  import (
     4  	"encoding/hex"
     5  	"fmt"
     6  
     7  	"github.com/tendermint/crypto/bcrypt"
     8  
     9  	"github.com/fibonacci-chain/fbc/libs/tendermint/crypto"
    10  	"github.com/fibonacci-chain/fbc/libs/tendermint/crypto/armor"
    11  	cryptoAmino "github.com/fibonacci-chain/fbc/libs/tendermint/crypto/encoding/amino"
    12  	"github.com/fibonacci-chain/fbc/libs/tendermint/crypto/xsalsa20symmetric"
    13  
    14  	tmos "github.com/fibonacci-chain/fbc/libs/tendermint/libs/os"
    15  
    16  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/crypto/keys/keyerror"
    17  )
    18  
    19  const (
    20  	blockTypePrivKey = "TENDERMINT PRIVATE KEY"
    21  	blockTypeKeyInfo = "TENDERMINT KEY INFO"
    22  	blockTypePubKey  = "TENDERMINT PUBLIC KEY"
    23  
    24  	defaultAlgo = "secp256k1"
    25  
    26  	headerVersion = "version"
    27  	headerType    = "type"
    28  )
    29  
    30  // BcryptSecurityParameter is a var so it can be changed within the lcd test
    31  // Making the bcrypt security parameter a var shouldn't be a security issue:
    32  // One can't verify an invalid key by maliciously changing the bcrypt
    33  // parameter during a runtime vulnerability. The main security
    34  // threat this then exposes would be something that changes this during
    35  // runtime before the user creates their key. This vulnerability must
    36  // succeed to update this to that same value before every subsequent call
    37  // to the keys command in future startups / or the attacker must get access
    38  // to the filesystem. However, with a similar threat model (changing
    39  // variables in runtime), one can cause the user to sign a different tx
    40  // than what they see, which is a significantly cheaper attack then breaking
    41  // a bcrypt hash. (Recall that the nonce still exists to break rainbow tables)
    42  // For further notes on security parameter choice, see README.md
    43  var BcryptSecurityParameter = 12
    44  
    45  //-----------------------------------------------------------------
    46  // add armor
    47  
    48  // Armor the InfoBytes
    49  func ArmorInfoBytes(bz []byte) string {
    50  	header := map[string]string{
    51  		headerType:    "Info",
    52  		headerVersion: "0.0.0",
    53  	}
    54  	return armor.EncodeArmor(blockTypeKeyInfo, header, bz)
    55  }
    56  
    57  // Armor the PubKeyBytes
    58  func ArmorPubKeyBytes(bz []byte, algo string) string {
    59  	header := map[string]string{
    60  		headerVersion: "0.0.1",
    61  	}
    62  	if algo != "" {
    63  		header[headerType] = algo
    64  	}
    65  	return armor.EncodeArmor(blockTypePubKey, header, bz)
    66  }
    67  
    68  //-----------------------------------------------------------------
    69  // remove armor
    70  
    71  // Unarmor the InfoBytes
    72  func UnarmorInfoBytes(armorStr string) ([]byte, error) {
    73  	bz, header, err := unarmorBytes(armorStr, blockTypeKeyInfo)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	if header[headerVersion] != "0.0.0" {
    79  		return nil, fmt.Errorf("unrecognized version: %v", header[headerVersion])
    80  	}
    81  	return bz, nil
    82  }
    83  
    84  // UnarmorPubKeyBytes returns the pubkey byte slice, a string of the algo type, and an error
    85  func UnarmorPubKeyBytes(armorStr string) (bz []byte, algo string, err error) {
    86  	bz, header, err := unarmorBytes(armorStr, blockTypePubKey)
    87  	if err != nil {
    88  		return nil, "", fmt.Errorf("couldn't unarmor bytes: %v", err)
    89  	}
    90  
    91  	switch header[headerVersion] {
    92  	case "0.0.0":
    93  		return bz, defaultAlgo, err
    94  	case "0.0.1":
    95  		if header[headerType] == "" {
    96  			header[headerType] = defaultAlgo
    97  		}
    98  		return bz, header[headerType], err
    99  	case "":
   100  		return nil, "", fmt.Errorf("header's version field is empty")
   101  	default:
   102  		err = fmt.Errorf("unrecognized version: %v", header[headerVersion])
   103  		return nil, "", err
   104  	}
   105  }
   106  
   107  func unarmorBytes(armorStr, blockType string) (bz []byte, header map[string]string, err error) {
   108  	bType, header, bz, err := armor.DecodeArmor(armorStr)
   109  	if err != nil {
   110  		return
   111  	}
   112  	if bType != blockType {
   113  		err = fmt.Errorf("unrecognized armor type %q, expected: %q", bType, blockType)
   114  		return
   115  	}
   116  	return
   117  }
   118  
   119  //-----------------------------------------------------------------
   120  // encrypt/decrypt with armor
   121  
   122  // Encrypt and armor the private key.
   123  func EncryptArmorPrivKey(privKey crypto.PrivKey, passphrase string, algo string) string {
   124  	saltBytes, encBytes := encryptPrivKey(privKey, passphrase)
   125  	header := map[string]string{
   126  		"kdf":  "bcrypt",
   127  		"salt": fmt.Sprintf("%X", saltBytes),
   128  	}
   129  	if algo != "" {
   130  		header[headerType] = algo
   131  	}
   132  	armorStr := armor.EncodeArmor(blockTypePrivKey, header, encBytes)
   133  	return armorStr
   134  }
   135  
   136  // encrypt the given privKey with the passphrase using a randomly
   137  // generated salt and the xsalsa20 cipher. returns the salt and the
   138  // encrypted priv key.
   139  func encryptPrivKey(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) {
   140  	saltBytes = crypto.CRandBytes(16)
   141  	key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter)
   142  	if err != nil {
   143  		tmos.Exit("Error generating bcrypt key from passphrase: " + err.Error())
   144  	}
   145  	key = crypto.Sha256(key) // get 32 bytes
   146  	privKeyBytes := privKey.Bytes()
   147  	return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key)
   148  }
   149  
   150  // UnarmorDecryptPrivKey returns the privkey byte slice, a string of the algo type, and an error
   151  func UnarmorDecryptPrivKey(armorStr string, passphrase string) (privKey crypto.PrivKey, algo string, err error) {
   152  	blockType, header, encBytes, err := armor.DecodeArmor(armorStr)
   153  	if err != nil {
   154  		return privKey, "", err
   155  	}
   156  	if blockType != blockTypePrivKey {
   157  		return privKey, "", fmt.Errorf("unrecognized armor type: %v", blockType)
   158  	}
   159  	if header["kdf"] != "bcrypt" {
   160  		return privKey, "", fmt.Errorf("unrecognized KDF type: %v", header["KDF"])
   161  	}
   162  	if header["salt"] == "" {
   163  		return privKey, "", fmt.Errorf("missing salt bytes")
   164  	}
   165  	saltBytes, err := hex.DecodeString(header["salt"])
   166  	if err != nil {
   167  		return privKey, "", fmt.Errorf("error decoding salt: %v", err.Error())
   168  	}
   169  	privKey, err = decryptPrivKey(saltBytes, encBytes, passphrase)
   170  
   171  	if header[headerType] == "" {
   172  		header[headerType] = defaultAlgo
   173  	}
   174  	return privKey, header[headerType], err
   175  }
   176  
   177  func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) {
   178  	key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter)
   179  	if err != nil {
   180  		tmos.Exit("error generating bcrypt key from passphrase: " + err.Error())
   181  	}
   182  	key = crypto.Sha256(key) // Get 32 bytes
   183  	privKeyBytes, err := xsalsa20symmetric.DecryptSymmetric(encBytes, key)
   184  	if err != nil && err.Error() == "Ciphertext decryption failed" {
   185  		return privKey, keyerror.NewErrWrongPassword()
   186  	} else if err != nil {
   187  		return privKey, err
   188  	}
   189  	privKey, err = cryptoAmino.PrivKeyFromBytes(privKeyBytes)
   190  	return privKey, err
   191  }