SSH overview

请注意:本文编写于 ,其中某些信息可能已经失去时效性。

前言

SSH 是什么

Secure Shell(安全外壳协议,简称 SSH)是一种加密的网络传输协议,可以再不安全的网络中为网络服务提供安全的传输环境。

——维基百科

通过维基百科的说明可以看出 SSH 实际上指的是一种加密的网络传输协议,而我们经常用来登录远程主机的 ssh 命令实际上是某个软件对 SSH 这种协议的包装实现,其中最常见的开源实现方案是 OpenSSH(OpenBSD Secure Shell)

SSH 目前还是比较可靠的,利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题。通过 SSH 可以对所有传输的数据进行加密,也能够防止 DNS 欺骗和 IP 欺骗。SSH 的另一项优点是其传输的数据可以是经过压缩的,所以可以加快传输速度。

SSH 当下有两个版本,分别是 SSHv1 和 SSHv2,v2 是主流版本,v1 版本存在中间人攻击的安全风险。

基本流程

SSH 协议规定的通讯流程可以分解成几个主要阶段:

  1. SSH 协议版本协商阶段
  2. 密钥和算法协商阶段
  3. 客户端认证阶段
  4. 会话请求阶段
  5. 交互会话阶段

SSH 协议版本协商阶段

  1. 服务端打开服务端口(默认为 22),等待客户端连接;
  2. 客户端向服务端发起 TCP 连接,双方完成握手并建立连接;
  3. 服务端通过 TCP 连接向客户端发送一个包含 SSH 版本信息的报文;
  4. 客户端收到报文后对比报文给出的版本信息和自身版本信息:
    1. 报文版本低于自身版本但自身能够兼容,使用报文版本进行通信;
    2. 其他情况下使用自身版本进行通信;
  5. 客户端将确定好的版本号通过 TCP 连接发送给服务端,服务端判断是否支持:
    1. 支持,进入密钥和算法协商阶段;
    2. 不支持,无法完成 SSH 连接,中断此次请求;

密钥和算法协商阶段

  1. 服务端和客户端通过 TCP 连接分别发送算法协商报文给对端,报文中包含自己支持的公钥算法列表,加密算法列表,MAC 算法列表,压缩算法列表等;
  2. 与协议版本协商阶段类似,服务端和客户端根据自己和对端支持的算法来决定最终使用的算法;
  3. 服务端和客户端利用 Diffie-Hellman 密钥交换使用算法,主机密钥对等参数,生成共享密钥和会话 ID;
  4. 至此加密的 SSH 通信通道建立完成,在后续的通信过程中共享密钥用于两端对传输数据的加密和解密,会话 ID 用于认证过程。

Diffie-Hellman 算法

DH 算法可以在一个不安全的信道上建立安全连接,从而解决不安全信道上信息安全交互的问题。

假设 A 与 B 要在不安全信道上使用 DH 算法安全地交换信息,大致流程如下:

  1. A 与 B 经过协商选定一个质数 p 及其本原根 g;
  2. A 生成随机数 $a \in [1, p-1]$,计算 $Y_{A} = g^{a} mod p$
  3. B 生成随机数 $b \in [1, p-1]$,计算 $Y_{B} = g^{b} mod p$
  4. 双方交换各自生成的 $Y$;
  5. A 计算 $K = Y_{B}^{a} mod p$;
  6. B 计算 $K = Y_{A}^{b} mod p$;
  7. 经过上述过程,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 的大致实现流程:

  1. 客户端通过 TCP 连接发送报文通知服务端开始 DH 交换流程;
  2. 服务端将选择好的 p 和 g 发送给客户端(含义参考上一小节);
  3. 客户端接收到 p 和 g 后生成自己的 Y-客户端,并返回给服务端;
  4. 服务端接收到 Y-客户端后:
    1. 通过计算得到密钥 K;
    2. 使用 sha256 算法将一些已知信息加密为 H-服务端并用 rsa 为其签名;
    3. 将 rsa 的公钥,Y-服务端,rsa 签名后的 H-服务端发送给客户端;
  5. 客户端接收到服务端的返回值:
    1. 计算出相同的密钥 K;
    2. 同样使用 sha256 算法将相同信息加密为 H-客户端;
    3. 利用 rsa 服务端公钥得到 H-客户端签名与 H-服务端签名进行对比;
    4. 校验无误,返回特定报文表示密钥交换完成,以后数据都由此密钥进行加密。

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 协商过程的理解:

  1. A 利用 RSA 算法生成公钥 A 和私钥 A,B 同上生成公钥 B 和私钥 B;
  2. A 把公钥 A 发给 B;
  3. B 把公钥 B 发给 A;
  4. 后续通讯过程 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 算法是非对称加密算法,消耗资源多,运行效率低,不适合用于长连接下的数据交换加密。

如果想看此问题下的更多展开内容推荐 点击此链接查看第六篇参考文章 给出的回答。

客户端认证阶段

  1. 客户端向服务端发送认证请求;
  2. 服务端对客户端进行认证,如果认证失败则向客户端发送失败消息;
  3. 客户端可以选择再次认证知道达到认证次数上线(如果有设置的话)或认证成功位置。

常见的客户端认证方式有两种

  1. 密码认证:密码认证所用的账户密码一般与系统用户密码相同;
  2. 密钥认证:可接受公钥一般存放在用户目录下的 ~/.ssh/authorized_keys 中,注意需要 SSH 服务端拥有此文件的访问权限。

会话请求阶段

  1. 服务端等待客户端请求;
  2. 认证完成后,客户端向服务端发送会话请求;
  3. 服务端处理客户端请求:
    1. 完成后向客户端 SSH_SMSG_SUCCESS 报文,双方进入交互会话阶段;
    2. 如果请求未被成功处理,返回 SSH_SMSG_FAILURE 报文,表示请求处理失败或者不能识别客户端请求。

交互会话阶段

  1. 客户端将要执行的命令加密发送给服务端。
  2. 服务端收到后解密命令,执行后将结果加密返回客户端。
  3. 客户端将返回结果解密后显示到终端上。

OpenSSH

OpenSSH 是在 1999年 10月第一次在 OpenBSD 2.6 里出现,当初的项目是取代由 SSH Communications Security 所提供的 SSH 软件。

——维基百科

程序主要包括了几个部分:

  1. ssh:SSH 客户端实现
  2. scp, sftp:rcp的替代方案,将文件复制到其他电脑上
  3. sshd:SSH 服务端实现
  4. ssh-keygen:产生 RSA 或 ECDSA 密钥,用来认证
  5. ssh-agent, ssh-add:帮助用户不需要每次输入密钥或密码的工具
  6. 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 查看已经添加的私钥列表。

参考

  1. 松鼠尚学堂:SSH 工作原理
  2. 运行的风:Linux SSH建立连接过程分析
  3. wchrt:ssh秘钥交换详解与实现…
  4. 月半兄:Diffie–Hellman 密钥协商算法详解
  5. Soulike:Diffie-Hellman 密钥交换算法及其安全性
  6. 知乎车小胖回答:SSH 为什么要用到 DH(Diffie-Hellman Exchange)?
  7. 坤哥玩csdn:关于ssh-keygen命令的介绍与用法