Verifying an ECSDA Signature in Go

Verifying an ECDSA (Elliptic Curve Digital Signature Algorithm) signature generated by Go’s ECDSA PrivateKey type and its Sign method is an exception — and a completely understandable one given Go’s ECDSA documentation explicitly states ”common uses should use the Sign function in the ECDSA package directly”:package crypto/ecdsafunc Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r,s, *big.Int, err error)This short tutorial walks you through the uncommon use case of verifying a signature []byte returned by the ECDSA PrivateKey type’s Sign method, which implements the crypto.Signer interface.package cryptotype Signer interface { Public() PublicKey Sign(rand io.Reader, digest []byte, opts SignerOpts) (signature []byte, err error)}package crypto/ecdsafunc (priv *PrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error)Whereas Go’s RSA package functions VerifyPKCS1v15 and VerifyPSS accept the signature []byte returned by the same package’s SignPKCS1v15 and SignPSS functions, ECDSA’s Verify function does not accept the signature []byte returned by the EDCSA *PrivateKey.Sign method as a parameter.package crypto/ecdsafunc Verify(pub *PublicKey, hash []byte, r, s, *big.Int) boolExamining ECDSA’s source code, PrivateKey.Sign’s returned signature []byte is actually an ASN1 (Abstract Syntax Notation One) encoding of the package’s unexported ecdsaSignature struct:package crypto/ecdsatype ecdsaSignature struct { R, S *big.Int }Parsing the R and S from the returned signature []byte requires you to unmarshal the ASN1 encoded signature []byte..This may appear relatively straight forward, but since ASN1’s Unmarshal function uses reflection and the ecdsaSignature struct is not exportable (lower case), preventing you from creating an instance of the struct, things become just slightly more complicated.package encoding/asn1func Unmarshal(b []byte, val interface{}) (rest []byte, err error)Referencing Go’s third law of reflection — “to modify a reflection object, the value must be settable” and “only upper case fields of a struct are settable” — create an exportable struct with the same fields as the unexported ecdsaSignature struct to provide a settable reflection object for the Unmarshal function.type ECDSASignature struct { R, S *big.Int }Create an instance of the new struct to use as the val interface{} parameter in ASN1’s Unmarshal function..You now have the means to parse the required R and S parameters for ECDSA’s Verify function to verify the digital signature — verification complete!e := &ECDSASignature{}_, err = asn1.Unmarshal(signature, e)verified := ecdsa.Verify(&privateKey.PublicKey, digest, e.R, e.S)Here is a complete example of how to sign and verify a message with ECDSA using a P–521 curve and SHA–512 hashing function and PrivateKey’s implementation of the crypto.Signer interface..Run using Go Playgroundpackage main// error handling and some return values (e.g. n, err) // removed for brevity's sakeimport ( "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "encoding/asn1" "fmt" "math/big")type ECDSASignature struct { R, S *big.Int}func main() { // construct a curve and generate a private key curve := elliptic.P521() privateKey, _ := ecdsa.GenerateKey(curve, rand.Reader) message := "test message" mac := crypto.SHA512.New() _, _ = mac.Write([]byte(message)) result := mac.Sum(nil) // generate signature using crypto.Signer interface signature, _ := privateKey.Sign(rand.Reader, result, crypto.SHA512) e := &ECDSASignature{} _, _ = asn1.Unmarshal(signature, e) verified := ecdsa.Verify(&privateKey.PublicKey, result, e.R, e.S) if verified { fmt.Print("verified") } else { fmt.Print("not verified") }}You can read more about ECDSA on Wikipedia or reference FIPS 186–3 (Federal Information Processing Standards Publication) Digital Signature Standards starting on pg..26..Check out NIST Special Publication 800–57 (Pt. 1 Revision 4) for some recommendations and best practices for managing cryptographic keys.. More details

Leave a Reply