别把手机通行密钥当 SSH 密码:SSH 免密登录与 Passkey 的正确姿势
先说结论
SSH 免密登录和手机、Mac、Windows 上的通行密钥都在使用“公钥/私钥”这一类密码学思想,但它们不是同一个东西。SSH key 主要用来登录服务器、Git、跳板机和自动化任务;Passkey 主要用来登录网站、App 和账号体系。你可以把它们理解成两把都很先进的钥匙:一把开机房门,一把开网银和邮箱门,原理相似,但锁芯完全不同。
正确姿势是:每台常用电脑生成独立 SSH key,私钥只留在本机或硬件安全钥匙中;服务器只保存公钥;确认免密登录成功后再考虑关闭密码登录。Passkey 则交给 iPhone、Android、macOS 26、Windows 11 的系统密码管理器或硬件安全钥匙保存,用于网站和 App 登录,不要试图把手机 passkey 直接塞进
authorized_keys。本文给出三套一键脚本,覆盖 Windows 11、Ubuntu 26.04 和 macOS 26;同时给出人工自动执行和 Agent 自动配置两种方法,全部不依赖第三方服务,也不包含任何真实内网地址、完整计算机名、私有域名或密钥。

图 1:AI 生成封面。SSH key 和 Passkey 都是“钥匙”,但不应该混成同一把钥匙。
一、问题背景:为什么大家会把 SSH key 和 Passkey 搞混
这几年“无密码登录”这个词被说得太多了。网站告诉你可以创建 Passkey,手机告诉你可以用面容、指纹或锁屏密码登录,Windows 11 里有 Windows Hello,macOS 和 iPhone 有 iCloud 钥匙串,Android 有 Google 密码管理器。与此同时,运维和开发场景里也一直在说 SSH 免密登录:生成 key,把公钥放到服务器上,以后登录不用再输入服务器密码。
于是很多人自然会产生一个疑问:既然手机通行密钥也很安全,那我能不能用 iPhone 或 Android 的 Passkey 直接 SSH 到服务器?我在 Windows 上已经有 Windows Hello,为什么 ssh user@<ssh-target> 还是问我要密码?我在 Mac 上能用 Touch ID 解锁密码,为什么 SSH 免密还要生成 id_ed25519?
这个问题看起来像“工具不会用”,本质是把三层东西混在了一起:第一层是账号身份,例如网站账号、服务器系统账号、Git 账号;第二层是协议,例如 WebAuthn、SSH、HTTPS;第三层是密钥保存位置,例如文件、系统钥匙串、TPM、安全芯片、手机、硬件安全钥匙。Passkey 主要服务于 WebAuthn/FIDO2 的网站和 App 登录;SSH key 服务于 SSH 协议。它们都可能用到硬件保护,但不是互相替代的按钮。
图 2:最容易记住的边界:SSH key 开终端门,Passkey 开账号门。
二、问题表现:看上去都是“免密”,实际错误各不相同
常见表现大概有六类。
第一类是“我已经开了手机 Passkey,但 SSH 仍然要求密码”。这是正常现象。网站登录时,浏览器和网站会走 WebAuthn 流程;SSH 登录时,客户端和 sshd 走 SSH publickey 流程。手机 passkey 默认不会自动变成服务器上的 SSH 公钥。
第二类是“我把公钥复制到了服务器,但还是要输入密码”。这通常不是 Passkey 的问题,而是 SSH 基础配置问题:复制的是私钥而不是公钥、放错用户、authorized_keys 权限过宽、服务器禁用了 PubkeyAuthentication、客户端没用到正确私钥,或者你连到的并不是同一个账号。
第三类是“我把私钥复制到多台电脑,后来不知道哪台泄露了”。这属于免密登录的典型反面教材。私钥像家门钥匙,不应该为了省事复制一大把到不同电脑上。更好的做法是每台设备生成自己的 key,服务器端放多条公钥;哪台设备淘汰,就删掉对应那一条公钥。
第四类是“SSH 免密成功后,我立刻关闭了密码登录,结果把自己锁在门外”。关闭密码登录不是第一步,而是最后一步。正确顺序应该是:先开一个已有会话别关;再开第二个新会话强制只用 publickey 登录;确认新会话能进去;确认有第二个管理员入口;最后再改 sshd_config。
第五类是“Windows Hello 或 Touch ID 能不能保护 SSH key”。答案是:能保护一部分体验,但不能简单等同。Windows 的 ssh-agent 可以缓存 key;macOS 可以把 SSH key passphrase 放进钥匙串;一些硬件安全钥匙可以生成 ed25519-sk 或 ecdsa-sk SSH key。可是平台 Passkey 的网站凭据和 SSH 私钥仍然是两套东西。
第六类是“我想让 Agent 帮我配置免密登录,但又怕它把内网信息写进文章或日志”。这确实要小心。Agent 可以执行脚本、生成 key、拷贝公钥、验证登录,但提示词里要明确禁止输出真实内网地址、完整主机名、私有域名和密钥内容。
三、问题分析:两者共同点是密码学,不同点是使用场景
SSH key 和 Passkey 的共同点,是都不应该把“真正的秘密”发给对方。服务器或网站发来一个挑战,客户端用私钥签名,对方用公钥验证。就像学校门卫不需要拿走你的身份证原件,只需要看你能不能拿出有效证件并完成验证。
差异在于锁不同。SSH 的锁在服务器的 sshd 上,钥匙孔叫 authorized_keys;Passkey 的锁在网站或 App 的账号系统上,钥匙孔通常是 WebAuthn/FIDO2。你拿小区门禁卡去开自行车锁,刷卡动作再高级也打不开,因为锁芯不一样。
图 3:SSH 免密登录不是“服务器记住了你”,而是服务器能验证你持有对应私钥。
OpenSSH 从 8.2 起加入了 FIDO/U2F 硬件认证器支持,新增了 ecdsa-sk 和 ed25519-sk 这类 security key backed key。这里的 -sk 很关键:它指的是 SSH 协议里由硬件安全钥匙支持的 SSH key,而不是浏览器网站里那条 passkey。两者可以使用同一类硬件能力,但凭据不通用。

图 4:真实截图。OpenSSH 8.2 的 FIDO/U2F 支持让硬件安全钥匙可以用于 SSH key,但这仍然是 SSH key。
Passkey 这一边,FIDO Alliance 把它定义为基于 FIDO 标准的认证凭据,用户用解锁设备的方式批准登录。Apple Support 也明确把 passkeys 描述为密码的替代品;Microsoft Learn 则把 Windows passkeys 放在 Windows 账号身份保护体系中说明。这些说法的共同核心是:Passkey 是账号登录凭据,不是一个你复制粘贴到服务器文件里的字符串。
图 5:Passkey 登录像“盖章证明我是我”,网站验证印章结果,但拿不到印章本体。
四、问题根因:把“无密码”理解成“没有秘密”
很多事故的根因,是把“无密码”理解成“没有秘密”。实际上,无密码登录不是没有秘密,而是秘密换了保存方式。
传统密码的秘密在人脑里,最容易被钓鱼、复用、撞库、截图、复制。SSH key 的秘密在私钥文件或硬件安全钥匙里,风险变成了私钥文件被偷、没有 passphrase、到处复制、服务器公钥管理混乱。Passkey 的秘密在设备安全模块、系统密码管理器或硬件安全钥匙里,风险变成了设备账号恢复、同步范围、备用认证器不足、账号恢复流程太弱。
可以用家里钥匙来打比方。密码像你背下来的门锁暗号,别人骗到暗号就能进门。SSH 私钥像一把实体钥匙,服务器只登记钥匙齿形,不拿钥匙本体。Passkey 像小区门禁加指纹确认,门禁系统只验证你能不能完成这次开门动作,不要求你说出暗号。它们都比“口头暗号”强,但钥匙丢了、门禁卡只有一张、备用入口没有设置,一样会出事。
五、如何解决:先把 SSH 做对,再把 Passkey 用对
5.1 SSH 免密登录的推荐规则
我的建议很保守。
第一,每台客户端设备使用独立 SSH key,不要把一份私钥复制到所有电脑。第二,私钥必须设置 passphrase,除非它在硬件安全钥匙里并且有明确触摸或 PIN 保护。第三,服务器只保存 .pub 公钥,不保存私钥。第四,公钥注释不要写完整主机名、内网地址或私人域名,可以写成 work-laptop-2026 这类低敏标签。第五,关闭服务器密码登录前,必须用第二个会话验证 publickey-only 登录。
常见服务器侧检查如下,注意这里全部是占位符,不包含真实地址:
ssh -o PreferredAuthentications=publickey -o PasswordAuthentication=no <ssh-target> 'echo publickey-ok'
如果这条命令成功,再考虑在服务器上检查:
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
改完后先执行 sshd -t,确认配置语法正确,再重启 SSH 服务。不要在唯一会话里直接断开。
5.2 Passkey 的推荐规则
Passkey 主要用于网站和 App。个人账号可以优先使用系统同步型 passkey:iPhone 和 Mac 走 iCloud 钥匙串,Android 走系统密码管理器,Windows 11 走 Windows Hello / 系统 passkey 管理。高价值账号建议至少注册两个认证器,例如手机加硬件安全钥匙,或者电脑平台认证器加手机。
不要只创建一个 passkey 就删除所有其他恢复方式。Passkey 很安全,但账号恢复流程如果很弱,攻击者可能绕过 passkey;如果恢复方式太少,你自己换手机、重装系统、离职交接时也可能进不去。正确做法是:开启 passkey,保留安全的恢复邮箱或恢复码,删除弱密码复用,开启登录通知,并定期检查账号里登记了哪些设备。

图 6:真实截图。Apple 把 passkey 定位为密码替代品,但它仍然属于网站和 App 登录体系。

图 7:真实截图。Windows 侧的 passkey 与 Windows Hello、设备解锁和账号登录体验绑定。

图 8:真实截图。ssh-keygen 手册里能看到 ed25519-sk 这类 SSH security key 类型,它和网站 Passkey 不是同一份凭据。
六、人工自动执行:三套一键脚本
下面三套脚本只配置 SSH 免密登录,不会替你创建网站 Passkey。Passkey 必须在对应网站、App 或系统设置里完成注册,因为那一步需要账号侧参与。脚本不依赖第三方服务,只调用系统 OpenSSH 组件和本机能力。
6.1 Windows 11:PowerShell 脚本
保存为 Setup-SshPasswordless-Windows11.ps1。先只生成 key:
powershell -ExecutionPolicy Bypass -File .\Setup-SshPasswordless-Windows11.ps1
如果要把公钥安装到远端类 Unix 服务器,使用占位形式替换目标:
powershell -ExecutionPolicy Bypass -File .\Setup-SshPasswordless-Windows11.ps1 -InstallPublicKey -Target "<ssh-target>"
脚本如下:
param(
[string]$KeyName = "id_ed25519_login",
[string]$Target = "",
[switch]$InstallPublicKey,
[switch]$UseSecurityKey
)
$ErrorActionPreference = "Stop"
$ssh = Get-Command ssh -ErrorAction SilentlyContinue
$sshKeygen = Get-Command ssh-keygen -ErrorAction SilentlyContinue
if (-not $ssh -or -not $sshKeygen) {
throw "OpenSSH Client is not available. Enable the Windows OpenSSH Client optional feature first."
}
$sshDir = Join-Path $HOME ".ssh"
New-Item -ItemType Directory -Force -Path $sshDir | Out-Null
$keyPath = Join-Path $sshDir $KeyName
$pubPath = "$keyPath.pub"
$keyType = $(if ($UseSecurityKey) { "ed25519-sk" } else { "ed25519" })
if (-not (Test-Path $keyPath)) {
Write-Host "Generating $keyType key at $keyPath"
& ssh-keygen -t $keyType -a 100 -f $keyPath -C "ssh-login-key"
} else {
Write-Host "Key already exists: $keyPath"
}
try {
Set-Service ssh-agent -StartupType Automatic
Start-Service ssh-agent
& ssh-add $keyPath
} catch {
Write-Warning "Could not add key to ssh-agent automatically: $($_.Exception.Message)"
}
Write-Host "`nPublic key:"
Get-Content $pubPath
if ($InstallPublicKey) {
if ([string]::IsNullOrWhiteSpace($Target)) {
throw "Pass -Target '<ssh-target>' when using -InstallPublicKey."
}
Write-Host "`nInstalling public key to $Target"
Get-Content $pubPath | ssh $Target 'umask 077; mkdir -p ~/.ssh; touch ~/.ssh/authorized_keys; cat >> ~/.ssh/authorized_keys; sort -u ~/.ssh/authorized_keys -o ~/.ssh/authorized_keys; chmod 700 ~/.ssh; chmod 600 ~/.ssh/authorized_keys'
Write-Host "`nTesting publickey-only login"
ssh -o PreferredAuthentications=publickey -o PasswordAuthentication=no $Target 'echo publickey-ok'
}
Write-Host "`nDone. For website and app passkeys, use Windows Settings > Accounts > Passkeys or the target website security page."
6.2 Ubuntu 26.04:Bash 脚本
保存为 setup-ssh-passwordless-ubuntu2604.sh。先生成 key:
bash setup-ssh-passwordless-ubuntu2604.sh
安装公钥并验证:
INSTALL_PUBLIC_KEY=1 REMOTE="<ssh-target>" bash setup-ssh-passwordless-ubuntu2604.sh
如果你插入了支持 OpenSSH 的 FIDO2 硬件安全钥匙,可以尝试:
USE_SECURITY_KEY=1 bash setup-ssh-passwordless-ubuntu2604.sh
脚本如下:
#!/usr/bin/env bash
set -euo pipefail
KEY_NAME="${KEY_NAME:-id_ed25519_login}"
REMOTE="${REMOTE:-}"
INSTALL_PUBLIC_KEY="${INSTALL_PUBLIC_KEY:-0}"
USE_SECURITY_KEY="${USE_SECURITY_KEY:-0}"
SSH_DIR="$HOME/.ssh"
KEY_PATH="$SSH_DIR/$KEY_NAME"
PUB_PATH="$KEY_PATH.pub"
KEY_TYPE="ed25519"
if [ "$USE_SECURITY_KEY" = "1" ]; then
KEY_TYPE="ed25519-sk"
fi
command -v ssh >/dev/null || { echo "ssh is missing"; exit 1; }
command -v ssh-keygen >/dev/null || { echo "ssh-keygen is missing"; exit 1; }
mkdir -p "$SSH_DIR"
chmod 700 "$SSH_DIR"
if [ ! -f "$KEY_PATH" ]; then
echo "Generating $KEY_TYPE key at $KEY_PATH"
ssh-keygen -t "$KEY_TYPE" -a 100 -f "$KEY_PATH" -C "ssh-login-key"
else
echo "Key already exists: $KEY_PATH"
fi
chmod 600 "$KEY_PATH"
chmod 644 "$PUB_PATH"
if command -v ssh-agent >/dev/null && command -v ssh-add >/dev/null; then
if [ -z "${SSH_AUTH_SOCK:-}" ]; then
eval "$(ssh-agent -s)" >/dev/null
fi
ssh-add "$KEY_PATH" || true
fi
echo
echo "Public key:"
cat "$PUB_PATH"
if [ "$INSTALL_PUBLIC_KEY" = "1" ]; then
[ -n "$REMOTE" ] || { echo "Set REMOTE='<ssh-target>' first."; exit 1; }
echo
echo "Installing public key to $REMOTE"
if command -v ssh-copy-id >/dev/null; then
ssh-copy-id -i "$PUB_PATH" "$REMOTE"
else
cat "$PUB_PATH" | ssh "$REMOTE" 'umask 077; mkdir -p ~/.ssh; touch ~/.ssh/authorized_keys; cat >> ~/.ssh/authorized_keys; sort -u ~/.ssh/authorized_keys -o ~/.ssh/authorized_keys; chmod 700 ~/.ssh; chmod 600 ~/.ssh/authorized_keys'
fi
echo
echo "Testing publickey-only login"
ssh -o PreferredAuthentications=publickey -o PasswordAuthentication=no "$REMOTE" 'echo publickey-ok'
fi
echo
echo "Done. Create website/app passkeys from each account security page; do not copy passkeys into SSH files."
6.3 macOS 26:zsh 脚本
保存为 setup-ssh-passwordless-macos26.zsh。先生成 key:
zsh setup-ssh-passwordless-macos26.zsh
安装公钥并验证:
INSTALL_PUBLIC_KEY=1 REMOTE="<ssh-target>" zsh setup-ssh-passwordless-macos26.zsh
脚本如下:
#!/usr/bin/env zsh
set -euo pipefail
KEY_NAME="${KEY_NAME:-id_ed25519_login}"
REMOTE="${REMOTE:-}"
INSTALL_PUBLIC_KEY="${INSTALL_PUBLIC_KEY:-0}"
USE_SECURITY_KEY="${USE_SECURITY_KEY:-0}"
SSH_DIR="$HOME/.ssh"
KEY_PATH="$SSH_DIR/$KEY_NAME"
PUB_PATH="$KEY_PATH.pub"
CONFIG_PATH="$SSH_DIR/config"
KEY_TYPE="ed25519"
if [[ "$USE_SECURITY_KEY" == "1" ]]; then
KEY_TYPE="ed25519-sk"
fi
command -v ssh >/dev/null || { echo "ssh is missing"; exit 1; }
command -v ssh-keygen >/dev/null || { echo "ssh-keygen is missing"; exit 1; }
mkdir -p "$SSH_DIR"
chmod 700 "$SSH_DIR"
if [[ ! -f "$KEY_PATH" ]]; then
echo "Generating $KEY_TYPE key at $KEY_PATH"
ssh-keygen -t "$KEY_TYPE" -a 100 -f "$KEY_PATH" -C "ssh-login-key"
else
echo "Key already exists: $KEY_PATH"
fi
chmod 600 "$KEY_PATH"
chmod 644 "$PUB_PATH"
touch "$CONFIG_PATH"
chmod 600 "$CONFIG_PATH"
if ! grep -q "UseKeychain yes" "$CONFIG_PATH"; then
cat >> "$CONFIG_PATH" <<'EOF'
Host *
AddKeysToAgent yes
UseKeychain yes
EOF
fi
if command -v ssh-add >/dev/null; then
ssh-add --apple-use-keychain "$KEY_PATH" 2>/dev/null || ssh-add -K "$KEY_PATH" 2>/dev/null || ssh-add "$KEY_PATH" || true
fi
echo
echo "Public key:"
cat "$PUB_PATH"
if [[ "$INSTALL_PUBLIC_KEY" == "1" ]]; then
[[ -n "$REMOTE" ]] || { echo "Set REMOTE='<ssh-target>' first."; exit 1; }
echo
echo "Installing public key to $REMOTE"
cat "$PUB_PATH" | ssh "$REMOTE" 'umask 077; mkdir -p ~/.ssh; touch ~/.ssh/authorized_keys; cat >> ~/.ssh/authorized_keys; sort -u ~/.ssh/authorized_keys -o ~/.ssh/authorized_keys; chmod 700 ~/.ssh; chmod 600 ~/.ssh/authorized_keys'
echo
echo "Testing publickey-only login"
ssh -o PreferredAuthentications=publickey -o PasswordAuthentication=no "$REMOTE" 'echo publickey-ok'
fi
echo
echo "Done. macOS passkeys are managed from Passwords and account security pages, not from ~/.ssh."
七、Agent 自动配置方法
如果你想让 Codex、Claude、OpenClaw、HermesAgent 这类 Agent 自动配置,不要只说“帮我配置 SSH 免密”。建议直接给下面这段提示词,并把真实目标只在会话里提供,不写进文章、脚本或公开仓库:
你是本机运维 Agent,请为当前这台客户端配置 SSH 免密登录。
要求:
1. 先识别系统是 Windows 11、Ubuntu 26.04 还是 macOS 26。
2. 只使用系统自带 OpenSSH 能力,不安装第三方云服务或未知工具。
3. 为当前设备生成独立 ed25519 SSH key;如果我明确要求并插入硬件安全钥匙,才尝试 ed25519-sk。
4. 私钥必须设置 passphrase;不要把私钥复制到任何远端。
5. 服务器端只追加公钥到 authorized_keys,不覆盖已有内容。
6. 全程不要输出真实内网 IP、完整计算机名、私有域名、私钥、密钥指纹以外的敏感内容。
7. 安装后用 PreferredAuthentications=publickey 和 PasswordAuthentication=no 做验证。
8. 只有在我明确确认后,才建议关闭服务器密码登录。
这段提示词的重点是“先验收再收口”。Agent 很适合干重复活,但你要把禁止动作写清楚:不复制私钥、不覆盖 authorized_keys、不暴露目标地址、不自动关闭密码登录。
八、Q&A
Q1:我能不能直接用 iPhone 或 Android 的 Passkey 登录 SSH?
一般不能。手机 passkey 面向网站和 App 的 WebAuthn/FIDO2 登录。标准 OpenSSH 登录需要 SSH key。你可以使用支持 OpenSSH 的硬件安全钥匙生成 ed25519-sk,但这不是把手机网站 passkey 直接拿来用。
Q2:那 Touch ID、Face ID、Windows Hello 对 SSH 完全没用吗?
不是。它们可以帮助你解锁系统钥匙串、保护系统账号、使用 passkey 登录网站,也可能间接改善 SSH key 的 passphrase 管理体验。但 SSH 认证本身仍然看客户端是否能用对应私钥完成签名。
Q3:SSH key 要不要设置 passphrase?
建议设置。没有 passphrase 的私钥一旦被复制走,就像门钥匙掉在路上还贴了门牌号。自动化任务如果必须无交互,应该尽量使用权限很小的专用账号、受限命令、短生命周期证书或硬件保护,而不是把高权限裸私钥到处放。
Q4:一台服务器可以放多条公钥吗?
可以,而且推荐这样做。每台客户端一条公钥,离职、换机、丢设备时只删对应一行。不要多人共用一把私钥。
Q5:我什么时候可以关闭服务器密码登录?
至少满足四个条件:已有免密登录成功;用新终端验证 publickey-only 成功;还有第二个管理员或控制台入口;你知道如何回滚。否则先别急。
Q6:Passkey 会不会因为换手机就丢?
取决于保存方式。同步型 passkey 通常跟随平台账号和系统密码管理器;设备绑定型或硬件安全钥匙型不会自动出现在新设备上。高价值账号建议注册至少两个认证器,并保存恢复码。
Q7:文章里为什么不用真实 IP 或真实主机名演示?
因为免密登录教程最容易把内网地址、跳板机名、账号名、密钥注释和私有域名一起泄露出去。公开文章应该使用 <ssh-target> 这类占位符,真实信息只在私有终端里出现。
九、最后的建议
如果只记一件事,就记住这句话:SSH key 管服务器入口,Passkey 管账号入口。
对开发者和运维来说,SSH 免密登录的安全底线是:私钥不外传、公钥可撤销、每台设备独立、关闭密码前先验证。对普通账号来说,Passkey 的安全底线是:用系统或硬件保存、注册备用认证器、保留可靠恢复方式、定期清理不认识的设备。
无密码不是没有钥匙,而是把钥匙从“人脑背密码”升级为“设备安全保存 + 本地解锁 + 公钥验证”。这件事一旦理解清楚,SSH 免密和手机/电脑通行密钥就不会互相打架,反而会各司其职:服务器登录更稳,网站账号更抗钓鱼,日常使用也更少输入密码。
参考资料
- OpenSSH 8.2 release notes: https://www.openssh.com/txt/release-8.2
- OpenBSD
ssh-keygen(1)manual: https://man.openbsd.org/ssh-keygen - FIDO Alliance Passkeys: https://fidoalliance.org/passkeys/
- Apple Support, About the security of passkeys: https://support.apple.com/en-us/102195
- Microsoft Learn, Support for Passkeys in Windows: https://learn.microsoft.com/en-us/windows/security/identity-protection/passkeys/
- Google Developers, Passkeys: https://developers.google.com/identity/passkeys