如何在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
相关产品推荐
相关产品推荐

