Bu örnekte ECDSA'yı kullanarak bir özel anahtar ve bir genel anahtar oluşturacağız. ECDSA, hem imzalama hem de doğrulama için farklı anahtarlar kullanan asimetrik bir imzalama yöntemidir. Bu, istemci ve sunucu iletişimlerinde kullanışlıdır. Mesajı oluşturan kişi hem özel hem de genel anahtarları bilirken, kullanıcı yalnızca genel anahtarı bilebilir. Mesaj özel anahtarla imzalanır ve genel anahtarla doğrulanır.


RSA ve ECDSA arasında sıkışıp kalırsanız, neredeyse her zaman tavsiye ECDSA'yı kullanmak olacaktır. Daha fazla ayrıntı için Internet'i kontrol edebilirsiniz, ancak işte onlardan bazıları. Avantajları nedeniyle daha fazla uygulama ECDSA'ya yöneliyor.



Daha kısa sonuçlar için hex kodlayıcıya alternatif olarak base64.URLEncoding/base64.RawStdEncoding işlevlerini kullanabilirsiniz.


ECDSA paketi


package ecdsa

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"fmt"
)

type keyType string

const (
Public keyType = "PUBLIC KEY"
Private keyType = "PRIVATE KEY" // Or EC PRIVATE KEY
)

type Key struct {
private *ecdsa.PrivateKey
public *ecdsa.PublicKey
}

// NewKey accepts a Curve type to generate keys.
// REF: https://pkg.go.dev/crypto/elliptic#Curve
func NewKey(curve elliptic.Curve) (Key, error) {
prv, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return Key{}, err
}

return Key{
private: prv,
public: &prv.PublicKey,
}, nil
}

// NewKeyFromPrivate accepts a private key to reproduce keys.
// REF: https://pkg.go.dev/crypto/elliptic#Curve
func NewKeyFromPrivate(curve elliptic.Curve, private string) (Key, error) {
block, _ := pem.Decode([]byte(private))

prv, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return Key{}, err
}

return Key{
private: prv,
public: &prv.PublicKey,
}, nil
}

func (k Key) String(keyType keyType) (string, error) {
var (
enc []byte
err error
)

switch keyType {
case Public:
enc, err = x509.MarshalPKIXPublicKey(k.public)
case Private:
enc, err = x509.MarshalECPrivateKey(k.private)
default:
return "", fmt.Errorf("invalid key type: %s", keyType)
}

if err != nil {
return "", err
}

return string(pem.EncodeToMemory(&pem.Block{Type: string(keyType), Bytes: enc})), nil
}

package ecdsa

import (
"crypto/ecdsa"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"errors"
)

type Message struct {
key Key
text string
}

// NewMessage returns a new message.
func NewMessage(key Key, text string) Message {
return Message{
key: key,
text: text,
}
}

func (m Message) Sign() (string, error) {
hash := sha256.Sum256([]byte(m.text)) // Prefer blake2b

sig, err := ecdsa.SignASN1(rand.Reader, m.key.private, hash[:])
if err != nil {
return "", err
}

return hex.EncodeToString(sig), nil
}

func (m Message) Verify(signature string) error {
sig, err := hex.DecodeString(signature)
if err != nil {
return err
}

hash := sha256.Sum256([]byte(m.text)) // Prefer blake2b

if !ecdsa.VerifyASN1(m.key.public, hash[:], sig) {
return errors.New("invalid signature")
}

return nil
}

Kullanım


Aynı genel anahtarı yeniden oluşturmak için özel anahtarı ortam değişkenlerine kaydedebilir ve yeniden kullanabilirsiniz. Bunun için NewKeyFromPrivate yöntemini kullanın.


package main

import (
"crypto/elliptic"
"fmt"
"random/ecdsa"
)

func main() {
// FRESH KEY ---------------------------------------------------------------

key, err := ecdsa.NewKey(elliptic.P256())
if err != nil {
panic(err)
}

pub, err := key.String(ecdsa.Public)
if err != nil {
panic(err)
}

prv, err := key.String(ecdsa.Private)
if err != nil {
panic(err)
}

fmt.Printf("PUBLIC KEY (NewKey):\n%s\nPRIVATE KEY (NewKey):\n%s\n", pub, prv)

// KEY FROM EXISTING PRIVATE KEY ------------------------------------------

key, err = ecdsa.NewKeyFromPrivate(elliptic.P256(), prv)
if err != nil {
panic(err)
}

pub, err = key.String(ecdsa.Public)
if err != nil {
panic(err)
}

prv, err = key.String(ecdsa.Private)
if err != nil {
panic(err)
}

fmt.Printf("PUBLIC KEY (NewKeyFromPrivate):\n%s\nPRIVATE KEY (NewKeyFromPrivate):\n%s\n", pub, prv)

// SIGN AND VERIFY MESSAGE -------------------------------------------------

plainMessage := "I am a plain message but I will be signed soon!"

fmt.Printf("PLAIN MESSAGE:\n%s\n", plainMessage)

msg := ecdsa.NewMessage(key, plainMessage)

signature, err := msg.Sign()
if err != nil {
panic(err)
}

fmt.Printf("MESSAGE SIGNATURE:\n%s\n", signature)

if err := msg.Verify(signature); err != nil {
panic(err)
}

fmt.Println("VERIFY SIGNATURE:\nOK")

if err := msg.Verify(signature[:len(signature)-3]+"ABC"); err != nil {
panic(err)
}

fmt.Println("VERIFY SIGNATURE:\nOK")
}

Test


$ go run -race main.go
PUBLIC KEY (NewKey):
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOAKvZyZ+V8YwhvT3pmbXivGJK/pU
nFJpnQ5yF4w+VcZK/eyuEfaaBqdWPAWf5qIKEqc42vq/SwzYJAvZlpUCsg==
-----END PUBLIC KEY-----

PRIVATE KEY (NewKey):
-----BEGIN PRIVATE KEY-----
MHcCAQEEINPB45II2s55I08oM3Vix56rLApzLcFk6yj5ISEsyoYloAoGCCqGSM49
AwEHoUQDQgAEOAKvZyZ+V8YwhvT3pmbXivGJK/pUnFJpnQ5yF4w+VcZK/eyuEfaa
BqdWPAWf5qIKEqc42vq/SwzYJAvZlpUCsg==
-----END PRIVATE KEY-----

PUBLIC KEY (NewKeyFromPrivate):
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOAKvZyZ+V8YwhvT3pmbXivGJK/pU
nFJpnQ5yF4w+VcZK/eyuEfaaBqdWPAWf5qIKEqc42vq/SwzYJAvZlpUCsg==
-----END PUBLIC KEY-----

PRIVATE KEY (NewKeyFromPrivate):
-----BEGIN PRIVATE KEY-----
MHcCAQEEINPB45II2s55I08oM3Vix56rLApzLcFk6yj5ISEsyoYloAoGCCqGSM49
AwEHoUQDQgAEOAKvZyZ+V8YwhvT3pmbXivGJK/pUnFJpnQ5yF4w+VcZK/eyuEfaa
BqdWPAWf5qIKEqc42vq/SwzYJAvZlpUCsg==
-----END PRIVATE KEY-----

PLAIN MESSAGE:
I am a plain message but I will be signed soon!
MESSAGE SIGNATURE:
3045022059ca575ac9e672db5a4d8f38408434ab5fb59b6d0e9c5f0e7fd7da54b234a721022100b9b762be232af27ed958cf1aa7e10ed9519296467434547b998e9614653e54a0
VERIFY SIGNATURE:
OK
panic: invalid signature

goroutine 1 [running]:
main.main()
/main.go:70 +0x585
exit status 2