Skip to content

crypto: Private keys are not guaranteed to be valid #3439

@liamsi

Description

@liamsi

We currently generate private keys via reading 32 bytes from a random source:

func genPrivKey(rand io.Reader) PrivKeySecp256k1 {
privKeyBytes := [32]byte{}
_, err := io.ReadFull(rand, privKeyBytes[:])
if err != nil {
panic(err)
}
// crypto.CRandBytes is guaranteed to be 32 bytes long, so it can be
// casted to PrivKeySecp256k1.
return PrivKeySecp256k1(privKeyBytes)
}

As far as I can see, we do not make sure that those 32 bytes are guaranteed to be a valid secp256k1 private key (see for instance https://github.com/golang/go/blob/7ccd3583eddcd79679fb29cfc83a6e6fb6973f1e/src/crypto/ecdsa/ecdsa.go#L89-L100 how this can be ensured).

Depending on the underlying library signing, or, signature verification might fail if the key is 32 bytes which does not represent an integer which is > 0 and smaller than the curve's order of the basepoint.

Here is a test that (passes but) explains the problem in golang:

// crypto/secp256k1/secp256k1_nocgo_test.go
func TestSignWithInvalidKeys(t *testing.T) {
	msg := []byte("Suite B Implementer’s Guide to FIPS 186-3")
	// this is probably prevented by crypto/rand? (didn't verify):
	zeros := PrivKeySecp256k1([32]byte{})
	sigStr, err := zeros.Sign(msg)
	assert.NoError(t, err)
	pub := zeros.PubKey()

	// signature verification will fail but nothing obvious prevents
	// GenPrivKey to create an all-zero key ...
	require.False(t, pub.VerifyBytes(msg, sigStr))

	var n [32]byte
	nb := secp256k1.S256().N.Bytes()
	copy(n[:], nb)
	N := PrivKeySecp256k1(n)

	sigStr, err = N.Sign(msg)
	assert.NoError(t, err)
	pub = N.PubKey()

	// again: signature verification will fail but nothing prevents
	// GenPrivKey to randomnly create a key that represents N
	// (valid keys are < N):
	require.False(t, pub.VerifyBytes(msg, sigStr))

	one := new(big.Int).SetInt64(1)
	n1b := secp256k1.S256().N.Add(secp256k1.S256().N, one).Bytes()
	var nPlusOne [32]byte
	copy(nPlusOne[:], n1b)

	NplusOne := PrivKeySecp256k1(nPlusOne)
	// ouch, this one even panics 
	// (nothing prevents GenPrivateKey to generate N+1)
	assert.Panics(t, func() { NplusOne.Sign(msg) })
}

Note: these cases are very unlikely to happen but we should still make sure we only generate valid keys (as far as I can see we never did).

cc @ValarDragon

Metadata

Metadata

Assignees

No one assigned

    Labels

    T:breakingType: Breaking ChangeT:bugType Bug (Confirmed)T:securityType: Security (specify priority)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions