记一次 TortoiseGit SSH 认证失败的排查与修复

2026-05-05

用 TortoiseGit 拉取自托管 Gitea 仓库时,弹出了 TortoiseGitPlink 的安全提示,确认后却提示 "No supported authentication methods available"。折腾了一圈才把问题全部解决,记录一下排查思路和解决方案。

环境

  • 远端:自托管 Gitea,端口 2222,SSH 协议
  • 客户端:Windows 11,TortoiseGit,OpenSSH
  • 密钥:Ed25519,最初为 PuTTY .ppk 格式

排查路线

1. 主机密钥缓存

首次连接 SSH 服务器时,TortoiseGitPlink 会弹出安全提示框,询问是否信任服务器的主机密钥。点击"是"即可缓存,这是正常的 SSH 安全机制。

2. Pageant 进程干扰

确认主机密钥后仍然失败,报 "Server refused our key"。检查任务管理器发现 Pageant(PuTTY 认证代理)正在后台运行。

TortoiseGitPlink 默认会优先使用 Pageant 中已加载的密钥,而不是远端设置中指定的 .ppk 文件。如果 Pageant 中没有加载正确的密钥,就会认证失败。

在 Pageant 中添加 .ppk 文件后仍然失败,决定放弃 TortoiseGitPlink,改用系统自带的 OpenSSH。

3. 密钥格式转换

TortoiseGit 的 SSH 客户端切换到 OpenSSH 后,需要 OpenSSH PEM 格式的私钥,而不是 .ppk

PuTTYgen 的 GUI 可以手动做转换,但命令行参数在这个版本中不理想。最后用 Python 的 cryptography 库直接解析 .ppk 文件:

import base64
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import serialization

with open("key.ppk") as f:
    lines = f.readlines()

# 提取 Private-Lines 的 base64 数据
private_b64 = ""
in_private = False
for line in lines:
    line = line.strip()
    if line == "Private-Lines: 1":
        in_private = True
        continue
    if in_private and line.startswith("Private-MAC:"):
        break
    if in_private:
        private_b64 += line

raw = base64.b64decode(private_b64)
length = int.from_bytes(raw[:4], "big")
seed = raw[4 : 4 + length]

key = Ed25519PrivateKey.from_private_bytes(seed)
pem = key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.OpenSSH,
    encryption_algorithm=serialization.NoEncryption(),
)

with open("key_openssh", "wb") as f:
    f.write(pem)

4. 文件权限

OpenSSH 对私钥文件的权限非常严格。从 .ppk 转换出来的 PEM 文件默认权限太宽松,OpenSSH 会直接拒绝加载:

Bad permissions. Try removing permissions for user: ...

用 PowerShell 修复:

$acl = Get-Acl -LiteralPath "key"
$acl.SetAccessRuleProtection($true, $false)
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
    $env:USERNAME, "FullControl", "Allow"
)
$acl.SetAccessRule($rule)
Set-Acl -LiteralPath "key" -AclObject $acl

5. SSH 配置

为了避免每次在 URL 中硬编码端口 2222,把配置写进 ~/.ssh/config

Host gitea.logfun.xyz
    HostName gitea.logfun.xyz
    Port 2222
    User git
    IdentityFile "D:/path/to/key"
    IdentitiesOnly yes

远程 URL 也从 ssh://git@host:2222/... 改为 SCP 格式 git@host:...,端口由配置文件自动处理。

6. Git SSH 变体

切换 OpenSSH 后拉取,遇到:

fatal: ssh variant 'simple' does not support setting port

Git 默认的 SSH 变体是 simple,不支持在 URL 中解析端口。解决方案是全局配置:

git config --global ssh.variant ssh
git config --global core.sshCommand "ssh -F C:/Users/$USER/.ssh/config"

这样就告诉 Git 使用完整的 OpenSSH 功能,包括端口解析和配置文件。

最终配置

项目
SSH 客户端C:\Windows\System32\OpenSSH\ssh.exe
远程 URLgit@gitea.logfun.xyz:user/repo.git
私钥OpenSSH PEM 格式(非 .ppk
端口管理~/.ssh/config

总结

这次排查涉及了六个独立的环节,任何一个出问题都会导致认证失败:

  1. 主机密钥缓存 — 首次连接必须确认
  2. Pageant 冲突 — 运行 Pageant 时 TortoiseGitPlink 的行为会改变
  3. 密钥格式.ppk 和 OpenSSH PEM 不互通
  4. 文件权限 — OpenSSH 对私钥有严格的 ACL 要求
  5. SSH 配置 — 用 config 文件管理端口和密钥,而不是 URL
  6. Git SSH 变体 — 默认 simple 模式功能受限

对于 Windows 上的 TortoiseGit 用户,如果使用非标准 SSH 端口(2222、443 等),直接切换到系统 OpenSSH 并通过 ~/.ssh/config 管理配置,比纠结 TortoiseGitPlink + Pageant 要省心得多。

https://blog.logfun.xyz/blog/feed.xml