深入了解SSH
学习:深入了解SSH
ssh
有商业和开源版本,其中openssh是开源中最流行的。
ssh历史
1995 年 7 月, 芬兰学者Tatu Ylonen 以免费软件的形式将一套保护信息传输的程序(也就是 SSH )发布出去。程序很快流行,到年底已经有两万用户,遍布五十国家。所以在年底时,他创立了 SSH 通信安全公司来继续开发和销售 SSH,所以它变成了专有软件。在 1999 年,瑞典程序员基于 SSH 最后一个开源的版本 1.2.12 开发了 OSSH,之后 OpenBSD 开发者在 OSSH 的基础上进行大量修改,形成了 OpenSSH,它是目前唯一一种最流行的 SSH 实现,成为了所有操作系统的默认组件。
用法
生成密钥
ssh-keygen
用生成、管理和转换身份验证密钥。
参数说明:
- -b bits 指定要创建的秘钥中的位数,默认 2048 位,值越大,密码越复杂
- -C comment 注释,在 id_rsa.pub 中末尾
- -t rsa/dsa等 指定要创建的秘钥类型,默认为 RSA
- -f filename 指定公私钥的名称,会在
$HOME/.ssh
目录下生产私钥 filename 和公钥 filename.pub - -N password 指定使用秘钥的密码,使得多人使用同一台机器时更安全
常用命令:
# 生成公私钥,默认文件为 ~/.ssh/id_rsa
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
id_rsa:私钥
id_rsa.pub:公钥
管理私钥
ssh-agent和ssh-add
用于管理私钥,一般情况下会使用ssh-keygen生成的(没有密码的)私钥作为默认私钥,此时没有必要使用ssh-agent,当出现以下两种情况时则需要使用:
- 使用不同的私钥连接不同的主机时,需要手动指定对应的私钥。(ssh-agent能帮助选择对应的私钥进行认证)
- 当私钥设置了密码时,又需要频繁的使用私钥进行认证。(ssh-agent能帮助免去重复输入密码)
# 启动代理
eval `ssh-agent`
# 关闭代理
ssh-agent -k
# 在 ~/.bashrc 中加入以下来实现登陆自动启动 ssh-agent,退出自动 kill 掉程序
eval $(ssh-agent -s) > /dev/null
trap 'test -n "$SSH_AGENT_PID" && eval `/usr/bin/ssh-agent -k` > /dev/null' 0
# 查看代理中的私钥
ssh-add -l
# 查看代理中私钥对应的公钥
ssh-add -L
# 移除指定的私钥
ssh-add -d /path/of/key/key_name
# 移除所有的私钥
ssh-add -D
发送公钥
ssh-copt-id
在登陆时,将公钥放在(绑定)要登陆的服务器上。(云服务器大都默认关闭密钥登陆 PasswordAuthentication no)
# 发送公钥的两种方式(等价)
ssh-copy-id -i ~/.ssh/id_rsa.pub user@host
ssh user@host 'mkdir -p .ssh && cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub
登陆
登陆配置
SSH 登陆服务器需要知道服务器的主机地址(主机名或主机 IP 地址),用户名和密码,有时还要指定端口号(默认 22 ),一般使用的登陆命令如下:
# 登陆目标服务器( 172.17.132.120 )
ssh -p 58422 user@172.17.132.120
# 通过跳板机登陆目标服务器( 172.17.132.120 )
ssh -p 58422 user@jumper.example.com ssh user@172.17.132.120
# 端口映射
ssh -p 58422 user@jumper.example.com -fNL 5433:172.17.132.120:5432 -N
通过设置$$HOME/.ssh/config$
,可以使用别名直接登陆。
# 登陆目标服务器( 172.17.132.120 )
ssh target
# 通过跳板机登陆目标服务器( 172.17.132.120 )
ssh jump_target
# 端口映射
## 登陆时通过 LocalForward 配置
ssh jump_target
## 使用-L来实现本地端口映射
ssh -C -N -g -L 5433:127.0.0.1:5432 jump_target
# 通用配置,所有配置都使用
Host *
AddKeysToAgent yes # 将私钥添加到ssh-agent中
UseKeychain yes # 保存密码到agent中
ServerAliveInterval 10 # 连接心跳间隔10s
ServerAliveCountMax 3 # 重连次数为3
# target配置
Host target
HostName 172.17.132.120
User user
Port 58422
IdentityFile ~/.ssh/id_rsa
# 跳板机配置
Host jumper
HostName jumper.example.com
User user
Port 58422
IdentityFile ~/.ssh/id_rsa
Host jump_target
HostName 172.17.132.120
User user
Port 22
IdentityFile ~/.ssh/id_rsa
ProxyCommand ssh user@jumper -W %h:%p 2>/dev/null
LocalForward 5433 localhost:5432 # 本地5433映射到jump_target的5432
一般在做远程开发时,VS Code 的 Remote 插件会读取本地的配置文件
$HOME/.ssh/config
,所以可以在本地匹配好,直接登陆。
首次登陆
一般在 $HOME/.ssh
目录下除了公私钥文件,config 配置文件,authorized_keys 认证文件外,还有一个 known_hosts
文件, 这个文件记录了远程主机 ip 和远程主机对应的公钥指纹。
我们在第一次登陆(密码或秘钥登陆)服务器时,会有如下的提示界面:
### SSH 首次登陆的提示
The authenticity of host '127.0.0.1 (127.0.0.1)' can't be established.
ECDSA key fingerprint is SHA256:HosOqhcUmbB7QG81yCuDPkvxTgot+vpple+czXPrEug.
ECDSA key fingerprint is MD5:fd:d7:e1:2c:42:4e:b4:2d:a3:21:4d:d1:c4:74:64:2d.
Are you sure you want to continue connecting (yes/no)?
此时 known_hosts
并没有 127.0.0.1 这台机器的指纹信息,所以显示这个提示来让我们确认这个指纹是否是目标机器的 ECDSA 算法的指纹。 当我们输入 yes 确认后,在下次登录的时候,远程主机发送过来的公钥指纹,直接和 known_hosts
文件中对应 ip 的公钥指纹比较即可。
# 本机查看服务器 172.17.132.120 的所有公钥(要与服务器上 /etc/ssh 下面的公钥 *.pub 一致)
ssh-keyscan -p 22 172.17.132.120
# 查看服务器公钥 ecdsa 的指纹 -E md5/sha256 指纹 hash 算法
ssh-keygen -E md5 -lf /etc/ssh/ssh_host_ecdsa_key.pub
## 256 MD5:84:3d:9c:6e:75:f2:6b:b2:0b:40:aa:d6:29:2f:b4:40 no comment (ECDSA)
## 256 SHA256:ZoGnph63gnKLC9wQYrHYVU8ROTf6+K9LKAjn+jrXB2o no comment (ECDSA)
# 从客户端查看服务器公钥 ecdsa 的指纹(初次登陆时要验证的指纹)
ssh-keyscan -t ecdsa -p 22 172.17.132.120 |ssh-keygen -lf -
# 公钥转换成特定指纹 hash 算法的指纹
awk '{print $2}' /etc/ssh/ssh_host_ecdsa_key.pub | base64 -d|openssl sha256 -binary |base64
known_hosts
这个文件是客户端验证服务端身份的重要依据。每次客户端向服务端发起连接请求时,不仅服务端要验证客户端的合法性,客户端也需要验证服务端的身份。客户端就是通过
known_hosts
中的公钥指纹来验证服务器是否发生了变化,它在一定程度上能避免中间人攻击,除了第一次登陆,因为那时 known_hosts 中还没有服务器的身份信息,所以对于首次提示的登陆指纹信息还是需要和服务器比对,最安全保险的做法是第一次登陆就使用秘钥登陆。
登录流程
- 版本号协商阶段
- 密钥和算法协商阶段
- 认证阶段
- 会话请求阶段
- 会话交互阶段
登陆方式分为密码登陆和密钥登录。
- 密码登陆
- 客户端使用密钥和算法协商阶段生成的会话密钥加密账号和密码(口令)、认证方式,将结果发送给服务器。
- 服务端使用获得的会话密钥解密报文,得到账号和口令。
- 服务端对这个账号和口令进行判断,如果失败,向客户端发送认证失败报文,其中包含了可以再次认证的方法列表。
- 客户端从认证方法列表中选择一种方法进行再次认证。
- 这个过程反复进行,直到认证成功或者认证次数达到上限,服务端关闭本次TCP连接。
- 密钥登录
- 客户端使用密钥和算法协商阶段生成的会话密钥加密账号、认证方法、id_rsa.pub,将结果发送给服务端。
- 服务端使用会话密钥解密报文,得到账号、id_rsa.pub。服务端在
$HOME/.ssh/authorized_keys
中找对应的公钥,如果没有找到,发送失败消息给客户端;如果找到,比较客户发送过来的这个公钥和找到的公钥,如果内容相同,服务端生成一个随机的字符串,简称“质询”,然后使用找到的公钥加密这个质询,然后使用会话密钥再次加密。 - 服务端把这个双重加密的数据发送给客户端。
- 客户端使用会话密钥解密报文,然后使用 id_rsa 再次解密数据,得到质询。
- 客户端使用会话密钥加密质询,发送给服务端。
- 服务端使用会话密钥解密报文,得到质询,判断是不是自己生成的那个质询,如果不相同,发送失败消息给客户端,如果相同,认证通过。
免密安全传输
scp/rsync/sftp
都可以基于 SSH 来进行免密安全传输。
# 从本地同步 src.tar.gz 文件到远程服务器 jump_target 的目录 /path/to/des/
scp src.tar.gz jump_target:/path/to/des/
rsync -avz src.tar.gz jump_target:/path/to/des/
sftp登录 & lcd src.tar.gz /path/to/des
# 从远程服务器 jump_target 的文件 /path/to/src.tar.gz 到本地
scp jump_target:/path/to/src.tar.gz .
rsync -avz jump_target:/path/to/src.tar.gz .
sftp登录 & put /path/to/des/src.tar.gz