密钥 key 是一个或一对非常大的数字,通过加密算法得到。对称加密只需要一个密钥。非对称加密需要两个密钥成对使用,分为公钥 public key 和私钥 private key 。
公钥和私钥一一对应,每一个私钥都有且仅有一个对应的公钥,反之亦然。私钥自己持有,不能泄露;公钥可以对外发送。
如果数据被公钥加密,那么有且只有对应的私钥才能解密;如果数据被私钥加密 (这个过程一般称为 签名),也只有使用对应的公钥解密。
SSH 身份认证可以采用非对称加密密钥认证,每个用户凭各自生成的密钥对登录。
OpenSSH 提供了指令程序 ssh-keygen
用来生成密钥。
需要提一下,在非对称加密中,公钥和私钥不可互推,所以,知道公钥不能推出私钥,反之亦然。
有读者问为什么用 openssl genrsa -out rsak 3072
生成一个私钥以后,可以用 openssl rsa -in rsak -pubout -out rsap
把公钥提取出来,然而理论上又不能从私钥得到公钥。实际上,rsak 文件并不是私钥文件,rsak 文件包含私钥和公钥的描述信息两部分。用 openssl rsa -text -in rsak
可以看到 rsak 文件中包含了什么东西,其中 modulus, publicExponent 两个字段描述了公钥的信息,提取公钥的步骤只是根据这些信息把公钥写到另一个地方而已。
而 ssh-keygen -y -f ~/.ssh/id_rsa > ~/.ssh/id_rsa.pub
能提取公钥的原因也是 ~/.ssh/id_rsa 私钥文件中包含了公钥的描述信息,提取公钥只需要根据描述信息把公钥写到另一个地方即可。
ssh-keygen -t <encryption-method>
中 -t 参数指定了生成密钥对的算法。可选 dsa, rsa, ecdsa, ecdsa-sk, ed25519, ed25519-sk 等。
其中:
NIST P-256, NIST P-384, NIST P-521 这三种曲线标准是美国国家标准技术研究所推出的标准。业界怀疑 NSA 在随机数生成器中留有可以用来推算私钥的后门,所以欧洲推出了 Brainpool P-256, Brainpool P-384, Brainpool P-521 曲线标准。比特币使用 secp256k1 曲线,由 SECG (Standards for Efficient Cryptography Group) 推出,不用担心后门问题。
所以,建议,旧设备使用 rsa 算法生成长度为 3072/4096 位的密钥,新设备使用 ed25519 算法生成密钥。
比如要生成一个使用 ed25519 算法的密钥,执行 ssh-keygen -t ed25519
后,会要求用户回答一些问题:
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/<username>/.ssh/id_ed25519): # 密钥文件的存储路径,按 Enter 保持默认
Enter passphrase (empty for no passphrase): # 使用密钥文件时需要输入的密码
Enter same passphrase again: # 使用密钥文件时需要输入的密码
Your identification has been saved in /home/<username>/.ssh/id_ed25519
Your public key has been saved in /home/<username>/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:IDJNSlpwsT4RiyEIiuduC98yJTAXVSFd+LFt2oU5U64 <username>@<hostname>
The key's randomart image is:
+--[ED25519 256]--+
|B.*o+oo+. |
|=B.B .o . . |
|= O.o .. + = |
|o+.+ . .o B o |
| ++ S+ = |
| .... . E |
|. oo |
| +oo |
| oo. |
+----[SHA256]-----+
示例中,第一个问题询问密钥保存路径,默认是 ~/.ssh/id_ed25519 文件,这个是私钥的文件名,对应的公钥文件 ~/.ssh/id_ed25519.pub 是自动生成的。
如果选择 rsa 算法,密钥文件默认是 ~/.ssh/id_rsa (私钥) 和 ~/.ssh/id_rsa.pub (公钥)。
第二个问题,询问是否给私钥文件设密码 (passphrase)。这样即使入侵者拿到私钥,还需要破解密码才能使用。如果为了方便不设密码,直接按回车键密码就会为空。接着是第二次密码确认,两次输入必须一致。注意,这里 密码 的英文单词是 passphrase ,避免与 Linux 账户的密码单词 password 混淆。
最后生成私钥和公钥,屏幕上给出公钥的指纹以及当前的用户名和主机名作为注释,用于识别密钥的来源。
公钥文件和私钥文件都是文本文件,自动生成的公钥文件通常以 .pub 结尾。公钥文件的内容类似下面这样。
$ cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIqgAHzoVuCQncg7w5sWuSxmIs7Vg3dCVnuwWWdvUPYB <username>@<hostname>
示例中,末尾的 <username>@<hostname>
是公钥的注释,用来识别不同的公钥,表示这是主机 <hostname>
上的用户 <username>
的公钥。注释不是必需项。
给密钥文件正确的权限: chmod 600 ~/.ssh/id_ed25599 && chmod 600 ~/.ssh/id_ed25599.pub
否则 ssh 将不使用这些密钥文件。
ssh-keygen 命令的常用配置项有下面这些:
ssh-keygen -t ed25519 -f <key-file>
指定私钥文件 <key-file>
和公钥文件 <key-file>.pub
的路径ssh-keygen -F <host>
ssh-keygen -t ed25519 -N <password>
ssh-keygen -R example.com
比如 ssh-keygen -t rsa -b 4096 -C "<comment>"
命令生成一个 4096 位 RSA 加密算法的密钥对,并加注释。(OpenSSH 当前默认的 rsa 密钥长度是 3072)
生成密钥以后,公钥必须上传到服务器,才能正常使用密钥对登录。
OpenSSH 规定,用户公钥保存在服务器的 ~/.ssh/authorized_keys 文件。要登陆的用户的密钥必须保存在该用户主目录 ~/.ssh/authorized_keys 文件中。只要把公钥添加到这个文件之中,就相当于公钥上传到服务器了。每个公钥占据一行。如果该文件不存在,可以手动创建。
用户可以手动编辑该文件,把公钥粘贴进去,也可以在本机计算机上,执行下面的命令:
cat ~/.ssh/id_rsa.pub | ssh <user_name>@<host> "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
<user_name>@<host>
替换成所要登录的用户名和主机名。
ssh 为了安全,对属主的目录和文件权限有所要求。如果权限不对,则 ssh 密钥或公钥不生效:
只要公钥上传到服务器,下次登录时,OpenSSH 就会自动采用密钥登录,不再提示输入密码。
OpenSSH 自带一个 ssh-copy-id
命令,可以自动将公钥记录到远程服务器的 ~/.ssh/authorized_keys 文件中。如果文件不存在则自动创建该文件。
在本地计算机执行 ssh-copy-id -i <key-file> <user_name>@<host>
将本地的公钥拷贝到服务器。其中 -i 参数指定公钥文件, <user_name>
是所要登录的账户名, <host>
是服务器地址。如果省略用户名,默认为当前的本机用户名。
注意,公钥文件可以不指定路径和 .pub 后缀名,ssh-copy-id 会自动在 ~/.ssh 目录里面寻找。比如执行 ssh-copy-id -i id_rsa <user_name>@<host>
公钥文件会自动匹配到 ~/.ssh/id_rsa.pub 。
ssh-copy-id 采用密码登录,系统会提示输入远程服务器的密码。
注意: ssh-copy-id 直接将公钥添加到 ~/.ssh/authorized_keys 文件的末尾。如果文件的末尾不是一个换行符,会导致新的公钥添加到前一个公钥的末尾,两个公钥连在一起,使得两条公钥都无法生效。
所以,如果 ~/.ssh/authorized_keys 文件已经存在,使用 ssh-copy-id 命令之前,务必保证文件的末尾是换行符 (即,文件末尾有一个空行)。
私钥设置了密码以后,每次使用都必须输入密码,有时让人感觉非常麻烦。比如,连续使用 scp 命令远程拷贝文件时,每次都要求输入密码。
ssh-agent 命令就是为了解决这个问题而设计的,它让用户在整个 Bash 对话 (session) 之中,只在第一次使用 SSH 命令时输入密码,然后将私钥保存在内存中,后面都不需要再输入私钥的密码了。
第一步,推荐的方式是用 ssh-agent $SHELL
新建一个子 SHELL,ssh-agent 进程会随子 SHELL 退出结束。
如果想在当前对话启用 ssh-agent,可以使用 :
# linux 命令,注意是反引号
$ eval `ssh-agent`
# windows 命令
$ eval $(ssh-agent)
ssh-agent 会先自动在后台运行,并将需要设置的环境变量输出在屏幕上,类似下面这样。
SSH_AUTH_SOCK=/tmp/ssh-AUBdzk7V7WAR/agent.5861; export SSH_AUTH_SOCK;
SSH_AGENT_PID=5862; export SSH_AGENT_PID;
echo Agent pid 5862;
eval
的作用,就是运行上面的,执行 ssh-agent
命令后的输出,设置环境变量。
注意,不建议以这种方式在当前对话启用 ssh-agent。 使用 eval 启动的 ssh-agent 进程必须被手动杀死,不会随 SHELL 退出。
第二步,在新建的 Shell 对话里面,使用 ssh-add </path/to/private-key-file>
命令添加默认的私钥文件。
$ ssh-add ~/.ssh/id_rsa
Enter passphrase for /home/you/.ssh/id_dsa: ********
Identity added: /home/you/.ssh/id_dsa (/home/you/.ssh/id_dsa)
上面例子中,添加私钥时,会要求输入密码。以后,在这个对话里面再使用密钥时,就不需要输入私钥的密码了,因为私钥已经加载到内存里面了。
第三步 ssh <user_name>@<remote_host>
正常登录远程服务器。
ssh 使用的是默认的私钥。这时如果私钥有密码,ssh 不再询问密码,直接取出内存里面的私钥。
如果要使用其他私钥登录服务器,需要指定私钥文件 ssh –i ~/.ssh/<key_file> <remote_host>
。
如果要退出 ssh-agent,可以 Ctrl+d 直接退出子 Shell,也可以执行 ssh-agent -k
退出。
ssh-add
命令用来将私钥加入 ssh-agent,它有如下的参数。
ssh-add -d name-of-key-file
ssh-add -D
ssh-add -l
# gitlab
Host git.iboxpay.com
HostName git.iboxpay.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_rsa
# github
Host github2.com
HostName github.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/feygh
# github
Host github.com
HostName github.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_rsa
为了安全性,启用密钥登录之后,最好关闭服务器的密码登录。
对于 OpenSSH 需要编辑 /etc/ssh/sshd_config 文件,将 PasswordAuthentication 这一项设为 no 。
修改配置文件以后,重新启动 sshd 以使新配置生效。