github.com/Finschia/finschia-sdk@v0.48.1/crypto/armor.go (about)

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