• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • 在 centOS 上详解 SSH 服务、OpenSSH 软件

    SSH、OpenSSH

    历史上,网络主机之间的通信是不加密的,属于明文通信。这使得通信很不安全,一个典型的例子就是服务器登录。登录远程服务器的时候,需要将用户输入的密码传给服务器,如果这个过程是明文通信,就意味着传递过程中,线路经过的中间计算机都能看到密码,这是很可怕的。

    SSH 就是为了解决这个问题而诞生的,它能够加密计算机之间的通信,保证不被窃听或篡改。它还能对操作者进行认证(authentication)和授权(authorization)。明文的网络协议可以套用在它里面,从而实现加密。

    1995年,芬兰赫尔辛基工业大学的研究员 Tatu Ylönen 设计了 SSH 协议的第一个版本(现称为 SSH 1),同时写出了第一个实现(称为 SSH1)。允许其他人免费使用。

    SSH 可以替换 rlogin、TELNET、FTP 和 rsh 这些不安全的协议,所以大受欢迎。SSH 1 协议也变成 IETF 的标准文档。IETF 是国际互联网工程任务组(The Internet Engineering Task Force,简称 IETF)。

    1996年又提出了 SSH 2 协议(或者称为 SSH 2.0)。这个协议与 1.0 版不兼容,1998年推出了软件实现 SSH2。但是,SSH2 软件是一个专有软件,不能免费使用,而且 SSH1 的有些功能也没有提供。现在,SSH-2 有多种实现,既有免费的,也有收费的。


    1999年,OpenBSD 的开发人员决定写一个 SSH 2 协议的开源实现,这就是 OpenSSH 项目,成为最流行的 SSH 实现。目前,Linux 的所有发行版几乎都自带 OpenSSH。

    OpenSSH是取代由 SSH Communications Security 所提供的商用版本的开放源代码方案,是免费的。OpenSSH 是使用 SSH 协议进行远程登录的连接工具。它加密所有通信讯息以消除窃听、连接劫持和其他攻击。此外,OpenSSH 提供了大量的安全隧道功能、多种身份验证方法和复杂的配置选项。OpenSSH 自带的服务端openssh-server(sshd 服务)和客户端openssh-clients(ssh 命令)。OpenSSH 还自带有openssh-askpass,用于图形界面下输入口令。还自带有openssh-keycat

    OpenSSH 包含的组件如下:

    • ssh:OpenSSH 远程登录客户端,作为 rlogin 和 Telnet 的替代方案。
    • sshd:OpenSSH 守护进程。
    • scp:OpenSSH 安全文件复制,作为 rcp 的替代方案,将文件复制到其他电脑上。
    • sftp:OpenSSH 安全文件传输,类似于 scp。
    • ssh-keygen:OpenSSH 身份验证密钥实用程序,产生 RSA 或 ECDSA 密钥,用来认证用。
    • ssh-copy-id:OpenSSH 自动将公钥拷贝到远程服务器的/.ssh/authorized_keys文件。ssh-copy-id 是直接将公钥添加到/.ssh/authorized_keys文件的末尾。如果/.ssh/authorized_keys文件不存在,会自动创建该文件。
    • ssh-agent:OpenSSH 身份验证代理,用于帮助用户不需要每次都要输入密钥密码的工具。
    • ssh-add:向 OpenSSH 身份验证代理添加私钥身份,用于帮助用户不需要每次都要输入密钥密码的工具。
    • ssh-keyscan:从服务器收集 SSH 公钥,并记录公钥。


    SSL、OpenSSL

    SSL(Secure Sockets Layer,安全套接层)是一种国际标准的加密及身份认证通信协议,您用的浏览器就支持此协议。SSL 最初是由美国 Netscape 公司研究出来的,后来成为了 Internet 网上安全通讯与交易的标准。SSL 协议使用通讯双方的客户证书以及 CA 根证书,允许客户/服务器应用以一种不能被偷听的方式通讯,在通讯双方间建立起了一条安全的、可信任的通讯通道。它具备以下基本特征:信息保密性、信息完整性、相互鉴定。主要用于提高应用程序之间数据的安全系数。SSL 协议的整个概念可以被总结为:一个保证任何安装了安全套接字的客户和服务器间事务安全的协议,它涉及所有 TC/IP 应用程序。其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS 与 SSL 在传输层对网络连接进行加密。HTTPS 可以使用 TLS 或者 SSL 协议。

    OpenSSL是一个强大的安全套接字层密码库,Apache 使用它加密 HTTPS,OpenSSH 使用它加密 SSH。OpenSSL 是一个开源的软件库,使用包含了众多加解密算法,用于传输层安全性(TLS)和安全套接字层(SSL)协议的强大、商业级和功能齐全的工具包。主要库是以 C 语言所写成,实现了基本的加密功能,实现了 SSL 与 TLS 协议。OpenSSL 中包含了绝大多数密码算法,支持了国密算法 SM2、SM3、SM4。它还是一个多用途的、跨平台的密码工具。


    OpenSSH 与 OpenSSL

    • SSL:是通讯链路的附加层。可以包含很多协议。https、ftps等
    • SSH:只是加密的 shell,最初是用来替代 telnet 的。通过 port forward,也可以让其他协议通过 ssh 的隧道而起到加密的效果。
    • OpenSSL:一个C语言函数库,是对 SSL 协议的实现。
    • OpenSSH:是对 SSH 协议的实现。OpenSSH 编译以及运行都依赖于 OpenSSL。


    SSH2 连接过程

    SSH 协议,有两个不兼容的版本:SSH1、SSH2。现在都流行使用 SSH2 协议。SSH1 使用非对称加密算法(RSA)来完成对称加密算法的密钥交换,最后使用对称加密算法实现数据安全传输。SSH2 用数字签名算法(DSA)和Diffie-Hellman(DH)算法代替 RSA 来完成对称密钥的交换,用消息证实代码(HMAC)来代替CRC,再通过客户端的公钥识别身份。SSH2 避免了 RSA 的专利问题,并修补了CRC 的缺陷。同时 SSH2 增加了 AES 和 Twofish 等对称加密算法。

    SSH2 连接过程大体上可分为两个部分:协商会话加密和身份认证。

    协商会话加密

    SSH2 先使用 Diffie-Hellman(DH)算法完成对称加密算法的密钥交换

    SSH2 是建立在 TCP/IP 协议之上的应用层协议,所以是在经过 TCP三次握手后才开始进行 SSH 连接,当然这个过程也是依赖于 TCP/IP。当客户端建立 TCP 连接时,服务器会使用它支持的协议版本进行响应。如果客户端可以匹配其中一个可接受的协议版本,则继续连接。服务器还提供其公共主机密钥,客户端可以使用该密钥来检查这是否是预期的主机。此时,双方使用称为 Diffie-Hellman 算法的版本协商会话密钥。该算法(及其变体)使得每一方能够将他们自己的私有数据与来自另一系统的公共数据组合以得到相同的秘密会话密钥。会话密钥将用于加密整个会话。用于此部分过程的公钥和私钥对与用于向服务器验证客户端的 SSH 密钥完全分开。

    经典 Diffie-Hellman 密钥协商的基本流程为:

    1. 双方都同意一个大的素数,它将作为种子值。
    2. 双方就密文生成器(通常为 AES)达成一致,该生成器将用于以预定义的方式操纵值。
    3. 双方各自独立地都提出另一个素数,该号码对另一方保密。此编号用作此交互的私钥(与用于身份验证的专用 SSH 密钥不同)。
    4. 生成的私钥,密文生成器和共享素数用于生成从私钥导出但可以与另一方共享的公钥。
    5. 然后两个参与者交换他们生成的公钥。
    6. 接收实体使用他们自己的私钥,另一方的公钥和原始共享素数来计算共享密钥。虽然这是由各方独立计算的,但使用相反的私钥和公钥,它将产生相同的共享密钥。
    7. 然后使用共享密钥加密随后的所有通信。

    用于其余连接的共享秘密加密称为二进制数据包协议。上述过程允许每一方平等地参与生成共享秘密,这不允许一端控制秘密。它还完成了生成相同的共享秘密的任务,而无需通过不安全的通道发送该信息。

    生成的秘密是对称密钥,这意味着用于加密消息的相同密钥可用于在另一侧解密它。这样做的目的是将所有进一步的通信包装在一个无法被外人破译的加密隧道中。

    建立会话加密后,用户身份验证阶段开始。


    身份认证

    假如从客户端 A(172.16.10.5),连接到服务端 B(172.16.10.6),服务端 B 上,开启了 ssh 服务,并且运行了 sshd 进程,即开启 SSH 默认端口 22。将包括主机验证和用户身份验证两个过程。

    共享密钥确定后,接下来的通信都使用共享密钥进行加密和解密,因此是安全的,但我们还没有确认双方的身份。客户端识别服务端是通过人工进行确认的。我们在第一次连接服务器时,都会弹出一个警告,让用户确定是否进行连接。警告的内容包含了服务器的公钥指纹。

    ssh 172.16.10.6
    
    # 显示如下:
    The authenticity of host '172.16.10.6 (172.16.10.6)' can't be established.
    ECDSA key fingerprint is SHA256:TER0dEslggzS/BROmiE/s70WqcYy6bk52fs+MLTIptM.
    Are you sure you want to continue connecting (yes/no/[fingerprint])?
    

    第一:主机验证过程

    当客户端 A 要连接 B 时,首先将进行主机验证过程,即判断主机 B 是否是否曾经连接过。

    判断的方法是读取~/.ssh/known_hosts文件和/etc/ssh/known_hosts文件,搜索是否有 172.16.10.6 的主机信息(主机信息称为 host key,表示主机身份标识)。如果没有搜索到对应该地址的 host key,则询问是否保存主机 B 发送过来的 host key,如果搜索到了该地址的 host key,则将此 host key和主机 B 发送过来的 host key 做比对,如果完全相同,则表示主机 A 曾经保存过主机 B 的 host key,无需再保存,直接进入下一个过程——身份验证,如果不完全相同,则提示是否保存主机 B 当前使用的 host key。

    主机 B 当前使用的 host key,在本机客户端中,被存在~/.ssh/known_hosts,在远程服务端中,被保存在/etc/ssh/ssh_host*中,这些文件是服务端(此处即主机 B)的 sshd 服务程序启动时重建的。以 rsa 算法为例,则保存在/etc/ssh/ssh_host_rsa_key/etc/ssh/ssh_host_rsa_key.pub中,其中公钥文件/etc/ssh/ssh_host_rsa_key.pub中保存的就是 host key。

    远程服务端,/etc/ssh/ssh_host_rsa_key.pub文件内容和本机客户端~/.ssh/known_hosts中该主机的 host key 部分完全一致,本机客户端 host key 部分还多了一个主机名,这正是搜索主机时的索引。

    综上所述,在主机验证阶段,服务端持有的是私钥,客户端保存的是来自于服务端的公钥。


    实际上,ssh 并非直接比对 host key,因为 host key 太长了,比对效率较低。所以 ssh 将 host key 转换成 host key 指纹,然后比对两边的 host key 指纹即可。host key 的指纹可由ssh-kegen计算得出。例如,下面分别是主机A(172.16.10.5)保存的host key指纹,和主机B(172.16.10.6)当前使用的host key的指纹。可见它们是完全一样的。

    # 查看客户端
    ssh-keygen -l -f ~/.ssh/known_hosts
    2048 f3:f8:e2:33:b4:b1:92:0d:5b:95:3b:97:d9:3a:f0:cf 172.16.10.6 (RSA)
    
    # 查看服务端
    ssh-keygen -l -f /etc/ssh/ssh_host_rsa_key
    2048 f3:f8:e2:33:b4:b1:92:0d:5b:95:3b:97:d9:3a:f0:cf  (RSA)。
    


    第二:身份验证过程

    主机验证通过后,将进入身份验证阶段。SSH 支持多种身份验证机制,它们的验证顺序如下:publickey,gssapi-keyex,gssapi-with-mic,password,但常见的是密码认证机制(password)和公钥认证机制(public key)。当公钥认证机制未通过时,再进行密码认证机制的验证。

    如果使用公钥认证机制,客户端 A 需要将自己生成的公钥(~/.ssh/id_rsa.pub)写入到服务端 B 的~/.ssh/authorized_keys文件中。进行公钥认证流程:

    1. 客户端将公钥 ID 发送给服务端。
    2. 服务端在对应用户的~/.ssh/authorized_keys中搜索与之匹配的公钥。
    3. 如果找到匹配的公钥,服务端将会生成一个随机数,并使用该公钥加密该随机数,得到加密随机数。
    4. 服务端将加密随机数发送给客户端。
    5. 客户端收到加密随机数后,如果其持有对应的私钥,那么它就可以使用私钥解密,从而得到随机数。
    6. 客户端将随机数和共享密钥组合进行加密,并将加密值发送给服务端。
    7. 服务端用同样的方式加密,并与客户端发送的加密值进行比较。如果相等,则身份认证通过。

    当使用密码认证时,将提示输入要连接的远程用户的密码,输入正确则验证通过。客户端将用户名和密码用共享密钥加密后发给服务端,服务端再使用共享密钥解密,取得用户名和密码,再以此验证用户信息。


    第三:验证通过

    当主机验证和身份验证都通过后,分两种情况:直接登录或执行 ssh 命令行中给定某个命令。如:

    ssh 172.16.10.6 
    ssh 172.16.10.6  'echo "haha"'
    
    • 前者 ssh 命令行不带任何命令参数,表示使用远程主机上的某个用户(此处为 root 用户)登录到远程主机 172.16.10.6 上,所以远程主机会为 ssh 分配一个伪终端,并进入 bash 环境。
    • 后者 ssh 命令行带有命令参数,表示在远程主机上执行给定的命令echo "haha"。ssh 命令行上的远程命令是通过 fork ssh-agent 得到的子进程来执行的,当命令执行完毕,子进程消逝,ssh 也将退出,建立的会话和连接也都将关闭。

    实际上,在 ssh 连接成功,登录或执行命令行中命令之前,可以指定要在远程执行的命令,这些命令放在~/.ssh/rc/etc/ssh/rc文件中,也就是说,ssh 连接建立之后做的第一件事是在远程主机上执行这两个文件中的命令。