Golang SSH密钥包不匹配的问题排查:密钥正确但验证失败

Golang SSH密钥包不匹配的问题排查:密钥正确但验证失败 你好,能否帮我找出密钥不匹配的原因?我使用的是 golang.org/x/crypto/ssh 包,执行代码时显示: ssh: handshake failed: ssh: host key mismatch 我已经重新检查了主机和服务器的密钥文件,并且通过终端连接也没有问题。

我检查了 getHostKey() 函数返回的内容,结果发现,将字节转换为字符串时:

fmt.Println(string(hostKey.Marshal()))

它显示为: ecdsa-sha2-nistp25nistp256A- “T�.��2l�IR�” �mDĄ���F���7dɇ� # a��: ���YP�9�� ��-I ^ �c� 这可能是由于密钥没有以正确的编码显示,因此导致不匹配。如果是这样,该如何修复?

以下是完整的代码:

func getHostKey(host string) ssh.PublicKey {
	// parse OpenSSH known_hosts file
	// ssh or use ssh-keyscan to get initial key
	file, err := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"))
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	var hostKey ssh.PublicKey
	for scanner.Scan() {
		fields := strings.Split(scanner.Text(), " ")
		if len(fields) != 3 {
			continue
		}
		if strings.Contains(fields[0], host) {

			var err error

			hostKey, _, _, _, err = ssh.ParseAuthorizedKey(scanner.Bytes())
			if err != nil {
				log.Fatalf("error parsing %q: %v", fields[2], err)
			}
			break
		}
	}

	if hostKey == nil {
		log.Fatalf("no hostkey found for %s", host)
	}

	return hostKey
}

func main() {
	host := "192.168.128.193"
	port := "22"
	user := "n0kk"
	pass := "password"
	cmd := "ps"

	// get host public key
	hostKey := getHostKey(host)

	fmt.Println(string(hostKey.Marshal()))

	// ssh client config
	config := &ssh.ClientConfig{
		User: user,
		Auth: []ssh.AuthMethod{
			ssh.Password(pass),
		},
		// allow any host key to be used (non-prod)
		// HostKeyCallback: ssh.InsecureIgnoreHostKey(),

		// verify host public key
		HostKeyCallback: ssh.FixedHostKey(hostKey),
		// optional host key algo list
		HostKeyAlgorithms: []string{
			ssh.KeyAlgoRSA,
			ssh.KeyAlgoDSA,
			ssh.KeyAlgoECDSA256,
			ssh.KeyAlgoECDSA384,
			ssh.KeyAlgoECDSA521,
			ssh.KeyAlgoED25519,
		},
		// optional tcp connect timeout
		Timeout: 5 * time.Second,
	}

	// connect
	client, err := ssh.Dial("tcp", host+":"+port, config)
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	// start session
	sess, err := client.NewSession()
	if err != nil {
		log.Fatal(err)
	}
	defer sess.Close()

	// setup standard out and error
	// uses writer interface
	sess.Stdout = os.Stdout
	sess.Stderr = os.Stderr

	// run single command
	err = sess.Run(cmd)
	if err != nil {
		log.Fatal(err)
	}
}

更多关于Golang SSH密钥包不匹配的问题排查:密钥正确但验证失败的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang SSH密钥包不匹配的问题排查:密钥正确但验证失败的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


问题出在getHostKey()函数中解析known_hosts文件的方式。当使用strings.Contains(fields[0], host)匹配主机时,如果known_hosts文件中有多个条目包含该IP地址(比如同时有192.168.128.193192.168.128.193,example.com),可能会匹配到错误的条目。

更可靠的方法是使用ssh.ParseKnownHosts()函数,它会正确处理known_hosts文件的格式。以下是修复后的代码:

func getHostKey(host string) ssh.PublicKey {
    knownHostsPath := filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts")
    
    // 使用ssh.ParseKnownHosts正确解析known_hosts文件
    file, err := os.Open(knownHostsPath)
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()
    
    // 读取文件内容
    content, err := io.ReadAll(file)
    if err != nil {
        log.Fatal(err)
    }
    
    // 解析所有已知主机
    knownHosts, err := ssh.ParseKnownHosts(content)
    if err != nil {
        log.Fatal(err)
    }
    
    // 查找匹配的主机密钥
    for _, knownHost := range knownHosts {
        for _, knownHostPattern := range knownHost.Patterns {
            // 精确匹配主机名或IP
            if knownHostPattern == host {
                return knownHost.Key
            }
            // 如果known_hosts中使用的是逗号分隔的主机列表
            hosts := strings.Split(knownHostPattern, ",")
            for _, h := range hosts {
                if h == host {
                    return knownHost.Key
                }
            }
        }
    }
    
    log.Fatalf("no hostkey found for %s", host)
    return nil
}

或者,更简单的方法是直接使用ssh.KnownHosts()作为HostKeyCallback:

func main() {
    host := "192.168.128.193"
    port := "22"
    user := "n0kk"
    pass := "password"
    cmd := "ps"
    
    knownHostsPath := filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts")
    
    // 使用ssh.KnownHosts自动处理known_hosts文件
    hostKeyCallback, err := ssh.KnownHosts(knownHostsPath)
    if err != nil {
        log.Fatal(err)
    }
    
    config := &ssh.ClientConfig{
        User: user,
        Auth: []ssh.AuthMethod{
            ssh.Password(pass),
        },
        HostKeyCallback: hostKeyCallback,
        HostKeyAlgorithms: []string{
            ssh.KeyAlgoRSA,
            ssh.KeyAlgoDSA,
            ssh.KeyAlgoECDSA256,
            ssh.KeyAlgoECDSA384,
            ssh.KeyAlgoECDSA521,
            ssh.KeyAlgoED25519,
        },
        Timeout: 5 * time.Second,
    }
    
    client, err := ssh.Dial("tcp", host+":"+port, config)
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()
    
    sess, err := client.NewSession()
    if err != nil {
        log.Fatal(err)
    }
    defer sess.Close()
    
    sess.Stdout = os.Stdout
    sess.Stderr = os.Stderr
    
    err = sess.Run(cmd)
    if err != nil {
        log.Fatal(err)
    }
}

如果问题仍然存在,可以添加调试代码查看实际接收到的密钥:

// 调试用:打印密钥指纹
config.HostKeyCallback = func(hostname string, remote net.Addr, key ssh.PublicKey) error {
    fmt.Printf("Host: %s\n", hostname)
    fmt.Printf("Key type: %s\n", key.Type())
    fmt.Printf("Key fingerprint: %s\n", ssh.FingerprintSHA256(key))
    
    // 调用原始的回调函数
    return hostKeyCallback(hostname, remote, key)
}

这样可以看到实际接收到的密钥指纹,与known_hosts文件中的指纹进行对比。

回到顶部