掌握SSH这篇就够了

SSH 是每一台电脑的标准配置,Linux 就没必要说了,连 windows 也从 2018 年开始自带 OpenSSH 了。
它主要的用途是登录到远程电脑中执行命令,在云开发的时代,它是每一个程序天天都要用到的工具。本文将简单介绍一下它的原理,
基本用法以及端口转发和动态转发等高阶用法。mysql

介绍

SSH 叫安全外壳协议(Secure Shell),是一种加密的网络传输协议,可在不安全的网络中网络服务提供安全的传输环境。它经过在网络中建立安全隧道来实现 SSH 客户端和服务器之间的链接。最先的时候,互联网通讯都是明文通讯,一旦被截获,内容就会被暴露。1995年,芬兰学者 Tatu Ylonen 设计了 SSH 协议,将登陆信息所有加密,成为互联网安全的一个基本解决方案,迅速在全世界得到推广,目前已经成为全部操做系统的标准配置。linux

SSH 是一种协议, 存在多种实现,既有商业实现,也有开源实现(OSSH,OpenSSH)。本文使用的自有软件 OpenSSH, 毕竟它是目前最流行的 SSH 实现,并且是全部操做系统的默认组件。git

TIPS: OpenSSH发展史
1995 年 7 月, Tatu Ylonen 以避免费软件的形式将一套保护信息传输的程序(也就是 SSH )发布出去。程序很快流行,到年末已经有两万用户,遍及五十国家。因此在年末时,他创立了 SSH 通讯安全公司来继续开发和销售 SSH, 因此它变成了专有软件。在 1999 年,瑞典程序员基于 SSH 最后一个开源的版本 1.2.12 开发了 OSSH,以后 OpenBSD 开发者在 OSSH 的基础上进行大量修改,造成了 OpenSSH。它是目前惟一一种最流行的 SSH 实现,成为了全部操做系统的默认组件。

原理介绍

SSH 之因此一经提出,就获得了快速发展,是由于数据的安全性对任何人都很是重要。这里咱们对其保护数据安全的原理进行探究。程序员

在聊加密前先介绍一下几个密码学的基本概念:github

  • 明文plaintext 指传送方(通常指客户端)想要接受方(通常指服务端)得到的可读信息
  • 密文ciphertext 指明文通过加密后所产生的信息
  • 秘钥key 指用来完成加密、解密、完整性验证等密码学应用的密码信息,是明文转换为密文或密文转换为明文的算法须要的参数
  • 私钥 指私有的秘钥
  • 公钥 指公开的秘钥

对称加密

对称加密就是加密或解密使用的是同一个秘钥。比较经常使用的对称加密算法有 AES,DES等。其具体的时序图以下:
ssh_symmetric.png算法

对称加密的优势是加解密效率高,速度快。对于服务端而言,它和每一个客户端都要有一个秘钥,庞大的客户端数目致使秘钥数目多,并且一旦机器被登陆,全部的秘钥都泄露,因此缺点是秘钥的管理和分发比较困难,不安全。sql

非对称加密

非对称加密须要一对秘钥来进行加密和解密,公开的秘钥叫公钥,私有的秘钥叫私钥。注意公钥加密的信息只有私钥才能解开(加密过程),私钥加密的信息只有公钥才能解开(验签过程)。比较经常使用的非对称加密算法有 RSA。其具体的时序图以下:
ssh_asymmetric.pngwindows

非对称加密的优势是安全性更高,秘钥管理比较方便,每一个服务器只要维护一对公私钥便可。缺点是加解密耗时长,速度慢。不过对于如今的计算机而言,这点成本能够忽略不计。api

中间人攻击

中间人攻击的英文全称是 Man-in-the-middle attack,缩写为 MITM。在密码学和计算机安全领域中是指攻击者与通信的两端分别建立独立的联系,并交换其所收到的数据,使通信的两端认为他们正在经过一个私密的链接与对方直接对话,但事实上整个会话都被攻击者彻底控制。在中间人攻击中,攻击者能够拦截通信双方的通话并插入新的内容。在许多状况下这是很简单的(例如,在一个未加密的 Wi-Fi 无线接入点的接受范围内的中间人攻击者,能够将本身做为一个中间人插入这个网络)。其具体的时序图以下:
ssh_mitm.png安全

受到中间人攻击的关键缘由是客户端不知道服务端的公钥真假,服务端也不知道客户端的公钥真假。因此破解这个问题的关键是如何相互认证,也就是要像黄宏《开锁》小品里同样证实我就是我,你就是你。

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@  WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!   @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ECDSA key sent by the remote host is
SHA256:sYNNR1L6T5cSAG4BndqtdDhJEI0eB9LamBTkuIue3+0.
Please contact your system administrator.
Add correct host key in /Users/xx/.ssh/known_hosts to get rid of this message.
Offending ECDSA key in /Users/xx/.ssh/known_hosts:40
ECDSA host key for [xx.com] has changed and you have requested strict checking.
Host key verification failed.

基本用法

生成公钥

ssh-keygen 是安全外壳( SSH )协议套件的标准组件,用于生成,管理和转换身份验证密钥。

参数说明

  • -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"

管理秘钥

ssh-agent 和 ssh-add 是安全外壳(SSH)协议套件的标准组件,用于管理私钥。通常状况下咱们使用不带密码的 id_rsa 做为咱们的默认私钥,此时是不必启动 ssh-agent 的。当咱们碰到如下两种状况则须要它:

  1. 使用不一样的秘钥链接到不一样的主机时,须要手动指定对应的秘钥。(ssh-agent 帮咱们选择对应的秘钥进行认证)
  2. 当私钥设置了密码,而咱们又须要频繁的使用私钥进行认证。(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-copy-id 是一个用来将公钥放到服务器上的脚本。它经过 SSH 密码登录远程服务器,并将指定的公钥放到远程服务器 $HOME/.ssh/authorized_keys 中。这个操做也能够先登录到服务器中,而后经过 vi 等文本编辑命令向 $HOME/.ssh/authorized_keys 中加入容许登录的公钥。不过对于云服务器能够在启动服务器时在页面上操做绑定公钥,这样更安全些(阿里云和腾讯云默认关闭秘钥登录 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 )。主机名还好,可是主机IP 地址就比较难记的,特别是当你可能要登陆十几台服务器时。通常咱们使用的登录命令以下:

# 登录目标服务器( 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
TIPS:
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
TIPS: known_hosts的重要性
known_hosts 这个文件是客户端验证服务端身份的重要依据。每次客户端向服务端发起链接请求时,不只服务端要验证客户端的合法性,客户端也须要验证服务端的身份。客户端就是经过 known_hosts 中的公钥指纹来验证服务器是否发生了变化。它在必定程度上能避免中间人攻击,除了第一次登录,由于那时 known_hosts 中尚未服务器的身份信息,因此对于首次提示的登录指纹信息仍是须要和服务器比对的。最安全保险的作法是第一次登录就使用秘钥登录。

登录流程

  1. 版本号协商阶段
  2. 密钥和算法协商阶段
    服务端和客户端分别发送算法协商报文给对方,报文中包含本身支持的公钥算法列表、加密算法列表、消息验证码算法列表、压缩算法列表等。服务端和客户端根据对方和本身支持的算法得出最终使用的算法。服务端和客户端利用 DH 交换算法、主机密钥对等参数,生成会话密钥和会话 ID。
  3. 认证阶段( publickey > gssapi-keyex > gssapi-with-mic > password )
  4. 会话请求阶段
  5. 会话交互阶段

密码登录

密码登录的认证流程以下:

  1. 客户端使用密钥和算法协商阶段生成的会话密钥加密帐号、认证方法、口令,将结果发送给服务器。
  2. 服务端使用得到的会话密钥解密报文,获得帐号和口令。
  3. 服务端对这个帐号和口令进行判断,若是失败,向客户端发送认证失败报文,其中包含了能够再次认证的方法列表。
  4. 客户端从认证方法列表中选择一种方法进行再次认证。
  5. 这个过程反复进行,直到认证成功或者认证次数达到上限,服务端关闭本次TCP链接。

ssh_password

秘钥登录

秘钥登录的认证流程以下:

  1. 客户端使用密钥和算法协商阶段生成的会话密钥加密帐号、认证方法、id_rsa.pub,将结果发送给服务端。
  2. 服务端使用会话密钥解密报文,获得帐号、id_rsa.pub。服务端在 $HOME/.ssh/authorized_keys 中找对应的公钥,若是没有找到,发送失败消息给客户端,若是找到,比较客户发送过来的这个公钥和找到的公钥,若是内容相同,服务端生成一个随机的字符串,简称“质询”,而后使用找到的公钥加密这个质询,而后使用会话密钥再次加密。
  3. 服务端把这个双重加密的数据发送给客户端
  4. 客户端使用会话密钥解密报文,而后使用 id_rsa 再次解密数据,获得质询。
  5. 客户端使用会话密钥加密质询,发送给服务端。
  6. 服务端使用会话密钥解密报文,获得质询,判断是否是本身生成的那个质询,若是不相同,发送失败消息给客户端,若是相同,认证经过。

ssh_key

两者区别

咱们常说使用秘钥登录比密码登录更方便更安全,为何这么说呢?方即是由于不用记密码,安全是一方面敏感关键的密码没有在传输,另外一方面是由于质询的存在使得在一次对话中同时验证了客户端和服务端。

高阶用法

免密安全传输

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/

# 从远程服务器 jump_target 的文件 /path/to/src.tar.gz 到本地
scp jump_target:/path/to/src.tar.gz .
rsync -avz jump_target:/path/to/src.tar.gz .

端口转发

SSH 不只仅可以自动加密和解密 SSH 客户端与服务端之间的网络数据,同时,SSH 还可以提供了一个很是有用的功能,那就是端口转发,即将 TCP 端口的网络数据,转发到指定的主机某个端口上,在转发的同时会对数据进行相应的加密及解密。若是工做环境中的防火墙限制了一些网络端口的使用,可是容许 SSH 的链接,那么也是可以经过使用 SSH 转发后的端口进行通讯。转发主要分为本地转发与远程转发两种类型。

转发经常使用参数

  • -C: 压缩传输,提升传输速度。
  • -f: 将 SSH 传输转入后台执行,不占用当前 SHELL, 常与 -N 一块儿使用
  • -N: 创建静默链接(创建了链接但看不到具体会话)
  • -g: 在 -L/-R/-D 参数中,容许远程主机链接到创建的转发的端口,若是不加这个参数,只容许本地主机创建链接。
  • -L: 本地端口转发
  • -R: 远程端口转发
  • -D:动态转发( SOCKS 代理)
  • -P: 指定 SSH 端口

本地端口转发

由本地网络服务器的端口 A,转发到远程服务器端口 B。说白了就是,将发送到本地端口 A 的请求,转发到目标端口 B。格式以下

ssh -L 本地网卡地址:本地端口:目标地址:目标端口 用户@目标地址
常见的应用场景见下图:
ssh_local

对应的命令以下:

# jump_target 服务器上的 3306 端口服务映射到本地 33306 `mysql -u root -p root -H localhost -P 33306`
## 1 是 2,3,5 路线中的加密通道,将本地 33306 的网络数据转发到 jump_target 的 3306 端口
ssh -C -N -g -L 33306:localhost:3306 jump_target
## 在 2,3 中搞了个加密通道,而后在跳板机上将本地 33306 的网络数据转发到 172.17.132.120 的 3306 端口
ssh -C -N -g -L 33306:172.17.132.120:3306 jumper

远程端口转发

由远程服务器的某个端口,转发到本地网络的服务器某个端口。说白了,就是将发送到远程端口的请求,转发到目标端口。格式以下:

ssh -R 远程网卡地址:远程端口:目标地址:目标端口 用户@目标地址
常见的应用场景有个专用术语叫内网穿透,结构以下图:
ssh_remote

# 将公网上的服务器 jump_target 的端口 33333 映射到本地的 22,这样就能够经过在 jump_target 上经过 SSH 来访问本地机器
ssh -f -N -g -R 33333:127.0.0.1:22 jump_target
TIPS:
公网上的服务器 jump_target 要设置 GatewayPorts yes,默认为 no。此外要映射的端口 33333 要能够访问。

动态转发

动态转发就是创建一个SSH加密的SOCKS 4/5代理通道。任何支持 SOCKS 4/5 协议的程序均可以使用这个加密的通道进行访问。格式以下:
ssh -D [本地地址:]本地端口号 远程用户@远程地址

# 将访问本地 55558 端口的请求都转发给 jump_target ,并让它去执行
ssh -C -N -g -T -D 127.0.0.1:55558 jump_target

平常使用问题

跳板机的配置

# 跳板机的配置
Host jump
  HostName  jumper.example.com
  Port      58422
  User      haojunyu
  IdentityFile ~/.ssh/dg_rsa
  AddKeysToAgent yes    # 将私钥添加到 agent 中
  UseKeychain yes       # 保存密码到 agent 中
# 目标机的配置
Host ws
  HostName  172.17.132.120
  Port      22
  User      haojunyu
  IdentityFile ~/.ssh/dg_rsa
  ProxyCommand ssh haojunyu@jump -W %h:%p 2>/dev/null
  ServerAliveInterval 10
  ServerAliveCountMax 3

内网任意服务访问

平常工做中常常会启不少服务在内网机器上,而后经过打洞(本地端口转发)来将本地的端口映射到内网机器上服务端口。
这样有个问题就是一个服务就得维持一个打洞命令 ssh -C -N -g -L 33306:172.17.132.120:3306 jumper
对应这样的问题,最好的解决方案是使用动态转发 ssh -C -N -g -T -D 127.0.0.1:55557 hb_jumper
本地经过 SwitchyOmega 或 proxifier 工具来将内网 IP 段 172.17.* 的请求转发到本地的 55557 端口。

git push 报权限不容许(公钥)

一般在服务器上执行 git push 时会报以下错误

具体报错信息:
Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights and the repository exists.

报错的缘由是当前机器上没有服务告诉 git 要使用哪一个私钥来进行 git 的操做。
对应的解决方法也比较多,推荐解法一和二:

  • 解法一:经过 ~/.ssh/config 指定(适用我的机器)

    Host github.com
        HostName  github.com
        User      haojunyu
        IdentityFile ~/.ssh/id_rsa
  • 解法二:配置仓库或全局的 core.sshCommand(指定仓库适用共享机器,全局适用我的机器.git版本高于2.3.0

    git config core.sshCommand "ssh -i ~/.ssh/id_rsa -F /dev/null"
  • 解法三:ssh-agent 临时受权(适用共享机器)

    eval `ssh-agent`
    ssh-add ~/.ssh/id_rsa

端口转发命令服务化

这个状况是但愿开机时就把端口转发开通,而且一直保持着。这就得介绍 linux 中经常使用的两种服务化的工具:Supervisor 和 Systemd。
前者是须要安装 Supervisor, 但工具比较轻量,使用也比较简单,后者虽然比较重,可是基本全部系统都自带。下面提供二者的配置方法:

  • Supervisor 的配置
[program:ssh-wifi_ol]
command=ssh -C -N -g -L 9789:127.0.0.1:9789 jump
stdout_logfile=/Users/haojunyu/.supervisord_log/ssh-wifi_ol.log
autostart=true
autorestart=true
startsecs=5
priority=1
stopasgroup=true
killasgroup=true
  • Systemd 的配置
# gfw service
[Unit]
Description=gfw
After=network.target
[Service]
Type=simple
User=hjy
ExecStart=ssh -C -N -g -T -D 127.0.0.1:55558 gfw
Restart=on-failure
[Install]
WantedBy=multi-user.target
TIPS:
把一些常常用的服务经过端口转发服务化,而一些临时性的服务经过命令来进行端口转发,也可使用同事编写的端口转发的 Python 程序来进行。

参考文献

  1. 什么是SSH?你应该用过吧
  2. 维基百科-SSH
  3. windows支持openssh
  4. 图解SSH原理
  5. SSH官方文档
  6. 全部配图
  7. 中间人攻击
  8. 了解ssh代理
  9. ssh远程登录中的钥匙指纹是什么以及如何比对
  10. ssh登录认证过程详解

若是该文章对您产生了帮助,或者您对技术文章感兴趣,能够关注微信公众号: 技术茶话会, 可以第一时间收到相关的技术文章,谢谢!
技术茶话会
本篇文章由一文多发平台ArtiPub自动发布

相关文章
相关标签/搜索