如何在Go中生成用于SSH的ECDSA密钥对?

huangapple go评论108阅读模式
英文:

How to Generate ECDSA Key Pair for SSH in Go?

问题

我正在尝试使用Go生成用于SSH的ECDSA密钥对,但我发现生成的私钥格式与ssh-keygen生成的格式不同,GitHub无法接受。

以下是通过ssh-keygen -t ecdsa -b 256生成的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-----

以下是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()
}

生成的结果如下:

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

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

为什么生成的SSH私钥与之前不同,如何使其正常工作?

英文:

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?

答案1

得分: 2

OpenSSH使用不同的格式来存储私钥,包括SEC1格式(由您的Go代码生成)、PKCS#8格式和较新的OpenSSH格式(使用ssh-keygen命令生成)。这在这里有详细描述,其中还包含了对OpenSSH格式的更详细解释。SEC1格式在这个帖子中有解释。

当前的Go代码生成了一个带有错误头部和尾部的SEC1密钥。这被证明是问题的原因!为了修复这个错误,在头部和尾部中必须将ECDSA替换为EC

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

也就是说,在Go代码的EncodeToMemory()调用中,Type: "ECDSA PRIVATE KEY"必须替换为Type: "EC PRIVATE KEY"

请注意,可以使用ssh-keygen进行格式之间的转换。例如,

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

data.key中包含的SEC1密钥转换为OpenSSH格式,参见这里

英文:

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.

huangapple
  • 本文由 发表于 2021年12月15日 01:28:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/70353152.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定