You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Go语言中无需密码获取加密SSH私钥的指纹?

可以在Go语言中无需密码获取加密SSH私钥的指纹

SSH私钥(尤其是OpenSSH格式的加密私钥)在文件中包含未加密的公钥信息,因此无需解密私钥就能提取公钥并计算指纹。下面是具体实现方案:

纯Go实现(针对OpenSSH格式私钥)

新的OpenSSH私钥格式(-----BEGIN OPENSSH PRIVATE KEY-----)内置了未加密的公钥字段,可以直接解析提取:

package main

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"strings"

	"golang.org/x/crypto/ssh"
)

// GetEncryptedSSHKeyFingerprint 从加密的SSH私钥中提取SHA256指纹,无需密码
func GetEncryptedSSHKeyFingerprint(privateKeyData []byte) (string, error) {
	// 检查是否为OpenSSH格式私钥
	if !bytes.HasPrefix(privateKeyData, []byte("-----BEGIN OPENSSH PRIVATE KEY-----")) {
		// 对于传统PEM格式加密私钥,可参考下文的fallback方案
		return "", fmt.Errorf("仅支持OpenSSH格式的加密私钥,传统PEM格式请使用ssh-keygen fallback")
	}

	// 移除PEM头尾标记
	trimmedContent := strings.TrimSpace(string(privateKeyData))
	trimmedContent = strings.TrimPrefix(trimmedContent, "-----BEGIN OPENSSH PRIVATE KEY-----")
	trimmedContent = strings.TrimSuffix(trimmedContent, "-----END OPENSSH PRIVATE KEY-----")

	// Base64解码私钥内容
	decodedData, err := base64.StdEncoding.DecodeString(trimmedContent)
	if err != nil {
		return "", fmt.Errorf("base64解码失败: %w", err)
	}

	// 解析OpenSSH私钥结构(参考OpenSSH PROTOCOL.key规范)
	reader := bytes.NewReader(decodedData)
	// 验证magic字符串
	magic, err := ssh.ReadString(reader)
	if err != nil {
		return "", fmt.Errorf("读取magic字符串失败: %w", err)
	}
	if magic != "openssh-key-v1" {
		return "", fmt.Errorf("不是有效的OpenSSH私钥")
	}

	// 跳过加密相关字段(ciphername、kdfname、kdfoptions)
	if _, err := ssh.ReadString(reader); err != nil {
		return "", err
	}
	if _, err := ssh.ReadString(reader); err != nil {
		return "", err
	}
	if _, err := ssh.ReadString(reader); err != nil {
		return "", err
	}

	// 读取密钥数量
	var keyCount uint32
	if err := ssh.Unmarshal(reader.Next(4), &keyCount); err != nil {
		return "", fmt.Errorf("读取密钥数量失败: %w", err)
	}
	if keyCount < 1 {
		return "", fmt.Errorf("私钥中无有效密钥")
	}

	// 提取未加密的公钥数据
	publicKeyBytes, err := ssh.ReadString(reader)
	if err != nil {
		return "", fmt.Errorf("提取公钥数据失败: %w", err)
	}

	// 解析公钥并计算SHA256指纹
	publicKey, err := ssh.ParsePublicKey([]byte(publicKeyBytes))
	if err != nil {
		return "", fmt.Errorf("解析公钥失败: %w", err)
	}
	return ssh.FingerprintSHA256(publicKey), nil
}

兼容传统PEM格式私钥的Fallback方案

对于传统PEM格式的加密私钥(如-----BEGIN RSA PRIVATE KEY-----且带有Proc-Type: 4,ENCRYPTED头),纯Go解析需要解密私钥(无法绕过密码),因此可以调用系统内置的ssh-keygen命令来获取指纹:

package main

import (
	"bytes"
	"fmt"
	"os/exec"
	"strings"
)

// GetEncryptedSSHKeyFingerprintWithSSHKeygen 通过ssh-keygen获取加密私钥的指纹
func GetEncryptedSSHKeyFingerprintWithSSHKeygen(privateKeyData []byte) (string, error) {
	cmd := exec.Command("ssh-keygen", "-l", "-f", "-")
	cmd.Stdin = bytes.NewReader(privateKeyData)

	output, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("ssh-keygen执行失败: %w, 输出: %s", err, string(output))
	}

	// 解析ssh-keygen输出,格式示例:"4096 SHA256:xxx user@host (RSA)"
	fields := strings.Fields(string(output))
	if len(fields) < 2 {
		return "", fmt.Errorf("无效的ssh-keygen输出格式")
	}
	return fields[1], nil
}

为什么ssh.ParsePrivateKey无法直接使用

ssh.ParsePrivateKey需要完整解密私钥才能返回密钥对象,而我们只需要公钥信息——这部分在OpenSSH格式私钥中是未加密的,因此可以绕过解密步骤直接提取,避免了PassPhraseMissingError。

应用场景

获取指纹后,你可以将其与SSH代理(如ssh-agent)中存储的公钥指纹对比,快速判断代理是否能处理该密钥,避免连接时逐个尝试所有密钥。

内容的提问来源于stack exchange,提问作者Kimmo Lehto

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 23:01:04