Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add scrypt to armor (to enable the removal of bcrypt later) #15155

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 95 additions & 31 deletions crypto/armor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -25,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.
Expand All @@ -44,6 +46,80 @@ const (
// For further notes on security parameter choice, see README.md
var BcryptSecurityParameter uint32 = 12

var (
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

Expand Down Expand Up @@ -124,17 +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 {
saltBytes, encBytes := encryptPrivKey(privKey, passphrase)
header := map[string]string{
"kdf": "bcrypt",
"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
}
Expand All @@ -147,17 +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, passphrase string) (saltBytes []byte, encBytes []byte) {
saltBytes = crypto.CRandBytes(16)
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter)
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 generating bcrypt key from passphrase"))
panic(errorsmod.Wrap(err, "error deriving key from passphrase"))
}

key = crypto.Sha256(key) // get 32 bytes
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
Expand All @@ -171,20 +250,7 @@ func UnarmorDecryptPrivKey(armorStr string, passphrase string) (privKey cryptoty
return privKey, "", fmt.Errorf("unrecognized armor type: %v", blockType)
}

if header["kdf"] != "bcrypt" {
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, passphrase)
Comment on lines -174 to -187
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can remove these as we are checking for these in other places

privKey, err = decryptPrivKey(encBytes, header, passphrase)

if header[headerType] == "" {
header[headerType] = defaultAlgo
Expand All @@ -193,14 +259,12 @@ 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)
func decryptPrivKey(encBytes []byte, header map[string]string, passphrase string) (privKey cryptotypes.PrivKey, err error) {
key, err := deriveKey(header, passphrase)
if err != nil {
return privKey, errorsmod.Wrap(err, "error generating bcrypt key from passphrase")
return nil, err
}

key = crypto.Sha256(key) // Get 32 bytes

privKeyBytes, err := xsalsa20symmetric.DecryptSymmetric(encBytes, key)
if err != nil && err == xsalsa20symmetric.ErrCiphertextDecrypt {
return privKey, sdkerrors.ErrWrongPassword
Expand Down
Loading