如何在 Go 中为 SSH 生成 ECDSA 密钥对?
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私钥生成后差别如此之大,如何让它发挥作用呢?
OpenSSH 使用不同的 EC 私钥格式,SEC1(由您的 Go 代码生成)、PKCS#8 或更新的 OpenSSH 格式(由 ssh-keygen 命令)。这是描述here, which also contains a more detailed explanation of the OpenSSH format. The SEC1 format is explained e.g. in this post.
当前的 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 格式,参见 。
我正在尝试使用 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私钥生成后差别如此之大,如何让它发挥作用呢?
OpenSSH 使用不同的 EC 私钥格式,SEC1(由您的 Go 代码生成)、PKCS#8 或更新的 OpenSSH 格式(由 ssh-keygen 命令)。这是描述here, which also contains a more detailed explanation of the OpenSSH format. The SEC1 format is explained e.g. in this post.
当前的 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 格式,参见