From af94ce026485af11794688ad226fc1642bf6af69 Mon Sep 17 00:00:00 2001 From: Facundo Medica Date: Fri, 24 Feb 2023 13:11:46 -0300 Subject: [PATCH 1/6] feat: add scrypt to armor --- crypto/armor.go | 59 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/crypto/armor.go b/crypto/armor.go index b65854262387..d9869dc5e98c 100644 --- a/crypto/armor.go +++ b/crypto/armor.go @@ -8,6 +8,7 @@ import ( "github.com/cometbft/cometbft/crypto" "golang.org/x/crypto/openpgp/armor" //nolint:staticcheck + "golang.org/x/crypto/scrypt" errorsmod "cosmossdk.io/errors" @@ -44,6 +45,11 @@ const ( // For further notes on security parameter choice, see README.md var BcryptSecurityParameter uint32 = 12 +var ( + KDFBcrypt = "bcrypt" + KDFScrypt = "scrypt" +) + //----------------------------------------------------------------- // add armor @@ -129,9 +135,10 @@ func unarmorBytes(armorStr, blockType string) (bz []byte, header map[string]stri // Encrypt and armor the private key. func EncryptArmorPrivKey(privKey cryptotypes.PrivKey, passphrase string, algo string) string { - saltBytes, encBytes := encryptPrivKey(privKey, passphrase) + kdf := KDFScrypt //KDFBcrypt + saltBytes, encBytes := encryptPrivKey(privKey, kdf, passphrase) header := map[string]string{ - "kdf": "bcrypt", + "kdf": kdf, "salt": fmt.Sprintf("%X", saltBytes), } @@ -147,14 +154,27 @@ func EncryptArmorPrivKey(privKey cryptotypes.PrivKey, passphrase string, algo st // encrypt the given privKey with the passphrase using a randomly // generated salt and the xsalsa20 cipher. returns the salt and the // encrypted priv key. -func encryptPrivKey(privKey cryptotypes.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) { +func encryptPrivKey(privKey cryptotypes.PrivKey, kdf string, passphrase string) (saltBytes []byte, encBytes []byte) { saltBytes = crypto.CRandBytes(16) - key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter) - if err != nil { - panic(errorsmod.Wrap(err, "error generating bcrypt key from passphrase")) + var ( + key []byte + err error + ) + if kdf == KDFBcrypt { + key, err = bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter) + if err != nil { + panic(errorsmod.Wrap(err, "error generating bcrypt key from passphrase")) + } + key = crypto.Sha256(key) // get 32 bytes + } else if kdf == KDFScrypt { + key, err = scrypt.Key([]byte(passphrase), saltBytes, 1<<15, 8, 1, 32) + if err != nil { + panic(errorsmod.Wrap(err, "error generating scrypt key from passphrase")) + } + } else { + panic(fmt.Sprintf("unrecognized KDF: %s", kdf)) } - key = crypto.Sha256(key) // get 32 bytes privKeyBytes := legacy.Cdc.MustMarshal(privKey) return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key) @@ -171,7 +191,7 @@ func UnarmorDecryptPrivKey(armorStr string, passphrase string) (privKey cryptoty return privKey, "", fmt.Errorf("unrecognized armor type: %v", blockType) } - if header["kdf"] != "bcrypt" { + if header["kdf"] != KDFBcrypt && header["kdf"] != KDFScrypt { return privKey, "", fmt.Errorf("unrecognized KDF type: %v", header["kdf"]) } @@ -184,7 +204,7 @@ func UnarmorDecryptPrivKey(armorStr string, passphrase string) (privKey cryptoty return privKey, "", fmt.Errorf("error decoding salt: %v", err.Error()) } - privKey, err = decryptPrivKey(saltBytes, encBytes, passphrase) + privKey, err = decryptPrivKey(saltBytes, encBytes, header["kdf"], passphrase) if header[headerType] == "" { header[headerType] = defaultAlgo @@ -193,14 +213,23 @@ func UnarmorDecryptPrivKey(armorStr string, passphrase string) (privKey cryptoty return privKey, header[headerType], err } -func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privKey cryptotypes.PrivKey, err error) { - key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter) - if err != nil { - return privKey, errorsmod.Wrap(err, "error generating bcrypt key from passphrase") +func decryptPrivKey(saltBytes []byte, encBytes []byte, kdf, passphrase string) (privKey cryptotypes.PrivKey, err error) { + var key []byte + if kdf == KDFBcrypt { + key, err = bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter) + if err != nil { + panic(errorsmod.Wrap(err, "error generating bcrypt key from passphrase")) + } + key = crypto.Sha256(key) // get 32 bytes + } else if kdf == KDFScrypt { + key, err = scrypt.Key([]byte(passphrase), saltBytes, 1<<15, 8, 1, 32) + if err != nil { + panic(errorsmod.Wrap(err, "error generating scrypt key from passphrase")) + } + } else { + panic(fmt.Sprintf("unrecognized KDF: %s", kdf)) } - key = crypto.Sha256(key) // Get 32 bytes - privKeyBytes, err := xsalsa20symmetric.DecryptSymmetric(encBytes, key) if err != nil && err == xsalsa20symmetric.ErrCiphertextDecrypt { return privKey, sdkerrors.ErrWrongPassword From 4910f851e3c4f98055c97521246578c99e86fdc6 Mon Sep 17 00:00:00 2001 From: Facundo Medica Date: Fri, 24 Feb 2023 13:25:02 -0300 Subject: [PATCH 2/6] comment + little rename --- crypto/armor.go | 2 +- crypto/keyring/keyring.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crypto/armor.go b/crypto/armor.go index d9869dc5e98c..c0b02827e3ec 100644 --- a/crypto/armor.go +++ b/crypto/armor.go @@ -135,7 +135,7 @@ func unarmorBytes(armorStr, blockType string) (bz []byte, header map[string]stri // Encrypt and armor the private key. func EncryptArmorPrivKey(privKey cryptotypes.PrivKey, passphrase string, algo string) string { - kdf := KDFScrypt //KDFBcrypt + kdf := KDFScrypt // every new encrypted key will use scrypt saltBytes, encBytes := encryptPrivKey(privKey, kdf, passphrase) header := map[string]string{ "kdf": kdf, diff --git a/crypto/keyring/keyring.go b/crypto/keyring/keyring.go index 59e672ec0e43..cca353bde602 100644 --- a/crypto/keyring/keyring.go +++ b/crypto/keyring/keyring.go @@ -46,7 +46,7 @@ const ( passKeyringPrefix = "keyring-%s" // temporary pass phrase for exporting a key during a key rename - passPhrase = "temp" + tempPassPhrase = "temp" ) var ( @@ -454,7 +454,7 @@ func (ks keystore) Rename(oldName, newName string) error { return errorsmod.Wrap(ErrKeyAlreadyExists, fmt.Sprintf("rename failed, %s", newName)) } - armor, err := ks.ExportPrivKeyArmor(oldName, passPhrase) + armor, err := ks.ExportPrivKeyArmor(oldName, tempPassPhrase) if err != nil { return err } @@ -463,7 +463,7 @@ func (ks keystore) Rename(oldName, newName string) error { return err } - if err := ks.ImportPrivKey(newName, armor, passPhrase); err != nil { + if err := ks.ImportPrivKey(newName, armor, tempPassPhrase); err != nil { return err } From b9bd59aa00e563c70c11babb718949d25d2033d6 Mon Sep 17 00:00:00 2001 From: Facundo Medica Date: Fri, 24 Feb 2023 14:19:19 -0300 Subject: [PATCH 3/6] comment + little rename --- crypto/armor.go | 1 + 1 file changed, 1 insertion(+) diff --git a/crypto/armor.go b/crypto/armor.go index c0b02827e3ec..98ba2b6114da 100644 --- a/crypto/armor.go +++ b/crypto/armor.go @@ -167,6 +167,7 @@ func encryptPrivKey(privKey cryptotypes.PrivKey, kdf string, passphrase string) } key = crypto.Sha256(key) // get 32 bytes } else if kdf == KDFScrypt { + // TODO: we should probably store these params in the header key, err = scrypt.Key([]byte(passphrase), saltBytes, 1<<15, 8, 1, 32) if err != nil { panic(errorsmod.Wrap(err, "error generating scrypt key from passphrase")) From 0565008c17d01595a62ef325ffe161ba9079412d Mon Sep 17 00:00:00 2001 From: Facundo Medica Date: Fri, 24 Feb 2023 17:02:55 -0300 Subject: [PATCH 4/6] progress --- crypto/armor.go | 152 ++++++++++++++++++++++--------------- crypto/armor_test.go | 174 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 262 insertions(+), 64 deletions(-) diff --git a/crypto/armor.go b/crypto/armor.go index 98ba2b6114da..4366c90239fe 100644 --- a/crypto/armor.go +++ b/crypto/armor.go @@ -26,8 +26,9 @@ const ( defaultAlgo = "secp256k1" - headerVersion = "version" - headerType = "type" + headerVersion = "version" + headerType = "type" + headerKDFParams = "kdfparams" ) // BcryptSecurityParameter is security parameter var, and it can be changed within the lcd test. @@ -46,10 +47,79 @@ const ( var BcryptSecurityParameter uint32 = 12 var ( - KDFBcrypt = "bcrypt" - KDFScrypt = "scrypt" + KDFBcrypt = "bcrypt" + KDFScrypt = "scrypt" + DefaultKDF = KDFScrypt ) +// TODO: verify that these parameters are reasonable +var ( + ScryptDefaultN int = 1 << 15 + ScryptDefaultR int = 8 + ScryptDefaultP int = 1 + ScryptDefaultDKLen int = 32 +) + +// Scrypt params +type scryptParams struct { + N int `json:"n"` + R int `json:"r"` + P int `json:"p"` + DKLen int `json:"dklen"` +} + +func ScryptDefaultParams() scryptParams { + return scryptParams{ + N: ScryptDefaultN, + R: ScryptDefaultR, + P: ScryptDefaultP, + DKLen: ScryptDefaultDKLen, + } +} + +func (p scryptParams) String() string { + return fmt.Sprintf("n=%d,r=%d,p=%d,dklen=%d", p.N, p.R, p.P, p.DKLen) +} + +func parseScryptParams(params string) (scryptParams, error) { + var p scryptParams + _, err := fmt.Sscanf(params, "n=%d,r=%d,p=%d,dklen=%d", &p.N, &p.R, &p.P, &p.DKLen) + if err != nil { + return p, fmt.Errorf("invalid scrypt params: %v %s", err, params) + } + return p, nil +} + +func deriveKey(header map[string]string, passphrase string) ([]byte, error) { + kdf := header["kdf"] + salt, err := hex.DecodeString(header["salt"]) + if err != nil { + return nil, fmt.Errorf("error decoding salt: %v", err.Error()) + } + + if kdf == KDFBcrypt { + key, err := bcrypt.GenerateFromPassword(salt, []byte(passphrase), BcryptSecurityParameter) + if err != nil { + return nil, errorsmod.Wrap(err, "error generating bcrypt key from passphrase") + } + key = crypto.Sha256(key) // get 32 bytes + return key, nil + } else if kdf == KDFScrypt { + params, err := parseScryptParams(header[headerKDFParams]) + if err != nil { + return nil, errorsmod.Wrap(err, "error parsing scrypt params") + } + + key, err := scrypt.Key([]byte(passphrase), salt, params.N, params.R, params.P, params.DKLen) + if err != nil { + return nil, errorsmod.Wrap(err, "error generating scrypt key from passphrase") + } + return key, nil + } + + return nil, fmt.Errorf("unrecognized KDF type: %s", kdf) +} + //----------------------------------------------------------------- // add armor @@ -130,18 +200,21 @@ func unarmorBytes(armorStr, blockType string) (bz []byte, header map[string]stri return } -//----------------------------------------------------------------- +// ----------------------------------------------------------------- // encrypt/decrypt with armor // Encrypt and armor the private key. func EncryptArmorPrivKey(privKey cryptotypes.PrivKey, passphrase string, algo string) string { - kdf := KDFScrypt // every new encrypted key will use scrypt - saltBytes, encBytes := encryptPrivKey(privKey, kdf, passphrase) header := map[string]string{ - "kdf": kdf, - "salt": fmt.Sprintf("%X", saltBytes), + "kdf": DefaultKDF, + } + + if header["kdf"] == KDFScrypt { + header[headerKDFParams] = ScryptDefaultParams().String() } + header, encBytes := encryptPrivKey(privKey, header, passphrase) + if algo != "" { header[headerType] = algo } @@ -154,31 +227,16 @@ func EncryptArmorPrivKey(privKey cryptotypes.PrivKey, passphrase string, algo st // encrypt the given privKey with the passphrase using a randomly // generated salt and the xsalsa20 cipher. returns the salt and the // encrypted priv key. -func encryptPrivKey(privKey cryptotypes.PrivKey, kdf string, passphrase string) (saltBytes []byte, encBytes []byte) { - saltBytes = crypto.CRandBytes(16) - var ( - key []byte - err error - ) - if kdf == KDFBcrypt { - key, err = bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter) - if err != nil { - panic(errorsmod.Wrap(err, "error generating bcrypt key from passphrase")) - } - key = crypto.Sha256(key) // get 32 bytes - } else if kdf == KDFScrypt { - // TODO: we should probably store these params in the header - key, err = scrypt.Key([]byte(passphrase), saltBytes, 1<<15, 8, 1, 32) - if err != nil { - panic(errorsmod.Wrap(err, "error generating scrypt key from passphrase")) - } - } else { - panic(fmt.Sprintf("unrecognized KDF: %s", kdf)) +func encryptPrivKey(privKey cryptotypes.PrivKey, header map[string]string, passphrase string) (updatedHeader map[string]string, encBytes []byte) { + header["salt"] = fmt.Sprintf("%X", crypto.CRandBytes(16)) + key, err := deriveKey(header, passphrase) + if err != nil { + panic(errorsmod.Wrap(err, "error deriving key from passphrase")) } privKeyBytes := legacy.Cdc.MustMarshal(privKey) - return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key) + return header, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key) } // UnarmorDecryptPrivKey returns the privkey byte slice, a string of the algo type, and an error @@ -192,20 +250,7 @@ func UnarmorDecryptPrivKey(armorStr string, passphrase string) (privKey cryptoty return privKey, "", fmt.Errorf("unrecognized armor type: %v", blockType) } - if header["kdf"] != KDFBcrypt && header["kdf"] != KDFScrypt { - return privKey, "", fmt.Errorf("unrecognized KDF type: %v", header["kdf"]) - } - - if header["salt"] == "" { - return privKey, "", fmt.Errorf("missing salt bytes") - } - - saltBytes, err := hex.DecodeString(header["salt"]) - if err != nil { - return privKey, "", fmt.Errorf("error decoding salt: %v", err.Error()) - } - - privKey, err = decryptPrivKey(saltBytes, encBytes, header["kdf"], passphrase) + privKey, err = decryptPrivKey(encBytes, header, passphrase) if header[headerType] == "" { header[headerType] = defaultAlgo @@ -214,21 +259,10 @@ func UnarmorDecryptPrivKey(armorStr string, passphrase string) (privKey cryptoty return privKey, header[headerType], err } -func decryptPrivKey(saltBytes []byte, encBytes []byte, kdf, passphrase string) (privKey cryptotypes.PrivKey, err error) { - var key []byte - if kdf == KDFBcrypt { - key, err = bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter) - if err != nil { - panic(errorsmod.Wrap(err, "error generating bcrypt key from passphrase")) - } - key = crypto.Sha256(key) // get 32 bytes - } else if kdf == KDFScrypt { - key, err = scrypt.Key([]byte(passphrase), saltBytes, 1<<15, 8, 1, 32) - if err != nil { - panic(errorsmod.Wrap(err, "error generating scrypt key from passphrase")) - } - } else { - panic(fmt.Sprintf("unrecognized KDF: %s", kdf)) +func decryptPrivKey(encBytes []byte, header map[string]string, passphrase string) (privKey cryptotypes.PrivKey, err error) { + key, err := deriveKey(header, passphrase) + if err != nil { + return nil, err } privKeyBytes, err := xsalsa20symmetric.DecryptSymmetric(encBytes, key) diff --git a/crypto/armor_test.go b/crypto/armor_test.go index 60ed5a495aee..20c6f59afafa 100644 --- a/crypto/armor_test.go +++ b/crypto/armor_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "cosmossdk.io/depinject" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec/legacy" "github.com/cosmos/cosmos-sdk/crypto" @@ -26,7 +27,8 @@ import ( "github.com/cosmos/cosmos-sdk/types" ) -func TestArmorUnarmorPrivKey(t *testing.T) { +func TestScryptArmorUnarmorPrivKey(t *testing.T) { + crypto.DefaultKDF = crypto.KDFScrypt priv := secp256k1.GenPrivKey() armored := crypto.EncryptArmorPrivKey(priv, "passphrase", "") _, _, err := crypto.UnarmorDecryptPrivKey(armored, "wrongpassphrase") @@ -72,7 +74,55 @@ func TestArmorUnarmorPrivKey(t *testing.T) { require.Equal(t, "unrecognized KDF type: wrong", err.Error()) } -func TestArmorUnarmorPubKey(t *testing.T) { +func TestBcryptArmorUnarmorPrivKey(t *testing.T) { + crypto.DefaultKDF = crypto.KDFBcrypt + priv := secp256k1.GenPrivKey() + armored := crypto.EncryptArmorPrivKey(priv, "passphrase", "") + _, _, err := crypto.UnarmorDecryptPrivKey(armored, "wrongpassphrase") + require.Error(t, err) + decrypted, algo, err := crypto.UnarmorDecryptPrivKey(armored, "passphrase") + require.NoError(t, err) + require.Equal(t, string(hd.Secp256k1Type), algo) + require.True(t, priv.Equals(decrypted)) + + // empty string + decrypted, algo, err = crypto.UnarmorDecryptPrivKey("", "passphrase") + require.Error(t, err) + require.True(t, errors.Is(io.EOF, err)) + require.Nil(t, decrypted) + require.Empty(t, algo) + + // wrong key type + armored = crypto.ArmorPubKeyBytes(priv.PubKey().Bytes(), "") + _, _, err = crypto.UnarmorDecryptPrivKey(armored, "passphrase") + require.Error(t, err) + require.Contains(t, err.Error(), "unrecognized armor type") + + // armor key manually + encryptPrivKeyFn := func(privKey cryptotypes.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) { + saltBytes = cmtcrypto.CRandBytes(16) + key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), crypto.BcryptSecurityParameter) + require.NoError(t, err) + key = cmtcrypto.Sha256(key) // get 32 bytes + privKeyBytes := legacy.Cdc.Amino.MustMarshalBinaryBare(privKey) + return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key) + } + saltBytes, encBytes := encryptPrivKeyFn(priv, "passphrase") + + // wrong kdf header + headerWrongKdf := map[string]string{ + "kdf": "wrong", + "salt": fmt.Sprintf("%X", saltBytes), + "type": "secp256k", + } + armored = crypto.EncodeArmor("TENDERMINT PRIVATE KEY", headerWrongKdf, encBytes) + _, _, err = crypto.UnarmorDecryptPrivKey(armored, "passphrase") + require.Error(t, err) + require.Equal(t, "unrecognized KDF type: wrong", err.Error()) +} + +func TestScryptArmorUnarmorPubKey(t *testing.T) { + crypto.DefaultKDF = crypto.KDFScrypt // Select the encryption and storage for your cryptostore var cdc codec.Codec err := depinject.Inject(configurator.NewAppConfig(), &cdc) @@ -142,7 +192,88 @@ func TestArmorUnarmorPubKey(t *testing.T) { require.Equal(t, "unrecognized version: unknown", err.Error()) } -func TestArmorInfoBytes(t *testing.T) { +func TestBcryptArmorUnarmorPubKey(t *testing.T) { + crypto.DefaultKDF = crypto.KDFBcrypt + // Select the encryption and storage for your cryptostore + var cdc codec.Codec + err := depinject.Inject(configurator.NewAppConfig(), &cdc) + require.NoError(t, err) + + cstore := keyring.NewInMemory(cdc) + + // Add keys and see they return in alphabetical order + k, _, err := cstore.NewMnemonic("Bob", keyring.English, types.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) + require.NoError(t, err) + key, err := k.GetPubKey() + require.NoError(t, err) + armored := crypto.ArmorPubKeyBytes(legacy.Cdc.Amino.MustMarshalBinaryBare(key), "") + pubBytes, algo, err := crypto.UnarmorPubKeyBytes(armored) + require.NoError(t, err) + pub, err := legacy.PubKeyFromBytes(pubBytes) + require.NoError(t, err) + require.Equal(t, string(hd.Secp256k1Type), algo) + require.True(t, pub.Equals(key)) + + armored = crypto.ArmorPubKeyBytes(legacy.Cdc.Amino.MustMarshalBinaryBare(key), "unknown") + pubBytes, algo, err = crypto.UnarmorPubKeyBytes(armored) + require.NoError(t, err) + pub, err = legacy.PubKeyFromBytes(pubBytes) + require.NoError(t, err) + require.Equal(t, "unknown", algo) + require.True(t, pub.Equals(key)) + + armored, err = cstore.ExportPrivKeyArmor("Bob", "passphrase") + require.NoError(t, err) + _, _, err = crypto.UnarmorPubKeyBytes(armored) + require.Error(t, err) + require.Equal(t, `couldn't unarmor bytes: unrecognized armor type "TENDERMINT PRIVATE KEY", expected: "TENDERMINT PUBLIC KEY"`, err.Error()) + + // armor pubkey manually + header := map[string]string{ + "version": "0.0.0", + "type": "unknown", + } + armored = crypto.EncodeArmor("TENDERMINT PUBLIC KEY", header, pubBytes) + _, algo, err = crypto.UnarmorPubKeyBytes(armored) + require.NoError(t, err) + // return secp256k1 if version is 0.0.0 + require.Equal(t, "secp256k1", algo) + + // missing version header + header = map[string]string{ + "type": "unknown", + } + armored = crypto.EncodeArmor("TENDERMINT PUBLIC KEY", header, pubBytes) + bz, algo, err := crypto.UnarmorPubKeyBytes(armored) + require.Nil(t, bz) + require.Empty(t, algo) + require.Error(t, err) + require.Equal(t, "header's version field is empty", err.Error()) + + // unknown version header + header = map[string]string{ + "type": "unknown", + "version": "unknown", + } + armored = crypto.EncodeArmor("TENDERMINT PUBLIC KEY", header, pubBytes) + bz, algo, err = crypto.UnarmorPubKeyBytes(armored) + require.Nil(t, bz) + require.Empty(t, algo) + require.Error(t, err) + require.Equal(t, "unrecognized version: unknown", err.Error()) +} + +func TestScryptArmorInfoBytes(t *testing.T) { + crypto.DefaultKDF = crypto.KDFScrypt + bs := []byte("test") + armoredString := crypto.ArmorInfoBytes(bs) + unarmoredBytes, err := crypto.UnarmorInfoBytes(armoredString) + require.NoError(t, err) + require.True(t, bytes.Equal(bs, unarmoredBytes)) +} + +func TestBcryptArmorInfoBytes(t *testing.T) { + crypto.DefaultKDF = crypto.KDFBcrypt bs := []byte("test") armoredString := crypto.ArmorInfoBytes(bs) unarmoredBytes, err := crypto.UnarmorInfoBytes(armoredString) @@ -150,7 +281,26 @@ func TestArmorInfoBytes(t *testing.T) { require.True(t, bytes.Equal(bs, unarmoredBytes)) } -func TestUnarmorInfoBytesErrors(t *testing.T) { +func TestScryptUnarmorInfoBytesErrors(t *testing.T) { + crypto.DefaultKDF = crypto.KDFScrypt + unarmoredBytes, err := crypto.UnarmorInfoBytes("") + require.Error(t, err) + require.True(t, errors.Is(io.EOF, err)) + require.Nil(t, unarmoredBytes) + + header := map[string]string{ + "type": "Info", + "version": "0.0.1", + } + unarmoredBytes, err = crypto.UnarmorInfoBytes(crypto.EncodeArmor( + "TENDERMINT KEY INFO", header, []byte("plain-text"))) + require.Error(t, err) + require.Equal(t, "unrecognized version: 0.0.1", err.Error()) + require.Nil(t, unarmoredBytes) +} + +func TestBcryptUnarmorInfoBytesErrors(t *testing.T) { + crypto.DefaultKDF = crypto.KDFBcrypt unarmoredBytes, err := crypto.UnarmorInfoBytes("") require.Error(t, err) require.True(t, errors.Is(io.EOF, err)) @@ -183,7 +333,21 @@ func BenchmarkBcryptGenerateFromPassword(b *testing.B) { } } -func TestArmor(t *testing.T) { +func TestScryptArmor(t *testing.T) { + crypto.DefaultKDF = crypto.KDFScrypt + blockType := "MINT TEST" + data := []byte("somedata") + armorStr := crypto.EncodeArmor(blockType, nil, data) + + // Decode armorStr and test for equivalence. + blockType2, _, data2, err := crypto.DecodeArmor(armorStr) + require.Nil(t, err, "%+v", err) + assert.Equal(t, blockType, blockType2) + assert.Equal(t, data, data2) +} + +func TestBcryptArmor(t *testing.T) { + crypto.DefaultKDF = crypto.KDFBcrypt blockType := "MINT TEST" data := []byte("somedata") armorStr := crypto.EncodeArmor(blockType, nil, data) From 4bed3126ad20c79a5fe1ecc47243661edf852e36 Mon Sep 17 00:00:00 2001 From: Facundo Medica Date: Fri, 24 Feb 2023 17:07:09 -0300 Subject: [PATCH 5/6] fix --- crypto/keyring/keyring.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crypto/keyring/keyring.go b/crypto/keyring/keyring.go index cca353bde602..c09af01ab77f 100644 --- a/crypto/keyring/keyring.go +++ b/crypto/keyring/keyring.go @@ -46,7 +46,7 @@ const ( passKeyringPrefix = "keyring-%s" // temporary pass phrase for exporting a key during a key rename - tempPassPhrase = "temp" + tempPassphrase = "temp" ) var ( @@ -454,7 +454,7 @@ func (ks keystore) Rename(oldName, newName string) error { return errorsmod.Wrap(ErrKeyAlreadyExists, fmt.Sprintf("rename failed, %s", newName)) } - armor, err := ks.ExportPrivKeyArmor(oldName, tempPassPhrase) + armor, err := ks.ExportPrivKeyArmor(oldName, tempPassphrase) if err != nil { return err } @@ -463,7 +463,7 @@ func (ks keystore) Rename(oldName, newName string) error { return err } - if err := ks.ImportPrivKey(newName, armor, tempPassPhrase); err != nil { + if err := ks.ImportPrivKey(newName, armor, tempPassphrase); err != nil { return err } From 3193adeb257667b2a0fecde9bd28fb9f91736276 Mon Sep 17 00:00:00 2001 From: Facundo Medica Date: Fri, 24 Feb 2023 17:12:15 -0300 Subject: [PATCH 6/6] progress --- crypto/armor_test.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/crypto/armor_test.go b/crypto/armor_test.go index 20c6f59afafa..416e899f37a6 100644 --- a/crypto/armor_test.go +++ b/crypto/armor_test.go @@ -11,6 +11,7 @@ import ( "github.com/cometbft/cometbft/crypto/xsalsa20symmetric" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/crypto/scrypt" "cosmossdk.io/depinject" @@ -52,11 +53,12 @@ func TestScryptArmorUnarmorPrivKey(t *testing.T) { require.Contains(t, err.Error(), "unrecognized armor type") // armor key manually + params := crypto.ScryptDefaultParams() encryptPrivKeyFn := func(privKey cryptotypes.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) { saltBytes = cmtcrypto.CRandBytes(16) - key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), crypto.BcryptSecurityParameter) + key, err := scrypt.Key([]byte(passphrase), saltBytes, params.N, params.R, params.P, params.DKLen) require.NoError(t, err) - key = cmtcrypto.Sha256(key) // get 32 bytes + privKeyBytes := legacy.Cdc.Amino.MustMarshalBinaryBare(privKey) return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key) } @@ -64,14 +66,21 @@ func TestScryptArmorUnarmorPrivKey(t *testing.T) { // wrong kdf header headerWrongKdf := map[string]string{ - "kdf": "wrong", - "salt": fmt.Sprintf("%X", saltBytes), - "type": "secp256k", + "kdf": "wrong", + "kdfparams": params.String(), + "salt": fmt.Sprintf("%X", saltBytes), + "type": "secp256k", } armored = crypto.EncodeArmor("TENDERMINT PRIVATE KEY", headerWrongKdf, encBytes) _, _, err = crypto.UnarmorDecryptPrivKey(armored, "passphrase") require.Error(t, err) require.Equal(t, "unrecognized KDF type: wrong", err.Error()) + + // fix kdf and try again + headerWrongKdf["kdf"] = "scrypt" + armored = crypto.EncodeArmor("TENDERMINT PRIVATE KEY", headerWrongKdf, encBytes) + _, _, err = crypto.UnarmorDecryptPrivKey(armored, "passphrase") + require.NoError(t, err) } func TestBcryptArmorUnarmorPrivKey(t *testing.T) {