Golang中使用带密码的Ed25519私钥SSH时出现"PassPhraseMissingError"错误

Golang中使用带密码的Ed25519私钥SSH时出现"PassPhraseMissingError"错误 1. 您使用的 Go 版本是什么(go version)?

go1.16.2

2. 您使用的操作系统和处理器架构是什么?

windows/amd64

3. 您做了什么?

运行 go-git-clone.go 两次: 第一次使用由 ssh-keygen -t rsa -b 3072 生成的受密码保护的密钥。 第二次使用由 ssh-keygen -t ed25519 生成的受密码保护的密钥。 每次运行命令为: git_clone_go.exe <git server> <local dir> <path to keyfile> <password>

使用的 ssh-keygen 版本来自:C:\WINDOWS\System32\OpenSSH\ssh-keygen.exe,版本 7.6.0.0

RSA 密钥被正确解密并使用。 ed25519 密钥抛出错误: generate publickeys failed: ssh: this private key is passphrase protected

4. 您期望看到什么?

应该能够像使用 RSA 密钥一样,使用带密码的 ed25519 密钥。

5. 您实际看到了什么?

输出: generate publickeys failed: ssh: this private key is passphrase protected

输出信息中的 ssh: this private key is passphrase protected 部分是由 golang.org/x/crypto/ssh/keys.go 文件中的 func unencryptedOpenSSHKey 函数里的 “PassPhraseMissingError” 消息生成的。 func ParseRawPrivateKey 函数走了 case "OPENSSH PRIVATE KEY": 这个分支。


更多关于Golang中使用带密码的Ed25519私钥SSH时出现"PassPhraseMissingError"错误的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

更多关于Golang中使用带密码的Ed25519私钥SSH时出现"PassPhraseMissingError"错误的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


问题出在Go的x/crypto/ssh包对加密的OpenSSH格式Ed25519私钥支持不完整。当使用ParseRawPrivateKey解析带密码的Ed25519密钥时,会返回PassPhraseMissingError错误,因为该函数没有实现解密逻辑。

以下是解决方案的示例代码:

package main

import (
    "bytes"
    "encoding/pem"
    "errors"
    "fmt"
    "io/ioutil"
    
    "golang.org/x/crypto/ssh"
    "golang.org/x/crypto/ssh/agent"
)

// 自定义函数处理加密的OpenSSH私钥
func parseEncryptedPrivateKey(data []byte, passphrase []byte) (interface{}, error) {
    // 尝试解析为PEM格式
    block, _ := pem.Decode(data)
    if block != nil {
        // 如果是加密的PEM格式
        if x509.IsEncryptedPEMBlock(block) {
            decrypted, err := x509.DecryptPEMBlock(block, passphrase)
            if err != nil {
                return nil, err
            }
            return ssh.ParseRawPrivateKey(decrypted)
        }
        return ssh.ParseRawPrivateKey(data)
    }
    
    // 处理OpenSSH格式的加密密钥
    if bytes.Contains(data, []byte("OPENSSH PRIVATE KEY")) {
        // 使用ssh-agent兼容的方式解密
        key, err := ssh.ParseRawPrivateKeyWithPassphrase(data, passphrase)
        if err != nil {
            return nil, fmt.Errorf("failed to parse encrypted openssh key: %w", err)
        }
        return key, nil
    }
    
    return nil, errors.New("unsupported key format")
}

// 替代方案:使用ssh-agent
func loadEncryptedKeyWithAgent(keyPath string, passphrase []byte) (ssh.Signer, error) {
    keyData, err := ioutil.ReadFile(keyPath)
    if err != nil {
        return nil, err
    }
    
    // 创建内存中的ssh-agent
    ag := agent.NewKeyring()
    
    // 使用agent.Add方法添加加密的密钥
    addedKey := agent.AddedKey{
        PrivateKey:   keyData,
        Comment:      "",
        LifetimeSecs: 0,
    }
    
    // 对于加密的密钥,需要设置解密函数
    signer, err := ssh.ParsePrivateKeyWithPassphrase(keyData, passphrase)
    if err != nil {
        return nil, err
    }
    
    // 添加到agent
    if err := ag.Add(agent.AddedKey{
        PrivateKey:   signer,
        Comment:      "",
        LifetimeSecs: 0,
    }); err != nil {
        return nil, err
    }
    
    return signer, nil
}

// 主函数示例
func main() {
    keyPath := "path/to/ed25519_key"
    passphrase := []byte("your_password")
    
    // 方法1:使用自定义解析函数
    keyData, err := ioutil.ReadFile(keyPath)
    if err != nil {
        panic(err)
    }
    
    privateKey, err := parseEncryptedPrivateKey(keyData, passphrase)
    if err != nil {
        // 方法2:使用ssh-agent方式
        signer, err := loadEncryptedKeyWithAgent(keyPath, passphrase)
        if err != nil {
            panic(err)
        }
        
        // 使用signer进行SSH认证
        config := &ssh.ClientConfig{
            User: "git",
            Auth: []ssh.AuthMethod{
                ssh.PublicKeys(signer),
            },
            HostKeyCallback: ssh.InsecureIgnoreHostKey(),
        }
        
        // 连接SSH服务器
        client, err := ssh.Dial("tcp", "github.com:22", config)
        if err != nil {
            panic(err)
        }
        defer client.Close()
        
        fmt.Println("SSH connection established")
    }
}

对于go-git库,可以使用以下方式:

import (
    "gopkg.in/src-d/go-git.v4"
    "gopkg.in/src-d/go-git.v4/plumbing/transport/ssh"
)

func cloneWithEncryptedEd25519() error {
    publicKeys, err := ssh.NewPublicKeysFromFile("git", "path/to/ed25519_key", "password")
    if err != nil {
        // 如果上述方法失败,使用自定义的AuthMethod
        auth, err := createCustomAuth("path/to/ed25519_key", "password")
        if err != nil {
            return err
        }
        
        _, err = git.PlainClone("/path/to/repo", false, &git.CloneOptions{
            URL:      "git@github.com:user/repo.git",
            Auth:     auth,
            Progress: os.Stdout,
        })
        return err
    }
    
    _, err = git.PlainClone("/path/to/repo", false, &git.CloneOptions{
        URL:      "git@github.com:user/repo.git",
        Auth:     publicKeys,
        Progress: os.Stdout,
    })
    return err
}

func createCustomAuth(keyPath, password string) (ssh.AuthMethod, error) {
    keyData, err := ioutil.ReadFile(keyPath)
    if err != nil {
        return nil, err
    }
    
    signer, err := ssh.ParsePrivateKeyWithPassphrase(keyData, []byte(password))
    if err != nil {
        return nil, err
    }
    
    return ssh.PublicKeys(signer), nil
}

注意:这个问题在Go 1.16中确实存在,因为x/crypto/ssh包对加密的OpenSSH格式Ed25519密钥支持有限。建议升级到更新的Go版本,或者使用上述的变通方案。

回到顶部