李守中
该站已迁往根域名 https://lishouzhong.com
需要注意,迁移后的文章的 url 可能会发生变化。
域名 https://note.lishouzhong.com 下的内容将不再更新,但已有内容会永久保留。

SSH 密钥管理

Table of Contents

1. 密钥是什么

密钥 key 是一个或一对非常大的数字,通过加密算法得到。对称加密只需要一个密钥。非对称加密需要两个密钥成对使用,分为公钥 public key 和私钥 private key

公钥和私钥一一对应,每一个私钥都有且仅有一个对应的公钥,反之亦然。私钥自己持有,不能泄露;公钥可以对外发送。

如果数据被公钥加密,那么有且只有对应的私钥才能解密;如果数据被私钥加密 (这个过程一般称为 签名),也只有使用对应的公钥解密。

SSH 身份认证可以采用非对称加密密钥认证,每个用户凭各自生成的密钥对登录。

2. 用 ssh-keygen 命令生成密钥对

2.1. 基本用法

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 等。

其中:

  • dsarsa 算法出现较早,对旧设备的兼容性好:
    • dsa 算法生成的密钥长度固定 (早期 1024 位,后期 2048 位),验证速度快但安全性很差,依赖随机数生成器,已不再使用
    • rsa 算法生成的密钥验证速度慢但比 dsa 更安全,且不依赖随机数生成器,所以现在依旧在使用
    • dsa 算法的安全性依赖于高质量的随机数生成器
    • rsa 算法当前默认使用的密钥长度为 3072 位, 但增加 RSA 密钥的长度并不会等比例地提高安全性
  • ecdsa 算法是 dsa 算法结合 ECC (Elliptic Curve Cryptograph) 椭圆曲线算法的实现:
    • openssh 中的 ecdsa 算法是 dsa 算法结合 NIST 椭圆曲线算法的实现
    • 它的安全性依赖于高质量的随机数生成器 (索尼 PS3 被破解的原因是它的 ecdsa 密钥使用了有缺陷的随机数生成算法)
    • 可以通过 -b 参数显式指定 NIST 椭圆曲线的大小,来隐式指定密钥长度,无法直接指定密钥长度
    • NIST 椭圆曲线的大小 (-b 参数) 只能选 256, 384, 521 其中之一,分别对应 NIST P-256, NIST P-384, NIST P-521 三个曲线标准
  • ed25519 算法是 dsa 算法结合 ECC (Elliptic Curve Cryptograph) 椭圆曲线线算法的实现:
    • openssh 中的 ed25519 算法是 dsa 算法结合 Ed25519 椭圆曲线算法的实现
    • 在概念上:
      • ed25519 算法是 eddsa 算法结合 Ed25519 椭圆曲线算法的实现
      • eddsa 算法是 dsa 算法结合 Twisted Edwards Curve (一种椭圆曲线的平面模型) 椭圆曲线算法的实现
      • 可以认为 ed25519 算法是 eddsa 算法的一种特例
      • eddsa 算法可以避免由随机数生成器的缺陷导致的安全问题
      • 以上提到的内容都没有专利问题
    • 它可以避免由随机数生成器的缺陷导致的安全问题
    • 自 OpenSSH 6.5 引入,安全性好于 dsa, ecdsa, eddsa
    • ed25519 算法生成的密钥长度固定,所以 -b 参数不起作用
  • -sk (with Secure Key) 结尾的密钥类型必须与 FIDO U2F 硬件令牌一起使用,一般不用这种密钥

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 将不使用这些密钥文件。

2.2. 配置项

ssh-keygen 命令的常用配置项有下面这些:

  • -b 指定密钥的二进制长度。密钥越长越不容易被破解,对应地,算力开销也会增加,但增加密钥长度并不会等比例提高安全性
  • -C 为密钥文件指定新的注释
  • -f 指定生成的私钥文件: ssh-keygen -t ed25519 -f <key-file> 指定私钥文件 <key-file> 和公钥文件 <key-file>.pub 的路径
  • -F 检查某个主机名是否在 known_hosts 文件里面: ssh-keygen -F <host>
  • -N 指定私钥的密码 passphrase: ssh-keygen -t ed25519 -N <password>
  • -p 重新指定私钥密码。与 -N 的不同之处在于,新密码不在命令中指定,而是执行后再输入。ssh 先要求输入旧密码,然后要求输入两遍新密码
  • -R 将指定的主机公钥指纹移出 ~/.ssh/known_hosts 文件: ssh-keygen -R example.com
  • -t 指定生成密钥的加密算法

比如 ssh-keygen -t rsa -b 4096 -C "<comment>" 命令生成一个 4096 位 RSA 加密算法的密钥对,并加注释。(OpenSSH 当前默认的 rsa 密钥长度是 3072)

3. 手动上传公钥

生成密钥以后,公钥必须上传到服务器,才能正常使用密钥对登录。

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 密钥或公钥不生效:

  • 用户家目录权限为 755 或者 700,不能是 77x 或 777,即保证 other 用户不能有 w 权限。
  • ~/.ssh 目录权限一般为 755 或者 700。
  • 公钥文件 (rsa_id.pub 及 ~/.ssh/authorized_keys) 权限一般为 644 或 600。
  • 私钥权限必须为 600。

只要公钥上传到服务器,下次登录时,OpenSSH 就会自动采用密钥登录,不再提示输入密码。

4. 用 ssh-copy-id 命令自动上传公钥

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 命令之前,务必保证文件的末尾是换行符 (即,文件末尾有一个空行)。

5. ssh-agent 与 ssh-add 命令

5.1. 基本用法

私钥设置了密码以后,每次使用都必须输入密码,有时让人感觉非常麻烦。比如,连续使用 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 退出。

5.2. ssh-add 命令

ssh-add 命令用来将私钥加入 ssh-agent,它有如下的参数。

  • -d 从内存中删除指定的私钥: ssh-add -d name-of-key-file
  • -D 从内存中删除所有已经添加的私钥: ssh-add -D
  • -l 列出所有已经添加的私钥: ssh-add -l

5.3. 多主机配置实例

# 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

6. 关闭密码登录

为了安全性,启用密钥登录之后,最好关闭服务器的密码登录。

对于 OpenSSH 需要编辑 etc/ssh/sshd_config 文件,将 /PasswordAuthentication 这一项设为 no

修改配置文件以后,重新启动 sshd 以使新配置生效。



Last Update: 2023-11-12 Sun 21:27

Generated by: Emacs 28.2 (Org mode 9.5.5)   Contact: lsz.sino@outlook.com

若正文中无特殊说明,本站内容遵循: 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议