请注意:本文编写于 2021-10-22,其中某些信息可能已经失去时效性。
前言
SSH 是什么
Secure Shell(安全外壳协议,简称 SSH)是一种加密的网络传输协议,可以再不安全的网络中为网络服务提供安全的传输环境。 ——维基百科
通过维基百科的说明可以看出 SSH 实际上指的是一种加密的网络传输协议,而我们经常用来登录远程主机的 ssh 命令实际上是某个软件对 SSH 这种协议的包装实现,其中最常见的开源实现方案是 OpenSSH(OpenBSD Secure Shell)。
SSH 目前还是比较可靠的,利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题。通过 SSH 可以对所有传输的数据进行加密,也能够防止 DNS 欺骗和 IP 欺骗。SSH 的另一项优点是其传输的数据可以是经过压缩的,所以可以加快传输速度。
SSH 当下有两个版本,分别是 SSHv1 和 SSHv2,v2 是主流版本,v1 版本存在中间人攻击的安全风险。
基本流程
SSH 协议规定的通讯流程可以分解成几个主要阶段:
- SSH 协议版本协商阶段
- 密钥和算法协商阶段
- 客户端认证阶段
- 会话请求阶段
- 交互会话阶段
SSH 协议版本协商阶段
- 服务端打开服务端口(默认为 22),等待客户端连接;
- 客户端向服务端发起 TCP 连接,双方完成握手并建立连接;
- 服务端通过 TCP 连接向客户端发送一个包含 SSH 版本信息的报文;
- 客户端收到报文后对比报文给出的版本信息和自身版本信息:
- 报文版本低于自身版本但自身能够兼容,使用报文版本进行通信;
- 其他情况下使用自身版本进行通信;
- 客户端将确定好的版本号通过 TCP 连接发送给服务端,服务端判断是否支持:
- 支持,进入密钥和算法协商阶段;
- 不支持,无法完成 SSH 连接,中断此次请求;
密钥和算法协商阶段
- 服务端和客户端通过 TCP 连接分别发送算法协商报文给对端,报文中包含自己支持的公钥算法列表,加密算法列表,MAC 算法列表,压缩算法列表等;
- 与协议版本协商阶段类似,服务端和客户端根据自己和对端支持的算法来决定最终使用的算法;
- 服务端和客户端利用 Diffie-Hellman 密钥交换使用算法,主机密钥对等参数,生成共享密钥和会话 ID;
- 至此加密的 SSH 通信通道建立完成,在后续的通信过程中共享密钥用于两端对传输数据的加密和解密,会话 ID 用于认证过程。
Diffie-Hellman 算法
DH 算法可以在一个不安全的信道上建立安全连接,从而解决不安全信道上信息安全交互的问题。
假设 A 与 B 要在不安全信道上使用 DH 算法安全地交换信息,大致流程如下:
- A 与 B 经过协商选定一个质数 p 及其本原根 g;
- A 生成随机数 a in [1, p-1],计算 Y_{A} = g^{a} mod p
- B 生成随机数 b in [1, p-1],计算 Y_{B} = g^{b} mod p
- 双方交换各自生成的 Y;
- A 计算 K = Y_{B}^{a} mod p;
- B 计算 K = Y_{A}^{b} mod p;
- 经过上述过程,A 和 B 都得到了安全的共享密钥 K。
注 1:流程中的数理知识这里不做普及,因为博主本身也不是很懂; 注 2:这里给出的仅仅是一个粗略的原理解释,并非最佳实现过程;
算法成立的原因:再此方法中公开数据有 p,g,Y_{A},Y_{B},若想要通过公开数据计算 K,则需要求取 Y_{A} = g^{a} mod p mid Y_{B} = g^{b} mod p 中的 a 或 b,求解此类问题一般使用穷举法,时间复杂度为 O(p),只要 p 足够大就能够保证此方法目前可以达到计算机安全的要求。
SSH 如何使用 Diffie-Hellman 算法
博主技术有限,没筛选出这一流程的 TCP 包,因此参考 第三篇参考文章 给出 diffie-hellman-group-exchange-sha256 的大致实现流程:
- 客户端通过 TCP 连接发送报文通知服务端开始 DH 交换流程;
- 服务端将选择好的 p 和 g 发送给客户端(含义参考上一小节);
- 客户端接收到 p 和 g 后生成自己的 Y-客户端,并返回给服务端;
- 服务端接收到 Y-客户端后:
- 通过计算得到密钥 K;
- 使用 sha256 算法将一些已知信息加密为 H-服务端并用 rsa 为其签名;
- 将 rsa 的公钥,Y-服务端,rsa 签名后的 H-服务端发送给客户端;
- 客户端接收到服务端的返回值:
- 计算出相同的密钥 K;
- 同样使用 sha256 算法将相同信息加密为 H-客户端;
- 利用 rsa 服务端公钥得到 H-客户端签名与 H-服务端签名进行对比;
- 校验无误,返回特定报文表示密钥交换完成,以后数据都由此密钥进行加密。
H 的计算方法:H = hash(VC parallel VS parallel IC parallel IS parallel KS parallel YC parallel YS parallel K)
类型 | 名称 | 意义 |
---|---|---|
string | VC | 客户端的初始报文 |
string | VS | 服务端的初始报文 |
string | IC | 客户端 SSH_MSG_KEX_INIY 的有效载荷 |
string | IS | 服务端 SSH_MSG_KEX_INIT 的有效载荷 |
string | KS | 服务端主机密钥(host key 一般是 RSA 公钥) |
string | YC | Y-客户端 |
string | YS | Y-服务端 |
string | K | 通过 DH 产生的共享密钥 |
以上内容按顺序进行拼接,不夹杂或尾随多余字符,拼接后的字符串进行 sha256 计算出结果就是 H 即 session_id。只有会话第一次密钥交换生成的 H 是 session_id,后面再进行密钥交换时,session_id 不会改变。
后续通信一般是采用 AES 算法进行加密,密钥计算方法:hash(K parallel H parallel W parallel session-id),其中 W 代指单个大写的 ASCII 字母,不同的加密秘钥使用不同的字符来计算。
字母 | 类型 |
---|---|
A | 客户端到服务端的初始 IV |
B | 服务端到客户端的初始 IV |
C | 客户端到服务端的加密秘钥 |
D | 服务端到客户端的加密秘钥 |
E | 客户端到服务端的完整性秘钥 |
F | 服务端到客户端的完整性秘钥 |
经过计算得到字符串 RE,如果我们想要的秘钥长度比 RE 长,则在 RE 后面继续加上一个值:hash(K parallel H parallel RE) 成为一个加长的 RE。如果还不够,则继续使用同样方法进行累加。
注 1:关于秘钥计算公式中 H 和 session-id 同时出现博主表示存疑,但是没找到更多资料所以暂时先这么写了,如果具体实现和给出内容有出入的话希望您能不吝赐教,第一时间联系博主进行修改。
延伸:SSH 为什么要使用 Diffie-Hellman 算法
我对 SSH 协商过程的理解:
- A 利用 RSA 算法生成公钥 A 和私钥 A,B 同上生成公钥 B 和私钥 B;
- A 把公钥 A 发给 B;
- B 把公钥 B 发给 A;
- 后续通讯过程 A 用公钥 B 加密内容发给 B;B 用公钥 A 加密内容发给 A。
我的疑惑是: 看很多资料在解释Linux下两台主机ssh通信协商时会提到DH(diffie-hellman),我知道DH是密钥交换算法,可以使通信双方安全地产生一个公共密钥(对称密钥)。但是通过上述> 上述协商过程,A 和 B 不是已经可以利用 RSA 算法产生的公钥和私钥进行加密通信了吗,那为什么还需要 DH 算法呢? 难道上述过程之后还要用 DH 算法再生成一个公共密钥? ——知乎问题:SSH为什么要用到DH(Diffie-Hellman Exchange)?
首先要指出的是问题提出者所理解的 SSH 协商过程是错误的。
至于为什么不直接用 RSA 算法进行加密通信其实和 HTTS 差不多一个道理:RSA 算法是非对称加密算法,消耗资源多,运行效率低,不适合用于长连接下的数据交换加密。
如果想看此问题下的更多展开内容推荐 点击此链接查看第六篇参考文章 给出的回答。
客户端认证阶段
- 客户端向服务端发送认证请求;
- 服务端对客户端进行认证,如果认证失败则向客户端发送失败消息;
- 客户端可以选择再次认证知道达到认证次数上线(如果有设置的话)或认证成功位置。
常见的客户端认证方式有两种
- 密码认证:密码认证所用的账户密码一般与系统用户密码相同;
- 密钥认证:可接受公钥一般存放在用户目录下的
~/.ssh/authorized_keys
中,注意需要 SSH 服务端拥有此文件的访问权限。
会话请求阶段
- 服务端等待客户端请求;
- 认证完成后,客户端向服务端发送会话请求;
- 服务端处理客户端请求:
- 完成后向客户端 SSH_SMSG_SUCCESS 报文,双方进入交互会话阶段;
- 如果请求未被成功处理,返回 SSH_SMSG_FAILURE 报文,表示请求处理失败或者不能识别客户端请求。
交互会话阶段
- 客户端将要执行的命令加密发送给服务端。
- 服务端收到后解密命令,执行后将结果加密返回客户端。
- 客户端将返回结果解密后显示到终端上。
OpenSSH
OpenSSH 是在 1999年 10月第一次在 OpenBSD 2.6 里出现,当初的项目是取代由 SSH Communications Security 所提供的 SSH 软件。 ——维基百科
程序主要包括了几个部分:
- ssh:SSH 客户端实现
- scp, sftp:rcp的替代方案,将文件复制到其他电脑上
- sshd:SSH 服务端实现
- ssh-keygen:产生 RSA 或 ECDSA 密钥,用来认证
- ssh-agent, ssh-add:帮助用户不需要每次输入密钥或密码的工具
- ssh-keysacn:收集大量主机的 ssh 主机公钥
ssh-keygen
常用选项:
选项 | 含义 | 作用 |
---|---|---|
-t | type | 指定要生成的密钥类型 |
-C | comment | 提供一个注释 |
-b | bits | 指定要生成的密钥长度(单位:bit) |
-f | filename | 指定生成的密钥文件名 |
ssh-agent 与 ssh-add
ssh-agent 是 OpenSSH 开发的用户提供 ssh 代理的工具,它可以为其他需要使用 ssh key 的程序提供代理。
ssh-add 是用来配合 ssh-agent 的,使用此工具可以向 ssh-agent 中添加私钥。
可以用过 ssh-add -l
查看已经添加的私钥列表。
参考
- 松鼠尚学堂:SSH 工作原理
- 运行的风:Linux SSH建立连接过程分析
- wchrt:ssh秘钥交换详解与实现…
- 月半兄:Diffie–Hellman 密钥协商算法详解
- Soulike:Diffie-Hellman 密钥交换算法及其安全性
- 知乎车小胖回答:SSH 为什么要用到 DH(Diffie-Hellman Exchange)?
- 坤哥玩csdn:关于ssh-keygen命令的介绍与用法