SSH 密钥对可以方便的登录到 SSH 服务器,而无需输入密码. 由于无需发送密码到网络中,SSH 密钥对被认为是更加安全的方式. 再加上使用密码短语 (passphrase) 的使用,安全性会更上一层楼.
同时,我们可以使用 SSH agent 来帮助我们记住密码短语,无需我们记住每一个密钥对的密码短语,减轻了我们的负担.
本文将介绍如何管理密钥对,以方便的连接到 SSH 服务器. 本文默认已经熟知 SSH),并安装好位于官方软件仓库) 的 openssh.
1. 背景
SSH 密钥对总是成双出现的,一把公钥,一把私钥. 公钥可以自由的放在所需要连接的 SSH 服务器上,而私钥必须稳妥的保管好.
所谓"公钥登录",原理很简单,就是用户将自己的公钥储存在远程主机上. 登录的时候,远程主机会向用户发送一段随机字符串,用户用自己的私钥加密后,再发回来. 远程主机用事先储存的公钥进行解密,如果成功,就证明用户是可信的,直接允许登录 shell,不再要求密码. 这样子,我们即可保证了整个登录过程的安全,也不会受到中间人攻击.
2. 生成密钥对
可以使用 ssh-keygen
命令生成密钥对:
ssh-keygen -t ecdsa -b 521 -C "$(whoami)@$(hostname)-$(date -I)"
打印信息如:
Generating public/private ecdsa key pair.
Enter file in which to save the key (/home/username/.ssh/id_ecdsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/username/.ssh/id_ecdsa.
Your public key has been saved in /home/username/.ssh/id_ecdsa.pub.
The key fingerprint is:
dd:15:ee:24:20:14:11:01:b8:72:a2:0f:99:4c:79:7f username@localhost-2011-12-22
The key's randomart image is:
+--[ECDSA 521]---+
| ..oB=. . |
| . . . . . |
| . . . + |
| oo.o . . = |
|o+.+. S . . . |
|=. . E |
| o . |
| . |
| |
+-----------------+
该例中,ssh-keygen
生成了一对长度为 521 bit (-b 521
) 的 ECDSA (-t ecdsa
) 加密的密钥对,comment 为 -C "$(whoami)@$(hostname)-$(date -I)"
. 而 randomart image 是 OpenSSH 5.1 引入的一种简单的识别指纹 (fingerprint) 的图像.
2.1. 选择合适的加密方式
椭圆曲线数字签名算法 (ECDSA) 生成的密钥更小,安全性更高. OpenSSH 5.7 建议默认使用 ECDSA,详情参见 OpenSSH 5.7 Release Notes. 较旧的 OpenSSH 版本可能不支持 ECDSA 密钥,需要注意. 而一些厂商因专利问题,暂未提供 ECDSA 的实现.
注意: 截至 2014 年06 月 10 日,这个 GNOME bug 导致 Gnome Keyring 暂不支持 ECDSA.
如果要生成 RSA (768-16384 bit) 或者 DSA (1024 bit) 密钥对,需要使用 -t rsa
或者 -t dsa
,并修改 -b
选项. -b
可以省略,ssh-keygen
会生成一个默认大小的密钥对.
注意: 这些密钥对是用于认证的,选择更加复杂的密钥类型并不会在登录时加重您的 CPU 负担.
2.2. 选择密钥存储位置以及密码短语
输入 ssh-keygen
时,它会询问将密钥对保存到何处,文件名如何命令等. 默认情况下,密钥对保存到 ~/.ssh
下,文件名则根据加密类型自动命名为 id_ecdsa
(私钥),id_ecdsa.pub
(公钥). 建议采用默认的存储位置和文件名.
而在 ssh-keygen
请求输入一个密码短语时,应该输入一些难以猜到的短语. 如果短语足够随机和复杂,则私钥落入贼人之手时就不会容易被破解掉.
当然,也可以不输入任何密码短语,也能够生成所需的密钥对. 虽然这用起来挺方便的,但是这会很危险. 在没有输入密码短语的情况下,私钥未经加密就存储在硬盘上,任何人拿到私钥都可以随意的访问对应的 SSH 服务器. 还有一种情况,如果不是 root
用户,则该机器上的 root
用户可以完全拥有所生成密钥对,因为他的权限是最大的.
2.2.1. 不修改密钥对的情况下修改密码短语
可以使用 ssh-keygen
命令来修改密码短语,而无需改动密钥对. 假设要修改的密钥对使用 RSA 加密,输入以下命令即可:
ssh-keygen -f ~/.ssh/id_rsa -p
2.2.2. 管理多组密钥对
可以创建 ~/.ssh/config
来管理多组密钥对,每一个 SSH 服务器对应一组密钥对. 或者,甚至可以对所有的 SSH 服务器使用同一组密钥对. 不过如果觉得这样不合适,还是编辑配置文件:
vim ~/.ssh/config
Host SERVERNAME1
IdentitiesOnly yes
IdentityFile ~/.ssh/id_rsa_SERVER1
# CheckHostIP yes
# Port 22
Host SERVERNAME2
IdentitiesOnly yes
IdentityFile ~/.ssh/id_rsa_SERVER2
# CheckHostIP no
# Port 2177
ControlMaster auto
ControlPath /tmp/%r@%h:%p
更多选项帮助请参考:
man ssh_config 5
3. 将公钥复制到远程服务器上
创建好密钥对之后,需要将公钥上传到远程服务器上,以便用于 SSH 密钥认证登录. 公钥文件名和私钥文件名相同,只不过公钥文件带有扩展名 .pub
而私钥文件名则没有. 千万不要将私钥上传,私钥应该保存在本地.
3.1. 简单的方法
注意: 如果您的远程服务器默认使用的是
non-sh
的 shell,比如tcsh
,则此方法可能不奏效. 详情参见这个 bug.
注意: 如果使用以下两种方法外的方法请不要忘记注册公钥文件,只需要命令:
cat ~/.ssh/id_ecdsa.pub >> ~/.ssh/authorized_keys
如果公钥文件为 ~/.ssh/id_rsa.pub
,只需要输入命令:
ssh-copy-id remote-server.org
如果远程服务器用户名与本地的不同,需要指明用户名:
ssh-copy-id username@remote-server.org
如果公钥文件名不是默认的,会得到错误 /usr/bin/ssh-copy-id: ERROR: No identities found
. 这种情况下,需要修改命令为:
ssh-copy-id -i ~/.ssh/id_ecdsa.pub username@remote-server.org
如果远程服务器监听端口不是 22, 也需要指明端口:
ssh-copy-id -i ~/.ssh/id_ecdsa.pub -p 221 username@remote-server.org
3.2. 传统的方法
使用命令:
scp ~/.ssh/id_ecdsa.pub username@remote-server.org:
将位于~/.ssh/id_ecdsa.pub
的公钥上传到服务器. 注意,该命令最末的 :
不可省略. 上传成功之后,先使用口令登录到服务器,将公钥文件重命名为 authorized_keys
,并移动到 ~/.ssh
下,若 ~/.ssh
不存在则新建一个.
ssh username@remote-server.org
username@remote-server.org'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
的权限为只有当前用户,也即文件拥有者,有读写权限.
4. 安全性
4.1. 禁用密码登录
将公钥上传到 SSH 服务器上之后,就不再需要输入 SSH 账户密码来登录了. 直接使用账户密码登录容易受到暴力破解的攻击. 倘若没有提供 SSH 私钥,默认情况下,SSH 服务器就会直接使用密码登录,这就有可能让不法之徒来猜测密码,有一定的安全隐患. 要禁用这一行为,需要编辑 SSH 服务器上的 /etc/ssh/sshd_config
:
vim /etc/ssh/sshd_config
#PasswordAuthentication no
#ChallengeResponseAuthentication no
4.2. 双因素认证与公钥
从 OpenSSH 6.2 开始,可以使用 AuthenticationMethods
选项来自己添加工具链进行认证. 这样就可以配合公钥使用双因素认证了.
谷歌身份验证器设置请参考 Google Authenticator.
如果使用 PAM (Pluggable Authentication Module,插入式验证模块),编辑下面这几行:
vim /etc/ssh/sshd_config
#ChallengeResponseAuthentication yes
#AuthenticationMethods publickey,keyboard-interactive:pam
vim /etc/pam.d/sshd
#%PAM-1.0
#auth required pam_google_authenticator.so
如果设置 PAM 仅使用 pam_google_authenticator.so
,则 sshd
则会采用双因素认证,而无需密码. 如果双因素认证失败,即使有有效的公钥,sshd 也不允许登录. 可以加上 nullok
选项来允许用户使用公钥登录:
/etc/pam.d/sshd
#%PAM-1.0
#auth required pam_google_authenticator.so nullok
5. SSH agents
如果私钥使用密码短语来加密了的话,每一次使用 SSH 密钥对进行登录的时候,都必须输入正确的密码短语.
而 SSH agent 程序能够将已解密的私钥缓存起来,在需要的时候提供给 SSH 客户端. 这样子,就只需要将私钥加入 SSH agent 缓存的时候输入一次密码短语就可以了. 这为经常使用 SSH 连接提供了不少便利.
SSH agent 一般会设置成在登录会话的时候自动启动,并在整个会话中保持运行. 有不少的 SSH agent 供选择,我们将介绍几种常用的 SSH agent,可以根据您的需要进行选择.
5.1. ssh-agent
ssh-agent 是 OpenSSH 自带的一个 SSH agent,它可以直接作为 SSH agent 来使用,或者作为其他 SSH 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
可以将上述命令添加到 ~/.bash_profile
,以便启动 登录外壳#登录外壳) 的时候它自动运行.
echo 'eval $(ssh-agent)' >> ~/.bash_profile
如果想要所有的用户都可以使用 ssh-agent
,可以直接把上述命令加入到 /etc/profile
.
echo 'eval $(ssh-agent)' >> /etc/profile
ssh-agent
运行起来之后,还需要将私钥加入它的缓存.
ssh-add ~/.ssh/id_ecdsa
#Enter passphrase for /home/user/.ssh/id_ecdsa:
#Identity added: /home/user/.ssh/id_ecdsa (/home/user/.ssh/id_ecdsa)
如果想要在登录 shell 的时候自动添加私钥到 ssh-agent
的缓存,需要在 ~/.bash_profile
加入以下命令:
ssh-add
如果私钥已用密码短语加密,ssh-add
会提示输入密码短语. 输入正确的密码短语之后,在当前会话中就不需要再次输入密码短语就能够使用密钥对进行 SSH 登录了.
如果想要在用到私钥的时候再输入密码短语,可以添加以下命令到 ~/.bashrc
:
ssh-add -l >/dev/null || alias ssh='ssh-add -l >/dev/null || ssh-add && unalias ssh; ssh'
但是这样做有个缺点,每次启动 登录外壳#登录外壳) 都会产生一个 ssh-agent
实例,并在会话期间一直运行. 不久之后,系统中就会有多个根本不再需要的 ssh-agent
进程在运行.
稍后,我们将介绍其他 ssh-agent
前端,它们能够避免这个问题.
5.1.1. 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
在运行了.
6. 疑难排解
如果 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