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

Elliptic curve P521 bug once every 4 signature or checking attempts. Help! #47673

Closed
pedroalbanese opened this issue Aug 12, 2021 · 4 comments
Closed

Comments

@pedroalbanese
Copy link

What version of Go are you using (go version)?

$ go version
go version go1.15.3 windows/386

Does this issue reproduce with the latest release?

I don't know.

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
set GO111MODULE=
set GOARCH=386
set GOBIN=
set GOCACHE=C:\Users\0wner\AppData\Local\go-build
set GOENV=C:\Users\0wner\AppData\Roaming\go\env
set GOEXE=.exe
set GOFLAGS=
set GOHOSTARCH=386
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=C:\Users\0wner\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\0wner\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=c:\go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=c:\go\pkg\tool\windows_386
set GCCGO=gccgo
set GO386=sse2
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m32 -mthreads -fmessage-length=0 -fdebug-prefix-map=C:\cygwin\tmp\go-build258689694=/tmp/go-build -gno-record-gcc-switches

What did you do?

My implementation:

package main

import (
	"flag"
	"log"
 	"crypto/ecdsa"
 	"crypto/elliptic"
 	"crypto/sha256"
 	"crypto/rand"
 	"encoding/hex"
 	"fmt"
 	"hash"
 	"io"
 	"math/big"
 	"os"
	"errors"
 )

var (
	curve   = flag.String("curve", "p256", "Elliptic curve.")
	gen     = flag.Bool("keygen", false, "Generate keypair.")
	key     = flag.String("key", "", "Private/Public key.")
	sig     = flag.String("signature", "", "Signature.")
	sign    = flag.Bool("sign", false, "Sign with Private key.")
	verify  = flag.Bool("verify", false, "Verify with Public key.")
)

func main() {
	flag.Parse()

	var privatekey *ecdsa.PrivateKey
	var pubkey ecdsa.PublicKey
 	var pub *ecdsa.PublicKey
	var err error
	var pubkeyCurve elliptic.Curve

	if *curve == "p224" {
	pubkeyCurve = elliptic.P224() 
	} else if *curve == "p256" {
	pubkeyCurve = elliptic.P256()
	} else if *curve == "p384" {
	pubkeyCurve = elliptic.P384()
	} else if *curve == "p521" {
	pubkeyCurve = elliptic.P521()
	}

	if *gen {
		if *key != "" {
			privatekey, err = ReadPrivateKeyFromHex(*key)
			if err != nil {
				log.Fatal(err)
			}
		} else {
		 	privatekey = new(ecdsa.PrivateKey)
		 	privatekey, err = ecdsa.GenerateKey(pubkeyCurve, rand.Reader) 

		 	if err != nil {
		 		fmt.Println(err)
		 		os.Exit(1)
		 	}
		}
	 	pubkey = privatekey.PublicKey
		fmt.Println("Private= " + WritePrivateKeyToHex(privatekey))
		fmt.Println("Public= " + WritePublicKeyToHex(&pubkey))
		os.Exit(0)
	}

 	// Sign ecdsa style
	if *sign {
	 	var h hash.Hash
	 	h = sha256.New()

		if _, err := io.Copy(h, os.Stdin); err != nil {
			panic(err)
		}

		privatekey, err = ReadPrivateKeyFromHex(*key)
		if err != nil {
			log.Fatal(err)
		}

		signature, err := Sign(h.Sum(nil), privatekey)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%x\n", signature)
	}

	// Verify
	if *verify {
	 	var h hash.Hash
	 	h = sha256.New()

		if _, err := io.Copy(h, os.Stdin); err != nil {
			panic(err)
		}

		pub, err = ReadPublicKeyFromHex(*key)
		if err != nil {
			log.Fatal(err)
		}

		sig, _ := hex.DecodeString(*sig)

	 	verifystatus := Verify(h.Sum(nil), sig, pub)
	 	fmt.Println(verifystatus) 
	}
}

func Verify(data, signature []byte, pubkey *ecdsa.PublicKey) bool {
	// hash message
	digest := sha256.Sum256(data)

	curveOrderByteSize := pubkey.Curve.Params().P.BitLen() / 8

	r, s := new(big.Int), new(big.Int)
	r.SetBytes(signature[:curveOrderByteSize])
	s.SetBytes(signature[curveOrderByteSize:])

	return ecdsa.Verify(pubkey, digest[:], r, s)
}

func Sign(data []byte, privkey *ecdsa.PrivateKey) ([]byte, error) {
	// hash message
	digest := sha256.Sum256(data)

	// sign the hash
	r, s, err := ecdsa.Sign(rand.Reader, privkey, digest[:])
	if err != nil {
		return nil, err
	}

	// encode the signature {R, S}
	// big.Int.Bytes() will need padding in the case of leading zero bytes
	params := privkey.Curve.Params()
	curveOrderByteSize := params.P.BitLen() / 8
	rBytes, sBytes := r.Bytes(), s.Bytes()
	signature := make([]byte, curveOrderByteSize*2)
	copy(signature[curveOrderByteSize-len(rBytes):], rBytes)
	copy(signature[curveOrderByteSize*2-len(sBytes):], sBytes)

	return signature, nil
}

func ReadPrivateKeyFromHex(Dhex string) (*ecdsa.PrivateKey, error) {
	var c elliptic.Curve
	if *curve == "p224" {
		c = elliptic.P224() 
	} else if *curve == "p256" {
		c = elliptic.P256()
	} else if *curve == "p384" {
		c = elliptic.P384()
	} else if *curve == "p521" {
		c = elliptic.P521()
	}

	d, err := hex.DecodeString(Dhex)
	if err != nil {
		return nil, err
	}
	k := new(big.Int).SetBytes(d)
	params := c.Params()
	one := new(big.Int).SetInt64(1)
	n := new(big.Int).Sub(params.N, one)
	if k.Cmp(n) >= 0 {
		return nil, errors.New("privateKey's D is overflow.")
	}
	priv := new(ecdsa.PrivateKey)
	priv.PublicKey.Curve = c
	priv.D = k
	priv.PublicKey.X, priv.PublicKey.Y = c.ScalarBaseMult(k.Bytes())
	return priv, nil
}

func WritePrivateKeyToHex(key *ecdsa.PrivateKey) string {
	return key.D.Text(16)
}

func ReadPublicKeyFromHex(Qhex string) (*ecdsa.PublicKey, error) {
	q, err := hex.DecodeString(Qhex)
	if err != nil {
		return nil, err
	}
	if len(q) == 65 && q[0] == byte(0x04) {
		q = q[1:]
	}
	if len(q) != 64 {
		return nil, errors.New("publicKey is not uncompressed.")
	}
	pub := new(ecdsa.PublicKey)
	if *curve == "p224" {
		pub.Curve = elliptic.P224() 
	} else if *curve == "p256" {
		pub.Curve = elliptic.P256()
	} else if *curve == "p384" {
		pub.Curve = elliptic.P384()
	} else if *curve == "p521" {
		pub.Curve = elliptic.P521()
	}
	pub.X = new(big.Int).SetBytes(q[:32])
	pub.Y = new(big.Int).SetBytes(q[32:])
	return pub, nil
}

func WritePublicKeyToHex(key *ecdsa.PublicKey) string {
	x := key.X.Bytes()
	y := key.Y.Bytes()
	if n := len(x); n < 32 {
		x = append(zeroByteSlice()[:32-n], x...)
	}
	if n := len(y); n < 32 {
		y = append(zeroByteSlice()[:32-n], y...)
	}
	c := []byte{}
	c = append(c, x...)
	c = append(c, y...)
	c = append([]byte{0x04}, c...)
	return hex.EncodeToString(c)
}

func zeroByteSlice() []byte {
	return []byte{
		0, 0, 0, 0,
		0, 0, 0, 0,
		0, 0, 0, 0,
		0, 0, 0, 0,
		0, 0, 0, 0,
		0, 0, 0, 0,
		0, 0, 0, 0,
		0, 0, 0, 0,
	}
}

Signature with P521 bug once every 4 signature or checking attempts, is unstable.

Error:

panic: runtime error: slice bounds out of range [-1:]

goroutine 1 [running]:
main.Sign(0x11f46080, 0x20, 0x20, 0x11c8de60, 0x11f46080, 0x20, 0x20, 0x13ef, 0x0)
        H:/PRGM/Linux/Go/jwt-go-master/cmd/jwt/ecdsa/ecdsa/main.go:139 +0x3a2
main.main()
        H:/PRGM/Linux/Go/jwt-go-master/cmd/jwt/ecdsa/ecdsa/main.go:82 +0x6e2

What did you expect to see?

What did you see instead?

@seankhliao
Copy link
Member

Unlike many projects, the Go project does not use GitHub Issues for general discussion or asking questions. GitHub Issues are used for tracking bugs and proposals only.

For questions please refer to https://github.com/golang/go/wiki/Questions

@pedroalbanese
Copy link
Author

It's a bug!!

@seankhliao
Copy link
Member

This is a bug in your code, not Go's standard library

@pedroalbanese
Copy link
Author

pedroalbanese commented Oct 16, 2021

func WritePrivateKeyToHex(key *ecdsa.PrivateKey) string {
	d := key.D.Bytes()
	if n := len(d); n < 32 {
		d = append(zeroByteSlice()[:64-n], d...)
	}
	c := []byte{}
	c = append(c, d...)
	return hex.EncodeToString(c)
}

Instead:

func WritePrivateKeyToHex(key *ecdsa.PrivateKey) string {
	return key.D.Text(16)
}

@golang golang locked and limited conversation to collaborators Oct 16, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants