Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generate ed25519 key-pair compatible with openssh

Tags:

openssh

go

I want to generate an ssh key compatible with openssh using ed25519 in go to replace rsa.GenerateKey since github does not support it anymore.

It should be the equivalent of:

ssh-keygen -t ed25519 -C "[email protected]"

But I can't find a way to do it.

For now, I have this code:

func GenerateSSHKeys() (*ED25519Keys, error) {
    publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
    if err != nil {
        return nil, err
    }

    publicED25519Key, err := ssh.NewPublicKey(publicKey)

    if err != nil {
        return nil, err
    }

    pubKeyBytes := ssh.MarshalAuthorizedKey(publicED25519Key)

    bytes, err := x509.MarshalPKCS8PrivateKey(privateKey) 
    if err != nil {
        return nil, err
    }

    privBlock := pem.Block{
        Type:    "PRIVATE KEY",
        Headers: nil,
        Bytes:   bytes,
    }

    privatePEM := pem.EncodeToMemory(&privBlock)

    return &ED25519Keys{
        Public:  pubKeyBytes,
        Private: privatePEM,
    }, nil

}

But it seems that the private key is shorter, and I can't explain some weird behavior that I have using it with git or argocd (sometimes it works, but most of the time no).

-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEINV+5Hyey1xTblwsVGfGmDCMdZgKQdhf1ublkGO2Qaf+
-----END PRIVATE KEY-----

How can I end up with something like that :

-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACAxIu+ndqJXpEJLk5c2qsjPvUybP8OANZlSqLaOau9ZCQAAAKCocC5dqHAu
[...]
AAAEChVq8FJPCYbKnNFFuISac83mzF+DDFCDrLd9Xva9fQ2zEi76d2olekQkuTlzaqyM+9
TJs/w4A1mVKoto5q71kJAAAAFnlvdXJfZW1haWxAZXhhbXBsZS5jb20BAgMEBQYH
-----END OPENSSH PRIVATE KEY-----

2 Answers

Yes, I've run into this as well.

The x509 package does not support marshaling ed25519 key types in the format used by openssh, so as you've discovered, this code - which works with other key types - fails for ed25519 keys:

bytes, err := x509.MarshalPKCS8PrivateKey(privateKey)  // produces invalid output for ed25519 keys

There is a repo (github.com/mikesmitty/edkey) with a helper function edkey.MarshalED25519PrivateKey to address this:

/* Writes ed25519 private keys into the new OpenSSH private key format. I have no idea why this isn't implemented anywhere yet, you can do seemingly everything except write it to disk in the OpenSSH private key format. */

it seems to be modeled on the openssh source: sshkey.c sshkey_private_to_blob2

So either copy that helper function into your code (recommended as repo from 2017 is several years old) or reference it as an import:

import "github.com/mikesmitty/edkey"

pubKey, privKey, _ := ed25519.GenerateKey(rand.Reader)
publicKey, _ := ssh.NewPublicKey(pubKey)

pemKey := &pem.Block{
    Type:  "OPENSSH PRIVATE KEY",
    Bytes: edkey.MarshalED25519PrivateKey(privKey),  // <- marshals ed25519 correctly
}
privateKey := pem.EncodeToMemory(pemKey)
authorizedKey := ssh.MarshalAuthorizedKey(publicKey)

_ = ioutil.WriteFile("id_ed25519", privateKey, 0600)
_ = ioutil.WriteFile("id_ed25519.pub", authorizedKey, 0644)
like image 147
colm.anseo Avatar answered Jan 29 '26 20:01

colm.anseo


I had the same issue and finally solved it. Here's how to do this now using only the stdlibs:

package main

import (
    "crypto"
    "crypto/ed25519"
    "encoding/base64"
    "encoding/pem"
    "fmt"

    "golang.org/x/crypto/ssh"
)

func main() {
    // If rand is nil, crypto/rand.Reader will be used
    pub, priv, err := ed25519.GenerateKey(nil)
    if err != nil {
        panic(err)
    }
    p, err := ssh.MarshalPrivateKey(crypto.PrivateKey(priv), "")
    if err != nil {
        panic(err)
    }
    privateKeyPem := pem.EncodeToMemory(p)
    privateKeyString := string(privateKeyPem)
    publicKey, err := ssh.NewPublicKey(pub)
    if err != nil {
        panic(err)
    }
    publicKeyString := "ssh-ed25519" + " " + base64.StdEncoding.EncodeToString(publicKey.Marshal())
    fmt.Printf("Private Key:\n%s\n", privateKeyString)
    fmt.Printf("Public Key:\n%s\n", publicKeyString)
}
like image 25
Anton Avatar answered Jan 29 '26 21:01

Anton



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!