Home > Software design >  How to Generate ECDSA Key Pair for SSH in Go?
How to Generate ECDSA Key Pair for SSH in Go?

Time:12-17

I'm trying to generate ECDSA Key Pair for SSH with Go, but I find that the private key format is different from ssh-keygen and can't be accepted by GitHub.

Here's the 256-bit key pair generated via ssh-keygen -t ecdsa -b 256:

ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOJWUhO waiB aKvXO0xC5XTL6P/X/TIzQ4hgdXkDfmAfntOj/HXRIu4GulCvJgUoyTiF5Qt9j9gK6Z17szUv3s= root@9e5eef4b58c5

-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTiVlITvsGogfmir1ztMQuV0y j/1/0
yM0OIYHV5A35gH57To/x10SLuBrpQryYFKMk4heULfY/YCumde7M1L97AAAAsDs42wc7ON
sHAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOJWUhO waiB aKv
XO0xC5XTL6P/X/TIzQ4hgdXkDfmAfntOj/HXRIu4GulCvJgUoyTiF5Qt9j9gK6Z17szUv3
sAAAAgOpAIXW6rZQqgYZboSJXojH2diS26wfm6P3hn8cQVZrwAAAARcm9vdEA5ZTVlZWY0
YjU4YzUBAgMEBQYH
-----END OPENSSH PRIVATE KEY-----

Here's the generator code in Go:

// GenerateECDSAKeys generates ECDSA public and private key pair with given size for SSH.
func GenerateECDSAKeys(bitSize int) (pubKey string, privKey string, err error) {
    // generate private key
    var privateKey *ecdsa.PrivateKey
    if privateKey, err = ecdsa.GenerateKey(curveFromLength(bitSize), rand.Reader); err != nil {
        return
    }

    // encode public key
    var (
        bytes     []byte
        publicKey ssh.PublicKey
    )
    if publicKey, err = ssh.NewPublicKey(privateKey.Public()); err != nil {
        return
    }
    pubBytes := ssh.MarshalAuthorizedKey(publicKey)

    // encode private key
    if bytes, err = x509.MarshalECPrivateKey(privateKey); err != nil {
        return
    }
    privBytes := pem.EncodeToMemory(&pem.Block{
        Type:  "ECDSA PRIVATE KEY",
        Bytes: bytes,
    })

    return string(pubBytes), string(privBytes), nil
}

func curveFromLength(l int) elliptic.Curve {
    switch l {
    case 224:
        return elliptic.P224()
    case 256:
        return elliptic.P256()
    case 348:
        return elliptic.P384()
    case 521:
        return elliptic.P521()
    }
    return elliptic.P384()
}

And generated result:

ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNYTtNRlEKh/harLSfIsSziDkEQ8E7OJ7azhTBJi1Qx fDa6dGg9f/vudGEizJ5d9TINVLTP Jemwg6FBhajiVA=

-----BEGIN ECDSA PRIVATE KEY-----
MHcCAQEEIEGZZ/4aD6tf0sc1ovyctlGWRSFp7RGw5ovRONZKLg4eoAoGCCqGSM49
AwEHoUQDQgAE1hO01GUQqH FqstJ8ixLOIOQRDwTs4ntrOFMEmLVDH58Nrp0aD1/
  50YSLMnl31Mg1UtM/4l6bCDoUGFqOJUA==
-----END ECDSA PRIVATE KEY-----

So why SSH private key was so different after the generation, and how to make it works?

CodePudding user response:

OpenSSH uses different formats for private EC keys, the SEC1 (as generated by your Go code), the PKCS#8 or the newer OpenSSH format (as generated with the ssh-keygen command). This is described here, which also contains a more detailed explanation of the OpenSSH format. The SEC1 format is explained e.g. in this post.

The current Go code generates a SEC1 key with wrong header and footer. This turned out to be the cause of the problem! To fix the bug, ECDSA must be replaced by EC in header and footer:

-----BEGIN EC PRIVATE KEY-----
...
-----END EC PRIVATE KEY-----

i.e. in the Go code in the EncodeToMemory() call Type: "ECDSA PRIVATE KEY" must be replaced by Type: "EC PRIVATE KEY".

Note that a conversion between the formats is also possible e.g. with ssh-keygen. For instance

ssh-keygen -p -N "" -f data.key

converts the SEC1 key contained in data.key to the OpenSSH format, see here.

  • Related