From a third party I'm receiving an ecdsa public key in uncompressed format
I wrote a basic parser (without any checks) that works for the specific key I am getting:
func HexToPublicKey(data []byte) *ecdsa.PublicKey {
x := new(big.Int).SetBytes(data[1:33])
y := new(big.Int).SetBytes(data[33:65])
pub := ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}
return &pub
}
Can anyone tell me if there is a more reliable method of doing this?
EDIT: (removed reference and added code)
The key I am receiving is in this format:
privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
ep := privateKey.PublicKey
ep2, _ := ep.ECDH()
fmt.Printf("received public key %x\n", ep2.Bytes())
output
received public key 0496cf21e333678b9c5cd0f6642e93f81e4544ef199ac0144404866309a139a421c5cf948b5ec8baefebaaf2af248835bf8b3f5c8570ff300c6f693f4015a39095
if I now split it, I get
start byte 04
X 96cf21e333678b9c5cd0f6642e93f81e4544ef199ac0144404866309a139a421
Y c5cf948b5ec8baefebaaf2af248835bf8b3f5c8570ff300c6f693f4015a39095
Before answering, I need to draw your attention to the fact that (contrary to its name) your implementation takes raw binary, not hexadecimal. The hexadecimal formatting was due to printing the bytes with the %x
format specifier. If you need to decode from hexadecimal, use encoding/hex.DecodeString
first.
Normally you would use crypto/elliptic.Unmarshal
, but it was deprecated. There is a proposal to replace it, but it hasn't yet been accepted. Until then, you can use this function based on a comment in that proposal:
package main
import (
"crypto/ecdh"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"errors"
"fmt"
"math/big"
)
// TODO: awaiting proposal acceptance: https://github.com/golang/go/issues/63963
func UnmarshalPublicKey(ecdsaCurve elliptic.Curve, bytes []byte) (*ecdsa.PublicKey, error) {
var curve ecdh.Curve
switch ecdsaCurve {
case elliptic.P256():
curve = ecdh.P256()
case elliptic.P384():
curve = ecdh.P384()
case elliptic.P521():
curve = ecdh.P521()
default:
return nil, errors.New("non-NIST curve")
}
// For error checking.
key, err := curve.NewPublicKey(bytes)
if err != nil {
return nil, err
}
// https://github.com/golang/go/issues/63963#issuecomment-1794706080
rawKey := key.Bytes()
switch key.Curve() {
case ecdh.P256():
return &ecdsa.PublicKey{
Curve: elliptic.P256(),
X: big.NewInt(0).SetBytes(rawKey[1:33]),
Y: big.NewInt(0).SetBytes(rawKey[33:]),
}, nil
case ecdh.P384():
return &ecdsa.PublicKey{
Curve: elliptic.P384(),
X: big.NewInt(0).SetBytes(rawKey[1:49]),
Y: big.NewInt(0).SetBytes(rawKey[49:]),
}, nil
case ecdh.P521():
return &ecdsa.PublicKey{
Curve: elliptic.P521(),
X: big.NewInt(0).SetBytes(rawKey[1:67]),
Y: big.NewInt(0).SetBytes(rawKey[67:]),
}, nil
default:
return nil, errors.New("cannot convert non-NIST *ecdh.PublicKey to *ecdsa.PublicKey")
}
}
func main() {
privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
publicKey := privateKey.PublicKey
ecdhKey, _ := publicKey.ECDH()
bytes := ecdhKey.Bytes()
unmarshaledPublicKey, err := UnmarshalPublicKey(elliptic.P256(), bytes)
if err != nil {
panic(err)
}
fmt.Printf("%t", publicKey.Equal(unmarshaledPublicKey)) // true
}