9

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 "your_email@example.com"

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-----
colm.anseo
  • 19,337
  • 4
  • 43
  • 52
switch
  • 460
  • 6
  • 15

1 Answers1

10

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)
colm.anseo
  • 19,337
  • 4
  • 43
  • 52