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 }