Go version

go version go1.23.1 linux/amd64

Output of go env in your module/workspace:

GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/root/.cache/go-build'
GOENV='/root/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='local'
GOTOOLDIR='/usr/local/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.23.1'
GODEBUG=''
GOTELEMETRY='local'
GOTELEMETRYDIR='/root/.config/go/telemetry'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='0'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -fno-caret-diagnostics -Qunused-arguments -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build2769731916=/tmp/go-build -gno-record-gcc-switches'

What did you do?

I generated two ed25519 private keys:

  1. with ssh-keygen
  2. with crypto/ed25519

1:

ssh-keygen -a 5000 -b 521  -t ed25519 -E sha512 -C "name@domain (a=5000 b=521  t=ed25519 E=sha512)"

2: (like this)

https://gist.github.com/rorycl/d300f3ab942fd79e6cc1f37db0c6260f

Then I converted BOTH to RFC4716 with:

ssh-keygen -ef "path/to/key"

and stored it in the correct path for proftpd.

What did you see happen?

Logging in with the key from ssh-keygen was possible via FileZilla Logging in with the golang generated key was not possible. It complains about the private key file not containing a private key!

To rule out FileZillas fault (which worked with the ssh-keaygen based key) I used the windows cmd:

ssh -i "path/to/key" user@ip

and got back:

Load key "C:\\Users\\user\\ssh\\key_test\\skel_id_ed25519": invalid format

So it seems like golangs library crypto/ed25519 is generating invalid keys.

Here btw the key (no worries I dont use it):

-----BEGIN OPENSSH PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEICD5FPTasx5/JS4aVWhVZ2agE9j6x71anKKxfEH3avNd
-----END OPENSSH PRIVATE KEY-----

It is very small and definitely smaller as the ssh-keygen based ones, which I was able to set to 521 bits.

Any help would be appreciated! :)

What did you expect to see?

  1. the golang generated key to work aswell
  2. https://github.com/golang/go/blob/master/src/crypto/ed25519/ed25519.go#L30-L39 beeing configuratable. as I want to adjust the keysizes to my needs.

Comment From: gabyhelp

Related Issues and Documentation

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

Comment From: ericlagergren

Ed25519 has a fixed key size of 256 bits. You cannot change it.

This means using both -b 521 -t ed25519 is incorrect. The man pages state that the -b flag is ignored when used with -t ed25519.

Comment From: the-hotmann

Ed25519 has a fixed key size of 256 bits. You cannot change it.

Thanks, you are correct.

This adresses 1 of the two points: - editing config (not possible - does not make sense!) - ed25519 generated from golang does not work.

That means, the key generated with ssh-keygen was no 521 bit key, but this does not change the fact. that the private key generated with Golang was way smaller and also did not work.

Do you have any recommendations of what I did wrong?

Comment From: the-hotmann

This is my code btw:

package main

import (
    "crypto/ed25519"
    "crypto/rand"
    "crypto/x509"
    "encoding/pem"
    "os"
    "strings"

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

func generateKeyPairED25519() error {

    var (
        err  error
        pub  ed25519.PublicKey
        priv ed25519.PrivateKey
    )

    pub, priv, err = ed25519.GenerateKey(rand.Reader)
    if err != nil {
        return err
    }

    privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(priv)
    if err != nil {
        return err
    }

    privateKeyBlock := &pem.Block{
        Type:  "OPENSSH PRIVATE KEY",
        Bytes: privateKeyBytes,
    }

    // store the private key to disk (for testing/debugging)
    err = os.WriteFile("./id_ed25519", pem.EncodeToMemory(privateKeyBlock), 0600)
    if err != nil {
        return err
    }

    publicKeyBytes, err := x509.MarshalPKIXPublicKey(pub)
    if err != nil {
        return err
    }

    publicKeyBlock := &pem.Block{
        Type:  "SSH2 PUBLIC KEY",
        Bytes: publicKeyBytes,
    }

    // store the private key to disk (for testing/debugging)
    err = os.WriteFile("./id_ed25519.pub", pem.EncodeToMemory(publicKeyBlock), 0600)
    if err != nil {
        return err
    }

    sshPublicKey, err := ssh.NewPublicKey(pub)
    if err != nil {
        return err
    }

    publicKeyRFC4716 := EncodeToRFC4716(ssh.MarshalAuthorizedKey(sshPublicKey))

    // store the public rfc4716 key to disk (for testing/debugging)
    err = os.WriteFile("./id_ed25519.rfc4716.pub", publicKeyRFC4716, 0600)
    if err != nil {
        return err
    }

    return nil
}

func EncodeToRFC4716(publicKeyData []byte) []byte {

    parts := strings.Split(string(publicKeyData), " ")
    if len(parts) < 2 {
        return []byte("")
    }

    var wrappedKey strings.Builder
    wrappedKey.WriteString("---- BEGIN SSH2 PUBLIC KEY ----\n")
    for i := 0; i < len(parts[1]); i += 70 {
        end := i + 70
        if end > len(parts[1]) {
            end = len(parts[1])
            wrappedKey.WriteString(parts[1][i:end])
        } else {
            wrappedKey.WriteString(parts[1][i:end] + "\n")
        }
    }
    wrappedKey.WriteString("---- END SSH2 PUBLIC KEY ----")

    return []byte(wrappedKey.String())
}

func main() {
    generateKeyPairED25519()
}

It stored the 3 keys (private, pub, pub.rfc4716) on the disc, where you execute the programm, so it is easier to test.

Comment From: ericlagergren

OpenSSH private keys aren't just PKCS #8 encoded private key bits. For example: https://pkg.go.dev/golang.org/x/crypto/ssh#MarshalPrivateKey

Comment From: the-hotmann

@ericlagergren thanks for the hint! I have rewritten the code - it now looks way cleaner and the private key also looks properly sized. But yet it does not work. I still get the invalid key error:

https://go.dev/play/p/I_vK-Va0wlr

the generated keys look ok, but it will not work if you use them. Is the problem with me?

Example output:

Private Key:

-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtz
c2gtZWQyNTUxOQAAACCeu+DqpCcnkdM/Jc9tHKJ2l/OyIjJdr3ih1rk4lOM31AAA
AIhaE8dzWhPHcwAAAAtzc2gtZWQyNTUxOQAAACCeu+DqpCcnkdM/Jc9tHKJ2l/Oy
IjJdr3ih1rk4lOM31AAAAEBsv5BbNpOwx4mwZddOcxPJzkFonpWM1nV+8tLvoE0Y
45674OqkJyeR0z8lz20conaX87IiMl2veKHWuTiU4zfUAAAAAAECAwQF
-----END OPENSSH PRIVATE KEY-----

Public Key:

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ674OqkJyeR0z8lz20conaX87IiMl2veKHWuTiU4zfU

Comment From: the-hotmann

I was able to do it. Not with crypto/ed25519 but with "github.com/mikesmitty/edkey". Thanks to @mikesmitty. I expected the standard library to be as simple as this.