SSH keys (简体中文)
对于使用公钥加密与质询-应答式认证的 SSH 服务器,SSH 密钥可以为您提供证明身份的方法。基于密钥的认证主要优点在于,与密码认证相比,它不易遭受暴力破解攻击,且在服务器被攻破的情况下也不会泄露您的有效凭证。[1]
不仅如此,与传统的密码认证相比,SSH 密钥认证也可以更加便利。当与被称作 SSH agent 的程序共用时,SSH 密钥可以让您无需记住或输入每个系统的密码,就能够连接到一个或多个服务器。
基于密钥的认证并非没有缺点,可能也并非适用于一切环境,但在很多情况下可以提供一些有力的优势。对 SSH 密钥如何工作的概略理解会帮助您决定如何以及何时使用它们,以满足您的需求。
本文假定您已经对于 SSH 有了基本理解,并安装了 openssh 软件包。
背景
SSH 密钥都是成对生成的,其一称为公钥,另一则称为私钥。私钥只由您所知,必须安全保管。相对地,公钥可以向您想连接的任何 SSH 服务器自由地共享。
如果一个 SSH 服务器在文件中存有您的公钥,并收到了您的连接请求,就会使用您的公钥构建一个质询问题并发送给您。这一质询问题是一条加密信息,必须得到正确应答,服务器才能允许您访问。这条编码信息特别安全是在于,只有私钥持有者才能理解它。公钥可以用来加密信息,但不能用来解密同一条信息。只有您,私钥的持有者,能够正确理解这一质询问题并产生合适的应答。
这一质询-应答过程发生在后台,对用户不可见。只要您持有私钥(一般存放在 ~/.ssh/
目录下),您的 SSH 客户端就应当能够向服务器回复正确的应答。
私钥是受保护的秘密,因此建议以加密形式将其存储在磁盘上。当需要被加密的私钥时,为了解密它,需要先输入密码。虽然这在表面上可能像是您在向 SSH 服务器提供登录密码,但该密码只用于解密本地系统上的私钥。该密码并未通过网络传输。
生成密钥对
通过运行 ssh-keygen
命令可以生成密钥对,默认为3072位的 RSA(以及 SHA256),ssh-keygen(1) 手册页称其“一般被认为充足”且应当兼容于几乎所有客户端和服务器:
$ ssh-keygen
Generating public/private rsa key pair. Enter file in which to save the key (/home/<username>/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/<username>/.ssh/id_rsa. Your public key has been saved in /home/<username>/.ssh/id_rsa.pub. The key fingerprint is: SHA256:gGJtSsV8BM+7w018d39Ji57F8iO6c0N2GZq3/RY2NhI username@hostname The key's randomart image is: +---[RSA 3072]----+ | ooo. | | oo+. | | + +.+ | | o + + E . | | . . S . . =.o| | . + . . B+@o| | + . oo*=O| | . ..+=o+| | o=ooo+| +----[SHA256]-----+
randomart 图像作为一种简便的密钥指纹视觉辨识方式在 OpenSSH 5.1 中引入。
-a
开关来指定密码加密的 KDF rounds 数量。也可以用 -C
开关对公钥添加可选的注释栏,从而在 ~/.ssh/known_hosts
、~/.ssh/authorized_keys
以及 ssh-add -L
输出等处更轻松地辨识它。例如:
$ ssh-keygen -C "$(whoami)@$(uname -n)-$(date -I)"
会添加一条注释,说明是哪个用户何时在哪台机器上创建的密钥。
选择认证密钥种类
OpenSSH(对于认证密钥)支持数种签名算法,按照所采用的数学性质可分为两类:
椭圆曲线密码学(ECC)算法是对于公钥密码体制的较新发展。其主要优势之一是以较小密钥提供同等安全性的能力,使得计算密集操作更少(也就是密钥创建、加密及解密更快)且存储及传输的要求更低。
由于已发现的漏洞,OpenSSH 7.0废弃并禁用了对 DSA 密钥的支持,因此密码体制的选择范围就限定在了 RSA 或两种 ECC 之一当中。
#RSA 密钥会提供最大的可移植性,而 #Ed25519 会提供最佳的安全性,但需要新版本的客户端与服务器[2]。. #ECDSA 兼容性可能比 Ed25519 更好(虽然还是不如 RSA),但存在对于其安全性的怀疑(见下文)。
RSA
ssh-keygen
默认使用 RSA,因此无需使用 -t
选项指定它。它提供所有算法当中最佳的兼容性,但提供充足安全性所需的密钥尺寸较大。
密钥尺寸最小为1024位,默认为3072(见 ssh-keygen(1)),最大为 16384。
如果您想生成较强的 RSA 密钥对(例如为了防范先进或未知攻击以及较高级的攻击者),只需为 -b
选项指定比默认高一点的值:
$ ssh-keygen -b 4096
需要明白的是,使用更长的密钥存在收益递减。[3][4] GnuPG 常见问答记载:“如果你需要的安全性比 RSA-2048 所提供的更强,应该做的是改用椭圆曲线密码学——而不是接着使用 RSA。”[5]
另一方面,最新版本的 NSA Fact Sheet Suite B Cryptography 建议对于 RSA 使用最少3072位的模,同时“为即将到来的抗量子算法迁移(做准备)”。[6]
ECDSA
椭圆曲线数字签名算法(ECDSA)在 OpenSSH 5.7中作为首选的认证算法被引入。一些提供商也由于潜在的专利问题禁用了所需的实现。
对于它的担忧有两种:
- 政治上的担忧:在 NSA(美国国家安全局)被曝在软件、硬件组件及发布的标准中蓄意植入后门之后,由 NIST(美国国家标准技术研究所)产生的曲线的可信性受到了质疑;密码领域的知名人士对于 NIST 曲线的设计方式表示 了 怀疑,并且之前已经有主动的污染被 证实。
- 技术上的担忧:对于正确实现该标准的困难以及在实现不够谨慎情况下降低安全性的迟缓与设计缺陷。
libssh curve25519 介绍当中很好地总结了上述担忧。尽管政治上的担忧仍受到争议,但有明确的共识认为 #Ed25519 技术上更优越,因此应当优先采用。
Ed25519
Ed25519 在2014年1月被引入 OpenSSH 6.5:“Ed25519 是一种能够提供比 ECDSA 及 DSA 更佳安全性及良好性能的椭圆曲线签名体系”。其主要长处在于速度、时间恒定运行时(从而能够对抗侧信道攻击)以及无需晦暗不明的硬编码常数。[7] 关于其工作方式,参见由一位 Mozilla 开发者撰写的博客文章。
它已经在众多应用及库中得到实现,并且在 OpenSSH 中是默认的密钥交换算法(与密钥签名不同)。
Ed25519 密钥对生成方法如下:
$ ssh-keygen -t ed25519
所有 Ed25519 密钥都是256位,故无需设置密钥尺寸。
注意较老的 SSH 客户端与服务器可能不支持这些密钥。
选择密钥存储位置以及密码短语
运行 ssh-keygen
时,它会询问您希望的私钥文件名称及位置。默认情况下,密钥保存到 ~/.ssh
目录下,并根据所使用的加密类型命名。为使下文中的示例代码正确工作,建议您接受默认的名称和位置。
当系统向您询问密码短语时,如果您在乎私钥的安全性,请选择难以猜到的密码。更长、更随机的密码一般会更强,当私钥落入贼人之手时更不容易被破解掉。
在没有密码短语的情况下生成私钥也是可能的。虽然也许很方便,但您需要明白随之而来的风险。在没有密码短语的情况下,您的私钥会以未加密形式存储在硬盘上。任何能接触到您私钥文件的人之后都能够在您使用基于密钥认证连接的任何 SSH 服务器面前冒用您的身份。更进一步,没有密码短语,您也必须信任 root 用户,因为他可以绕过文件权限并能够随时访问您未加密的私钥文件。
不修改密钥对的情况下修改密码短语
如果不希望使用原本选择的 SSH 密钥密码短语或者必须更换,可以使用 ssh-keygen
命令来修改密码短语,而无需改动实际密钥。此法也可用于将密码编码格式改为新标准。
$ ssh-keygen -f ~/.ssh/id_rsa -p
管理多组密钥对
对多台主机使用同一 SSH 密钥对是可能的,尽管受到争议[8] [9]。
另一方面,使用您 OpenSSH 配置文件中的 IdentityFile
指令,为多台主机管理不同的密钥对就比较容易了:
~/.ssh/config
Host SERVER1 IdentitiesOnly yes IdentityFile ~/.ssh/id_rsa_SERVER1 Host SERVER2 IdentitiesOnly yes IdentityFile ~/.ssh/id_ed25519_SERVER2
对于这些选项的完整描述见 ssh_config(5)。
在硬件令牌上存储 SSH 密钥
SSH 密钥也可以存储在智能卡或 USB 令牌等安全令牌上。这样做的好处是私钥安全地存储在令牌上,而不是存储在磁盘中。当使用安全令牌时,敏感的私钥也从来不会存在于 PC 的内存当中;密码学操作在令牌自身上进行。密码学令牌还有一点好处是,它不与单台电脑绑定;可以将其从一台电脑上轻松取下,四处携带之后在其他电脑上使用。
硬件令牌的例子如下所述:
- YubiKey#SSH notes 用于 FIDO/U2F keys 的原生 OpenSSH 支持
- YubiKey#SSH keys
- Trusted Platform Module#Securing SSH keys
将公钥复制到远程服务器上
创建好密钥对之后,您需要将公钥上传到远程服务器上,以便用于 SSH 密钥认证登录。公钥文件名和私钥文件名相同,只不过公钥文件带有扩展名 .pub
而私钥文件名则没有。千万不要将私钥上传,私钥应该保存在本地。
$ cat ~/.ssh/id_ecdsa.pub >> ~/.ssh/authorized_keys
简易方式
如果您的公钥文件为 ~/.ssh/id_rsa.pub
,您只需要输入命令
$ ssh-copy-id remote-server.org
如果您的远程服务器用户名与本地的不同,您需要指明用户名
$ ssh-copy-id [email protected]
如果您的公钥文件名不是默认的 ~/.ssh/id_rsa.pub
,您会得到错误 /usr/bin/ssh-copy-id: ERROR: No identities found
。这种情况下,您需要显式提供公钥的位置:
$ ssh-copy-id -i ~/.ssh/id_ecdsa.pub [email protected]
如果远程服务器监听端口不是默认的22,请在主机参数中指明:
$ ssh-copy-id -i ~/.ssh/id_ecdsa.pub -p 221 [email protected]
手动方式
对于 OpenSSH,默认需要将公钥添加到 ~/.ssh/authorized_keys
。先从复制公钥到远程服务器开始。
$ scp ~/.ssh/id_ecdsa.pub [email protected]:
以上示例通过 scp
将公钥(id_ecdsa.pub
)复制到您在远程服务器上的主目录。别忘了加上服务器地址末尾的 :
。并请注意,您的公钥名称可能与此例所示不同。
在远程服务器上,如果还没有 ~/.ssh
目录,您需要创建一个;然后将您的公钥追加到 authorized_keys
。
$ ssh [email protected] [email protected]'s password: $ mkdir ~/.ssh $ chmod 700 ~/.ssh $ cat ~/id_ecdsa.pub >> ~/.ssh/authorized_keys $ rm ~/id_ecdsa.pub $ chmod 600 ~/.ssh/authorized_keys
上面最后两个命令将公钥文件从服务器上删除,并设置 authorized_keys
文件的权限,这样只有作为文件拥有者的您能够读取及写入。
SSH agents
如果您的私钥使用密码短语来加密了的话,每一次试图连接到使用公钥认证的 SSH 服务器的时候,您都必须输入密码短语。每次唤起 ssh
或 scp
时都需要密码短语来解密您的私钥以便认证能够继续。
而 SSH agent 程序能够将您的已解密的私钥缓存起来,并代表您将其提供给 SSH 客户端。这样子,您就只需要将私钥加入 SSH agent 缓存的时候输入一次密码短语就可以了。这为您经常使用 SSH 连接提供了不少便利。
SSH agent 一般会设置成在登录会话的时候自动启动,并在整个会话中保持运行。有多种 agent、前端及配置都能够达成这一效果。本节概述了多种不同的解决方案,能够适应以满足您的特定需求。
ssh-agent
ssh-agent 是 OpenSSH 自带的默认 agent。它可以直接使用,或者作为下文所述的几种前端解决方案的后端。ssh-agent
运行时会自动 fork 到后台,然后打印出其所需的环境变量。如
$ ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-vEGjCM2147/agent.2147; export SSH_AUTH_SOCK; SSH_AGENT_PID=2148; export SSH_AGENT_PID; echo Agent pid 2148;
要使用这些环境变量,请使用 eval
命令来运行它。
$ eval $(ssh-agent)
Agent pid 2157
ssh-agent
运行起来之后,您还需要将您的私钥加入它的缓存。
$ ssh-add ~/.ssh/id_ed25519
Enter passphrase for /home/user/.ssh/id_ed25519: Identity added: /home/user/.ssh/id_ed25519 (/home/user/.ssh/id_ed25519)
如果您的私钥已加密,ssh-add
会提示您输入密码短语。一旦您的私钥被成功添加到 agent,您不需要再输入密码短语就能够建立 SSH 连接了。
git
在内的所有 ssh
客户端在第一次使用时就把密钥存储在 agent 中,请将配置 AddKeysToAgent yes
添加到 ~/.ssh/config
。其他可能的值有 confirm
、ask
和 no
(默认)。为了自动启动 agent 并确保同一时间只有一个 ssh-agent
进程在运行,添加以下内容到您的 ~/.bashrc
:
if ! pgrep -u "$USER" ssh-agent > /dev/null; then ssh-agent -t 1h > "$XDG_RUNTIME_DIR/ssh-agent.env" fi if [[ ! "$SSH_AUTH_SOCK" ]]; then source "$XDG_RUNTIME_DIR/ssh-agent.env" >/dev/null fi
如果还没有 ssh-agent
进程在运行的话,该脚本会自动启动一个,并保存其输出。如果已经有一个在运行了,就会读取缓存的 ssh-agent
输出并执行之,从而设置必要的环境变量。解密密钥的存活时间设置为1小时。
本节后面会介绍几种 ssh-agent
的前端及其他 agent,也能够避免这一问题。
用 systemd user 启动 ssh-agent
通过 Systemd (简体中文)/User (简体中文) 机制来启动 agent 同样可行。如果想要在登录时运行 SSH agent 而不论 X 是否在运行的话,可用此法。
~/.config/systemd/user/ssh-agent.service
[Unit] Description=SSH key agent [Service] Type=simple Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket # DISPLAY required for ssh-askpass to work Environment=DISPLAY=:0 ExecStart=/usr/bin/ssh-agent -D -a $SSH_AUTH_SOCK [Install] WantedBy=default.target
然后在您的登录 shell 初始化文件(如 ~/.bash_profile
)中 export 环境变量 SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent.socket"
。
最后,加上 --user
标志 enable 或 start 服务。
ssh-agent 作为包装程序运行
加州大学伯克利分校实验室的这份 ssh-agent 教程还介绍了一种随每个 X 会话启动 ssh-agent
的方法。基本用法是,如果您使用命令 startx
正常启动 X,您可以改成像这样在前面加上 ssh-agent
:
$ ssh-agent startx
您也可以在 .bash_aliases
或等同文件中设置别名,就不用再费心了:
alias startx='ssh-agent startx'
这样做可以避免多个会话中存在多余的 ssh-agent
进程。整个 X 会话中只会有一个 ssh-agent
在运行。
ssh-agent startx
的替代方案,可以将 eval $(ssh-agent)
添加到 ~/.xinitrc
。见下文有关协同使用 x11-ssh-askpass 与 ssh-add 的说明了解如何立即将密钥添加到 agent。
GnuPG Agent
gpg-agent 提供对 OpenSSH agent 的模拟。必要配置见 GnuPG (简体中文)#SSH agent。
Keychain
Keychain 是一个用来方便管理 SSH 密钥对的程序,它能尽最大努力去减少对用户的打扰。实际上,它就是一个 shell 脚本,驱动 ssh-agent
或者 gpg-add
来工作。一个值得注意的特性是,keychain 在多个会话中重复使用同一个 ssh-agent
进程。这意味着您只需要在机器启动时输入一次密码短语即可。
安装
配置
-Q, --quick
选项存在预期之外的副作用,导致 keychain 在重新登陆时切换到新产生的 ssh-agent (至少在使用 GNOME 的系统上如此),使您不得不重新添加所有先前注册的密钥。将类似以下的行加入到您 shell 的 配置文件中,例如若您使用Bash:
~/.bashrc
eval $(keychain --eval --quiet id_ed25519 id_rsa ~/.keys/my_custom_key)
~/.bashrc
而非上游建议的 ~/.bash_profile
,是因为在 Arch 上登录与非登录 shell 都会使用它,因此同样适用于文字与图形环境。关于二者之间差异的更多信息见Bash (简体中文)#调用。上例当中,
-
--eval
开关可以输出供eval
命令执行的行;这样可以设置必要的环境变量,让 SSH 客户端能够找到您的 agent。 -
--quiet
会把输出限制为只包含警告、错误及用户提示。
如例子所示,可在命令行中指定多个密钥。keychain 默认会在 ~/.ssh/
目录中寻找密钥对,但对于位于非标准位置的密钥可以使用绝对路径。也可使用 --confhost
选项告知 keychain 在 ~/.ssh/config
中寻找为特定主机定义的 IdentityFile
设置,并使用这些路径来定位密钥。
关于如何为其他 shell 设置keychain的详情见 keychain --help
或者 keychain(1)。
要测试 keychain,只需打开一个终端模拟器,或注销当前会话再重新登录。它应该会使用 $SSH_ASKPASS
中设置的程序或在终端上提示您输入指定私钥的密码短语(如果有)。
因为 keychain 重新使用之前的登录中的 ssh-agent 进程,所以您再次登录或打开新终端时应该就无需输入密码短语了。每当您重启机器时,您才会被要求再次输入密码短语。
提示
-
keychain 需要公钥文件与相应私钥存放在同一目录下,带有
.pub
扩展名。如私钥为符号链接,公钥与符号链接一起存放或与符号链接目标在同一目录都能找得到(该功能需要系统上有readlink
命令)。
- 要禁用图形提示,始终在终端中输入密码短语,请使用
--nogui
选项。举例来说,这样可以从密码管理器中复制粘贴长密码。
- 如果您想要等到需要时再提示解锁密钥,而不是一开始就提示,请使用
--noask
选项。
--agents
选项可以改变这一行为,例如 --agents ssh,gpg
。见 keychain(1)。envoy
Envoy 算是 keychain 的一个替代品,您可以从 官方软件仓库 下载安装 envoyAUR,或者从 AUR 安装 envoy-gitAUR。
安装完成之后,使用以下命令启用 envoy 套接字 (socket):
# systemctl enable [email protected]
加入您的外壳 rc 文件:
envoy -t ssh-agent -a ssh_key source <(envoy -p)
如果您的私钥为 ~/.ssh/id_rsa
, ~/.ssh/id_dsa
, ~/.ssh/id_ecdsa
,或者 ~/.ssh/identity
,则不需要 -a ssh_key
参数。
利用 KDE 电子钱包存储密码短语并和 envoy 配合工作
如果您的密码短语很长很复杂,那记忆起来是有点痛苦。您可以让 KDE 电子钱包来为您记住密码短语哦!
安装位于 官方软件仓库 的 ksshaskpass 和 kwalletmanager,然后按照上文介绍的方法启用 envoy 套接字。
添加一个脚本到 ~/.kde4/Autostart/
,输入以下内容:
~/.kde4/Autostart/ssh-agent.sh
#!/bin/sh envoy -t ssh-agent -a ssh_key
记得加上可执行权限:
$ chmod +x ~/.kde4/Autostart/ssh-agent.sh
添加一个脚本到 ~/.kde4/env/
,加入以下内容:
~/.kde4/env/ssh-agent.sh
#!/bin/sh eval $(envoy -p)
同样不要忘记加上可执行权限:
$ chmod +x ~/.kde4/env/ssh-agent.sh
当您登录到 KDE 桌面环境时,它就会自动运行脚本 ssh-agent.sh
。这样子就能够调用 ksshaskpass
,当 envoy
调用 ssh-agent
时,ksshaskpass
就会请求您输入 KDE 电子钱包的密码了。而 KDE 电子钱包则会保存您的密码短语,在 ssh-agent
需要的时候由 KDE 电子钱包提供。
pam_ssh
pam_ssh 项目为 SSH 私钥提供了一个 插入式验证模块 (PAM)。当您的密码短语与系统登录用户密码相同的时候,可以为您减去再次输入密码的麻烦。开机登录时,您需要输入您的登录密码,如有需要,还要输入 ssh 密钥的密码短语。您成功登录系统之后,ssh-agent
则会在整个会话期间缓存您已解密的私钥。
要在 tty 模式下使用 pam_ssh,您需要安装位于 AUR 的 pam_sshAUR 。
~/.ssh/login-keys.d/
下。您可以为您的私钥文件创建一个软链接,并放到 ~/.ssh/login-keys.d/
:
$ mkdir ~/.ssh/login-keys.d/ $ cd ~/.ssh/login-keys.d/ $ ln -s ../id_rsa
注意将上述例子中的 id_rsa
换成您对应的私钥文件。
编辑 /etc/pam.d/login
,将下面例子中高亮加粗的那几行加进去。请注意配置内容的顺序会影响到登录行为,应当按照例子中的来。
/etc/pam.d/login
#%PAM-1.0 auth required pam_securetty.so auth requisite pam_nologin.so auth include system-local-login auth optional pam_ssh.so try_first_pass account include system-local-login session include system-local-login session optional pam_ssh.so
在上面的例子中,登录认证初始化进程如平常那样启动,用户要输入登录密码。try_first_pass
选项传递给 pam_ssh
模块,它会尝试使用用户密码作为密码短语去解密 ~/.ssh/login-keys.d/
下的私钥。如果用户密码与密码短语一致,那么私钥可以被解密,用户就无需再次输入相同的密码。如果两者不同,ssh_pam 模块会在用户正确输入登录密码之后输入密码短语。optional
可以保证 ~/.ssh/login-keys.d/
下没有私钥时,用户也能够正常登录系统。这种情况下,ssh_pam 模块对于没有私钥的用户来说就等于无。
如果您使用其他方式登录,比如使用登录管理器 SLiM (简体中文) 或者 XDM (简体中文),您必须按照类似的方法来编辑 PAM 配置文件,才能正常工作。/etc/pam.d/
目录下保存有默认的配置文件,您可以参考默认配置文件来进行配置。
pam_ssh 已知问题
pam_ssh 并不广泛使用,其提供的文档也比较少。您应该注意一下该软件包的一些使用限制,比如:
- pam_ssh < 2.0 不支持 ECDSA,您必须使用 RSA 或者 DSA。
- pam_ssh 调用的
ssh-agent
进程仅限于当前会话,不能在多个会话中共享。上文提到的 keychain 前端可以避免这个问题。
GNOME Keyring
如果您使用 Gnome,您也可以使用 Gnome 钥匙圈 (GNOME Keyring) 作为 SSH agent。详情请参考 GNOME Keyring。
疑难排解
如果您的 SSH 服务器忽略了您的 SSH 密钥对,您需要检查一下相关文件的权限是否正确。
本地机器上:
$ chmod 700 ~/ $ chmod 700 ~/.ssh $ chmod 600 ~/.ssh/id_ecdsa
服务器上:
$ chmod 700 ~/ $ chmod 700 ~/.ssh $ chmod 600 ~/.ssh/authorized_keys
如果这样还不能解决您的问题,您可以试试将 sshd_config
中的 StrictModes
设为 no
。如果将 StrictModes
关闭就能够顺利认证的话,说明相关文件的权限还没有改对。
StrictModes
设为 yes
。请确认您的 SSH 服务器支持您所使用的密钥类型, 可以实施 RSA 或者 DSA。某些服务器可能不支持 ECDSA 密钥。
如果还不行,打开 sshd 的 debug 模式,查看连接时的日志输出,查找原因吧:
# /usr/bin/sshd -d