Signing JWTs with Go's crypto/ed25519

The crypto/ed25519 package was added to the standard library in Go 1.13. This package implements the Ed25519 Edwards-curve Digital Signature Algorithm. It offers significant speed and security improvements over RSA and it makes for a perfect signing method for JWTs. Unfortunately, the most popular JWT library for Go does not natively support it yet. It only supports ECDSA, HMAC, RSA, and RSAPSS, however, it is trivial to extend the package and satisfy the interface ( for EdDSA.

Implementing (

type SigningMethodEd25519 struct{}

func (m *SigningMethodEd25519) Alg() string {
	return "EdDSA"

func (m *SigningMethodEd25519) Verify(signingString string, signature string, key interface{}) error {
	var err error

	var sig []byte
	if sig, err = jwt.DecodeSegment(signature); err != nil {
		return err

	var ed25519Key ed25519.PublicKey
	var ok bool
	if ed25519Key, ok = key.(ed25519.PublicKey); !ok {
		return jwt.ErrInvalidKeyType

	if len(ed25519Key) != ed25519.PublicKeySize {
		return jwt.ErrInvalidKey

	if ok := ed25519.Verify(ed25519Key, []byte(signingString), sig); !ok {
		return ErrEd25519Verification

	return nil

func (m *SigningMethodEd25519) Sign(signingString string, key interface{}) (str string, err error) {
	var ed25519Key ed25519.PrivateKey
	var ok bool
	if ed25519Key, ok = key.(ed25519.PrivateKey); !ok {
		return "", jwt.ErrInvalidKeyType

	if len(ed25519Key) != ed25519.PrivateKeySize {
		return "", jwt.ErrInvalidKey

	// Sign the string and return the encoded result
	sig := ed25519.Sign(ed25519Key, []byte(signingString))
	return jwt.EncodeSegment(sig), nil

In order to work with the JWT methods above we need to have a (crypto/ed25119).PrivateKey and (crypto/ed25119).PublicKey loaded into our application to sign and verify tokens. For this case we will assume these keys are in PEM format and we can load them in from a file or environment varible.

Private and Public Keys in PEM Format

These are strings converted to []byte which is needed by (encoding/pem).Decode().

privateKeyPEM := []byte(`-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY-----`)

publicKeyPEM := []byte(`-----BEGIN PUBLIC KEY-----
-----END PUBLIC KEY-----`)

We have to parse these from their string format into actual (crypto/ed25119).PrivateKey and (crypto/ed25119).PublicKey types. We only have to do this once when our application starts up and we can pass these keys around to the functions that need them to do signing and verifying.

Decoding the Private Key

type ed25519PrivKey struct {
	Version          int
	ObjectIdentifier struct {
		ObjectIdentifier asn1.ObjectIdentifier
	PrivateKey []byte

var block *pem.Block
block, _ = pem.Decode(privateKeyPEM)

var asn1PrivKey ed25519PrivKey
asn1.Unmarshal(block.Bytes, &asn1PrivKey)

privateKey := ed25519.NewKeyFromSeed(asn1PrivKey.PrivateKey[2:])

Decoding the Public Key

type ed25519PubKey struct {
	OBjectIdentifier struct {
		ObjectIdentifier asn1.ObjectIdentifier
	PublicKey asn1.BitString

var block *pem.Block
block, _ = pem.Decode(publicKeyPEM)

var asn1PubKey ed25519PubKey
asn1.Unmarshal(block.Bytes, &asn1PubKey)

publicKey := ed25519.PublicKey(asn1PubKey.PublicKey.Bytes)

Now that we have implemented the ( and have a (crypto/ed25519).PrivateKey and (crypto/ed25519).PublicKey we can put this all together to sign and verify JWTs.

var Ed25519SigningMethod SigningMethodEd25519

jwt.RegisterSigningMethod(Ed25519SigningMethod.Alg(), func() jwt.SigningMethod { return &Ed25519SigningMethod })

token := jwt.NewWithClaims(&Ed25519SigningMethod, jwt.StandardClaims{
    IssuedAt: time.Now().Unix(),
    Issuer:   "urn:ed25519-jwt",
    Subject:  "",

jwtstring := token.SignedString(privateKey)
// Outputs: eyJhbGciOiJFRDI1NTE5IiwidHlwI...

token, _ := jwt.Parse(jwtstring, func(token *jwt.Token) (interface{}, error) {
    return publicKey, nil
fmt.Println(token.Issuer, token.Subject)
// Outputs: urn:ed25519-jwt

The jwtstring can be used by clients to verify their identity as any other JWT, but now we are using Ed25519 for signing and verifying tokens like you can with RSA. The website is a great tool for working with JWTs and you can see other libraries that support different signing methods. Since Ed25519 is fairly new you will notice that a lot are missing the "EdDSA" algorithm, but now you can at least implement this yourself with not a lot of code or importing a 3rd party dependency.