Skip to content

Instantly share code, notes, and snippets.

@bradleypeabody
Created August 2, 2016 16:52
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bradleypeabody/2bcbb71dc46eba160bc7362a98d9fc87 to your computer and use it in GitHub Desktop.
Save bradleypeabody/2bcbb71dc46eba160bc7362a98d9fc87 to your computer and use it in GitHub Desktop.
Example of supporting additional elliptic curves for ECDSA to sign and verify with different key sizes. (Example uses curves with bit sizes smaller than P224 to achieve shorter signatures. Signatures are compatible with standard stuff like OpenSSL.)
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"log"
"math/big"
"reflect"
)
func main() {
log.Printf("Performing ECDSA test")
signTool := NewDefaultSignTool()
// use normal base64 for ease of checking at command line;
// but for use on sites RawURLEncoding (the default from
// NewDefaultSignTool() is probably better)
signTool.B2A = base64.StdEncoding.EncodeToString
signTool.A2B = base64.StdEncoding.DecodeString
pubkey, err := signTool.DecodePublicKey(samplePublicKey)
if err != nil {
panic(err)
}
privkey, err := signTool.DecodePrivateKey(samplePrivateKey)
if err != nil {
panic(err)
}
fmt.Printf("Private key: %+v\n", privkey)
sig, err := signTool.SignBytes(privkey, []byte("Make America Great Again!"))
if err != nil {
panic(err)
}
fmt.Printf("Signature: %s (%d bytes)\n", sig, len(sig))
fmt.Printf("Public key: %+v\n", pubkey)
err = signTool.VerifyBytes(pubkey, []byte("Make America Great Again!"), sig)
fmt.Printf("Verify(1) returned: %+v\n", err)
err = signTool.VerifyBytes(pubkey, []byte("Make Canada Great Again!"), sig)
fmt.Printf("Verify(2) returned: %+v\n", err)
}
type SignTool struct {
Curve elliptic.Curve
B2A func([]byte) string
A2B func(string) ([]byte, error)
}
func NewDefaultSignTool() *SignTool {
return &SignTool{
Curve: elliptic.P256(),
B2A: base64.RawURLEncoding.EncodeToString,
A2B: base64.RawURLEncoding.DecodeString,
}
}
func (t *SignTool) DecodePrivateKey(b []byte) (*ecdsa.PrivateKey, error) {
block, _ := pem.Decode(b)
if block == nil {
return nil, fmt.Errorf("failed to find private key block")
}
// ret, err := x509.ParseECPrivateKey(block.Bytes)
ret, err := ParseCustomECPrivateKey(block.Bytes)
return ret, err
}
func (t *SignTool) DecodePublicKey(b []byte) (*ecdsa.PublicKey, error) {
block, _ := pem.Decode(b)
if block == nil {
return nil, fmt.Errorf("failed to find public key block")
}
// pubi, err := x509.ParsePKIXPublicKey(block.Bytes)
pubi, err := ParseECDSACustomPKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
if ret, ok := pubi.(*ecdsa.PublicKey); ok {
return ret, nil
}
return nil, fmt.Errorf("Parsed key of unknown type: %T", pubi)
}
// Create and return a digital signature of a hash of the specified data.
// The equivalent command at the command line is:
// echo -n 'Make America Great Again!' | openssl dgst -sha256 -sign priv1.pem > signature.dat
// (The only difference being that the output is encoded with the B2A function on SignTool,
// whereas OpenSSL will output the raw binary[ASN.1] data.)
func (t *SignTool) SignBytes(privkey *ecdsa.PrivateKey, b []byte) (string, error) {
hash := sha256.Sum256(b)
r, s, err := ecdsa.Sign(rand.Reader, privkey, hash[:])
if err != nil {
return "", err
}
asn1Data := []*big.Int{r, s}
sbytes, err := asn1.Marshal(asn1Data)
if err != nil {
return "", err
}
ret := t.B2A(sbytes)
return ret, nil
}
// Verify a digitial signature. Returns nil if all is well or an error indicating
// what went wrong. The equivalent command at the command line is:
// echo -n 'Make America Great Again!' | openssl dgst -verify pub1.pem -signature signature.dat
func (t *SignTool) VerifyBytes(pubkey *ecdsa.PublicKey, b []byte, s string) error {
return t.VerifyBytesN([]*ecdsa.PublicKey{pubkey}, b, s)
}
// Do a verify with multiple public keys possibly matching
func (t *SignTool) VerifyBytesN(pubkeys []*ecdsa.PublicKey, b []byte, s string) error {
if len(pubkeys) == 0 {
return fmt.Errorf("You must provide at least one key")
}
sigb, err := t.A2B(s)
if err != nil {
return err
}
asn1Data := []*big.Int{}
_, err = asn1.Unmarshal(sigb, &asn1Data)
if err != nil {
return err
}
if len(asn1Data) != 2 {
return fmt.Errorf("While decoding ASN.1 data, expected exactly 2 values, instead got %d", len(asn1Data))
}
er, es := asn1Data[0], asn1Data[1]
hash := sha256.Sum256(b)
for _, pubkey := range pubkeys {
if ecdsa.Verify(pubkey, hash[:], er, es) {
return nil
}
}
return fmt.Errorf("Verify failed, no keys matched")
}
// NOTE: (1) You should be using a recent version (1.0.2+) of OpenSSL in order
// for things to work properly. This stuff defintely does not work on the older
// OpenSSL 0.9.8. On a Mac you can do "brew install openssl"
// and then run the update binary directly, e.g.:
// /usr/local/Cellar/openssl/1.0.2g/bin/openssl
// Or you can symlink it to /usr/local/bin, e.g.:
// ln -s /usr/local/Cellar/openssl/1.0.2g/bin/openssl /usr/local/bin/
// Use:
// openssl version
// to verify the version you are using.
//
// NOTE: (2) To generate a new key from the command line: (adjust the name
// to be which ever curve you want to use, 'secp112r1' is the shortest)
// openssl ecparam -name secp112r1 -noout -genkey > ecdsa-private-key.pem
// To get the public part of it, do:
// cat ecdsa-private-key.pem | openssl ec -pubout
// // prime256v1 ("P-256") example:
// var samplePrivateKey = []byte(`-----BEGIN EC PRIVATE KEY-----
// MHcCAQEEIG7SPhnkj/nockBdLrim4hx/QLXO7/ECHMTlc3YepOhgoAoGCCqGSM49
// AwEHoUQDQgAEIT0qsh+0jdYDhK5+rSedhT7W/5rTRiulhphqtuplGFAyNiSh9I5t
// 6MsrIuxFQV7A/cWAt8qcbVscT3Q2l6iu3w==
// -----END EC PRIVATE KEY-----`)
// // secp128r1 example:
// var samplePrivateKey = []byte(`-----BEGIN EC PRIVATE KEY-----
// MEQCAQEEENiL3d7BfP0zcMVz1YsEUOmgBwYFK4EEAByhJAMiAAQ1msY3sYG+Of0Y
// KZTnoHRa2VIng/nOpMGVcjCVo6f4Zg==
// -----END EC PRIVATE KEY-----`)
// // secp112r1 example:
// var samplePrivateKey = []byte(`-----BEGIN EC PRIVATE KEY-----
// MD4CAQEEDlt2OUtbHvKINCKrbME3oAcGBSuBBAAGoSADHgAEGt/VpBtAgPXnQV5H
// 9ooJ1kHH/At5FjLq4P3zxw==
// -----END EC PRIVATE KEY-----`)
// secp160r1 example:
var samplePrivateKey = []byte(`-----BEGIN EC PRIVATE KEY-----
MFACAQEEFGXV9iYYNIC947An1J+jqx1s2qxjoAcGBSuBBAAIoSwDKgAEu/0pKKxY
BpoCSzBb6wJCr0o8QlOz8cOQih4NdYLCkVPFe9+778//xA==
-----END EC PRIVATE KEY-----`)
// // prime256v1 ("P-256") example:
// var samplePublicKey = []byte(`-----BEGIN PUBLIC KEY-----
// MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIT0qsh+0jdYDhK5+rSedhT7W/5rT
// RiulhphqtuplGFAyNiSh9I5t6MsrIuxFQV7A/cWAt8qcbVscT3Q2l6iu3w==
// -----END PUBLIC KEY-----`)
// // secp128r1 example:
// var samplePublicKey = []byte(`-----BEGIN PUBLIC KEY-----
// MDYwEAYHKoZIzj0CAQYFK4EEABwDIgAENZrGN7GBvjn9GCmU56B0WtlSJ4P5zqTB
// lXIwlaOn+GY=
// -----END PUBLIC KEY-----`)
// // secp112r1 example:
// var samplePublicKey = []byte(`-----BEGIN PUBLIC KEY-----
// MDIwEAYHKoZIzj0CAQYFK4EEAAYDHgAEGt/VpBtAgPXnQV5H9ooJ1kHH/At5FjLq
// 4P3zxw==
// -----END PUBLIC KEY-----`)
// secp160r1 example:
var samplePublicKey = []byte(`-----BEGIN PUBLIC KEY-----
MD4wEAYHKoZIzj0CAQYFK4EEAAgDKgAEu/0pKKxYBpoCSzBb6wJCr0o8QlOz8cOQ
ih4NdYLCkVPFe9+778//xA==
-----END PUBLIC KEY-----`)
// Define the elliptic curves we want to support. These are standard
// curves with values dumped from OpenSSL 1.0.2g.
// NOTE: Two of the "brainpool" curves were tested but do not work -
// the public key when unmarshalled returns curve.IsOnCurve(x,y) == false;
// not sure why that is. But the "secp..." ones below work.
var secp112r1 *elliptic.CurveParams
var secp112r1OID asn1.ObjectIdentifier = []int{1, 3, 132, 0, 6}
var secp128r1 *elliptic.CurveParams
var secp128r1OID asn1.ObjectIdentifier = []int{1, 3, 132, 0, 28}
var secp160r1 *elliptic.CurveParams
var secp160r1OID asn1.ObjectIdentifier = []int{1, 3, 132, 0, 8}
func init() {
// openssl ecparam -name secp112r1 -param_enc explicit -text
// Field Type: prime-field
// Prime:
// 00:db:7c:2a:bf:62:e3:5e:66:80:76:be:ad:20:8b
// A:
// 00:db:7c:2a:bf:62:e3:5e:66:80:76:be:ad:20:88
// B:
// 65:9e:f8:ba:04:39:16:ee:de:89:11:70:2b:22
// Generator (uncompressed):
// 04:09:48:72:39:99:5a:5e:e7:6b:55:f9:c2:f0:98:
// a8:9c:e5:af:87:24:c0:a2:3e:0e:0f:f7:75:00
// Order:
// 00:db:7c:2a:bf:62:e3:5e:76:28:df:ac:65:61:c5
// Cofactor: 1 (0x1)
// Seed:
// 00:f5:0b:02:8e:4d:69:6e:67:68:75:61:51:75:29:
// 04:72:78:3f:b1
secp112r1 = &elliptic.CurveParams{}
secp112r1.Name = "secp112r1"
secp112r1.P, _ = new(big.Int).SetString("00db7c2abf62e35e668076bead208b", 16) // Prime
secp112r1.N, _ = new(big.Int).SetString("00db7c2abf62e35e7628dfac6561c5", 16) // Order
secp112r1.B, _ = new(big.Int).SetString("659ef8ba043916eede8911702b22", 16) // B
secp112r1.Gx, _ = new(big.Int).SetString("09487239995a5ee76b55f9c2f098", 16) // Generator X
secp112r1.Gy, _ = new(big.Int).SetString("a89ce5af8724c0a23e0e0ff77500", 16) // Generator Y
secp112r1.BitSize = 112
// openssl ecparam -name secp128r1 -param_enc explicit -text
// Field Type: prime-field
// Prime:
// 00:ff:ff:ff:fd:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:
// ff:ff
// A:
// 00:ff:ff:ff:fd:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:
// ff:fc
// B:
// 00:e8:75:79:c1:10:79:f4:3d:d8:24:99:3c:2c:ee:
// 5e:d3
// Generator (uncompressed):
// 04:16:1f:f7:52:8b:89:9b:2d:0c:28:60:7c:a5:2c:
// 5b:86:cf:5a:c8:39:5b:af:eb:13:c0:2d:a2:92:dd:
// ed:7a:83
// Order:
// 00:ff:ff:ff:fe:00:00:00:00:75:a3:0d:1b:90:38:
// a1:15
// Cofactor: 1 (0x1)
// Seed:
// 00:0e:0d:4d:69:6e:67:68:75:61:51:75:0c:c0:3a:
// 44:73:d0:36:79
secp128r1 = &elliptic.CurveParams{}
secp128r1.Name = "secp128r1"
secp128r1.P, _ = new(big.Int).SetString("00fffffffdffffffffffffffffffffffff", 16) // Prime
secp128r1.N, _ = new(big.Int).SetString("00fffffffe0000000075a30d1b9038a115", 16) // Order
secp128r1.B, _ = new(big.Int).SetString("00e87579c11079f43dd824993c2cee5ed3", 16) // B
secp128r1.Gx, _ = new(big.Int).SetString("161ff7528b899b2d0c28607ca52c5b86", 16) // Generator X
secp128r1.Gy, _ = new(big.Int).SetString("cf5ac8395bafeb13c02da292dded7a83", 16) // Generator Y
secp128r1.BitSize = 128
// openssl ecparam -name secp160r1 -param_enc explicit -text
// Field Type: prime-field
// Prime:
// 00:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:
// ff:ff:7f:ff:ff:ff
// A:
// 00:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:
// ff:ff:7f:ff:ff:fc
// B:
// 1c:97:be:fc:54:bd:7a:8b:65:ac:f8:9f:81:d4:d4:
// ad:c5:65:fa:45
// Generator (uncompressed):
// 04:4a:96:b5:68:8e:f5:73:28:46:64:69:89:68:c3:
// 8b:b9:13:cb:fc:82:23:a6:28:55:31:68:94:7d:59:
// dc:c9:12:04:23:51:37:7a:c5:fb:32
// Order:
// 01:00:00:00:00:00:00:00:00:00:01:f4:c8:f9:27:
// ae:d3:ca:75:22:57
// Cofactor: 1 (0x1)
// Seed:
// 10:53:cd:e4:2c:14:d6:96:e6:76:87:56:15:17:53:
// 3b:f3:f8:33:45
secp160r1 = &elliptic.CurveParams{}
secp160r1.Name = "secp160r1"
secp160r1.P, _ = new(big.Int).SetString("00ffffffffffffffffffffffffffffffff7fffffff", 16) // Prime
secp160r1.N, _ = new(big.Int).SetString("0100000000000000000001f4c8f927aed3ca752257", 16) // Order
secp160r1.B, _ = new(big.Int).SetString("1c97befc54bd7a8b65acf89f81d4d4adc565fa45", 16) // B
secp160r1.Gx, _ = new(big.Int).SetString("4a96b5688ef573284664698968c38bb913cbfc82", 16) // Generator X
secp160r1.Gy, _ = new(big.Int).SetString("23a628553168947d59dcc912042351377ac5fb32", 16) // Generator Y
secp160r1.BitSize = 160
}
func namedCurveFromOID(oid asn1.ObjectIdentifier) *elliptic.CurveParams {
switch {
case reflect.DeepEqual(oid, secp112r1OID):
return secp112r1
case reflect.DeepEqual(oid, secp128r1OID):
return secp128r1
case reflect.DeepEqual(oid, secp160r1OID):
return secp160r1
}
return nil
}
// There isn't really a pluggable mechanism that lets you add elliptic curves
// into what is supported by the Go standard library. However, it's easy enough
// to copy and paste out the offending functions and add the functionality we
// want like that. Here goes nothing...
type publicKeyInfo struct {
Raw asn1.RawContent
Algorithm pkix.AlgorithmIdentifier
PublicKey asn1.BitString
}
var oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
var ErrUnknownEllipticCurve = errors.New("unknown elliptic curve")
// Like x509.ParsePKIXPublicKey() but with support for our own elliptic curves.
// Falls back to x509.ParsePKIXPublicKey() if the key does not use one of our own
// elliptic curves or is not an ECDSA key.
func ParseECDSACustomPKIXPublicKey(derBytes []byte) (pub interface{}, err error) {
var pki publicKeyInfo
if rest, err := asn1.Unmarshal(derBytes, &pki); err != nil {
return nil, err
} else if len(rest) != 0 {
return nil, errors.New("x509: trailing data after ASN.1 of public-key")
}
if !oidPublicKeyECDSA.Equal(pki.Algorithm.Algorithm) {
// return nil, errors.New("ParseECDSACustomPKIXPublicKey() only supports ECDSA public keys")
return x509.ParsePKIXPublicKey(derBytes)
}
ret, err := parsePublicKey(&pki)
if err == ErrUnknownEllipticCurve {
return x509.ParsePKIXPublicKey(derBytes)
}
return ret, err
}
func parsePublicKey(keyData *publicKeyInfo) (interface{}, error) {
asn1Data := keyData.PublicKey.RightAlign()
paramsData := keyData.Algorithm.Parameters.FullBytes
namedCurveOID := new(asn1.ObjectIdentifier)
rest, err := asn1.Unmarshal(paramsData, namedCurveOID)
if err != nil {
return nil, err
}
if len(rest) != 0 {
return nil, errors.New("x509: trailing data after ECDSA parameters")
}
// log.Printf("OID: %v\n", *namedCurveOID)
namedCurve := namedCurveFromOID(*namedCurveOID)
// log.Printf("Got curve: %v\n", namedCurve)
if namedCurve == nil {
return nil, ErrUnknownEllipticCurve
}
x, y := ellipticUnmarshal(namedCurve, asn1Data)
if x == nil {
return nil, errors.New("x509: failed to unmarshal elliptic curve point")
}
pub := &ecdsa.PublicKey{
Curve: namedCurve,
X: x,
Y: y,
}
return pub, nil
}
const ecPrivKeyVersion = 1
type ecPrivateKey struct {
Version int
PrivateKey []byte
NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"`
PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"`
}
// Like x509.ParseECPrivateKey() but supports our custom elliptic curves.
// Falls back to the x509 package implmentation if it's for a curve we don't
// recognize.
func ParseCustomECPrivateKey(der []byte) (key *ecdsa.PrivateKey, err error) {
var privKey ecPrivateKey
if _, err := asn1.Unmarshal(der, &privKey); err != nil {
return nil, errors.New("x509: failed to parse EC private key: " + err.Error())
}
if privKey.Version != ecPrivKeyVersion {
return nil, fmt.Errorf("x509: unknown EC private key version %d", privKey.Version)
}
var curve elliptic.Curve
curve = namedCurveFromOID(privKey.NamedCurveOID)
if curve == nil {
// return nil, errors.New("x509: unknown elliptic curve")
return x509.ParseECPrivateKey(der)
}
k := new(big.Int).SetBytes(privKey.PrivateKey)
curveOrder := curve.Params().N
if k.Cmp(curveOrder) >= 0 {
return nil, errors.New("x509: invalid elliptic curve private key value")
}
priv := new(ecdsa.PrivateKey)
priv.Curve = curve
priv.D = k
privateKey := make([]byte, (curveOrder.BitLen()+7)/8)
// Some private keys have leading zero padding. This is invalid
// according to [SEC1], but this code will ignore it.
for len(privKey.PrivateKey) > len(privateKey) {
if privKey.PrivateKey[0] != 0 {
return nil, errors.New("x509: invalid private key length")
}
privKey.PrivateKey = privKey.PrivateKey[1:]
}
// Some private keys remove all leading zeros, this is also invalid
// according to [SEC1] but since OpenSSL used to do this, we ignore
// this too.
copy(privateKey[len(privateKey)-len(privKey.PrivateKey):], privKey.PrivateKey)
priv.X, priv.Y = curve.ScalarBaseMult(privateKey)
return priv, nil
}
// Unmarshal converts a point, serialized by Marshal, into an x, y pair.
// It is an error if the point is not on the curve. On error, x = nil.
func ellipticUnmarshal(curve elliptic.Curve, data []byte) (x, y *big.Int) {
byteLen := (curve.Params().BitSize + 7) >> 3
if len(data) != 1+2*byteLen {
log.Printf("ellipticUnmarshal: data is wrong size, expected %d bytes, got %d bytes", 1+2*byteLen, len(data))
return
}
if data[0] != 4 { // uncompressed form
log.Printf("ellipticUnmarshal: point is not in compressed form (or is garbage), cannot continue")
return
}
x = new(big.Int).SetBytes(data[1 : 1+byteLen])
y = new(big.Int).SetBytes(data[1+byteLen:])
if !curve.IsOnCurve(x, y) {
log.Printf("ellipticUnmarshal: point is not on the curve, something is wrong here")
x, y = nil, nil
}
return
}
@Amberlyd
Copy link

Amberlyd commented Feb 23, 2021

very thankful!! I want to know how openssl is handled ecdsa
asn1Data := []*big.Int{r, s} sbytes, err := asn1.Marshal(asn1Data)
it's very helpful , and now my work have done, thank you very much!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment