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 }