1.1 非对称加密基础知识
对称加密:加密和解密使用一样的算法,只要解密时提供与加密时一致的密码就可以完成解密。
很简单的加密方式,只要一把钥匙能够同样打开本地锁和服务器锁就可以建立SSH连接
**非对称加密:**通过公钥(public key)和私钥(private key)来加密、解密。公钥加密的内容可以使用私钥解密,私钥加密的内容可以使用公钥解密。一般使用公钥加密,私钥解密,但并非绝对如此,例如CA签署证书时就是使用自己的私钥加密。在接下来介绍的SSH服务中,虽然一直建议分发公钥,但也可以分发私钥。
//公钥和私钥均有加密内容,均可以相互解密
所以,如果A生成了(私钥A,公钥A),B生成了(私钥B,公钥B),那么A和B之间的非对称加密会话情形包括:
(1).A将自己的公钥A分发给B,B拿着公钥A将数据进行加密,并将加密的数据发送给A,A将使用自己的私钥A解密数据。
(2).A将自己的公钥A分发给B,并使用自己的私钥A加密数据,然后B使用公钥A解密数据。
(3).B将自己的公钥B分发给A,A拿着公钥B将数据进行加密,并将加密的数据发送给B,B将使用自己的私钥B解密数据。
(4).B将自己的公钥B分发给A,并使用自己的私钥B加密数据,然后A使用公钥B解密数据。
首先公钥和私钥是俗称的不对称加密方式,也就是非对称加密算法,是对以前的对称加密(使用用户名与密码)方式的提高。
【这里讲解一下为什么私钥不可分发】
私钥有两个作用,一个是解密加密,一个是辨别身份,如果分发私钥那么我们就无法建立唯一的信息传输通道,也就是拥有私钥的其他人也可以被系统认为是我然后盗取我的信息
私钥是完成交易的唯一不要条件,为了给其他人(网络中的其他节点)证明你拥有对应的私钥,完成了对应的交易,就需要将公钥发给大家,来证明你拥有对应的私钥。
因为私钥可生成公钥,但是公钥无法倒推私钥,所以这种方式即能证明交易成功,又能保证私钥的安全性。
简单理解就是:既然是加密,那肯定是不希望别人知道我的消息,所以只有我才能解密,所以可得出公钥负责加密,私钥负责解密;同理,既然是签名,那肯定是不希望有人冒充我发消息,只有我才能发布这个签名,所以可得出私钥负责签名,公钥负责验证。
虽然理论上支持4种情形,但在SSH的**身份验证阶段,SSH只支持服务端保留公钥,客户端保留私钥的方式,**所以方式只有两种:客户端生成密钥对,将公钥分发给服务端;服务端生成密钥对,将私钥分发给客户端。只不过出于安全性和便利性,一般都是客户端生成密钥对并分发公钥。后文将给出这两种分发方式的示例。
1.2 SSH概要
(1).SSH是传输层和应用层上的安全协议,它只能通过加密连接双方会话的方式来保证连接的安全性。当使用ssh连接成功后,将建立客户端和服务端之间的会话,该会话是被加密的,之后客户端和服务端的通信都将通过会话传输。
(2).SSH服务的守护进程为sshd,默认监听在22端口上。
(3).所有ssh客户端工具,包括ssh命令,scp,sftp,ssh-copy-id等命令都是借助于ssh连接来完成任务的。也就是说它们都连接服务端的22端口,只不过连接上之后将待执行的相关命令转换传送到远程主机上,由远程主机执行。
(4).ssh客户端命令(ssh、scp、sftp等)读取两个配置文件:全局配置文件/etc/ssh/ssh_config和用户配置文件~/.ssh/config。实际上命令行上也可以传递配置选项。它们生效的优先级是:命令行配置选项 > ~/.ssh/config > /etc/ssh/ssh_config。
(5).ssh涉及到两个验证:主机验证和用户身份验证。通过主机验证,再通过该主机上的用户验证,就能唯一确定该用户的身份。一个主机上可以有很多用户,所以每台主机的验证只需一次,但主机上每个用户都需要单独进行用户验证。
(6).ssh支持多种身份验证,最常用的是密码验证机制和公钥认证机制,其中公钥认证机制在某些场景实现双机互信时几乎是必须的。虽然常用上述两种认证机制,但认证时的顺序默认是gssapi-with-mic,hostbased,publickey,keyboard-interactive,password。注意其中的主机认证机制hostbased不是主机验证,由于主机认证用的非常少(它所读取的认证文件为/etc/hosts.equiv或/etc/shosts.equiv),所以网络上比较少见到它的相关介绍。总的来说,通过在ssh配置文件(注意不是sshd配置文件)中使用指令PreferredAuthentications改变认证顺序不失为一种验证的效率提升方式。
(7).ssh客户端其实有不少很强大的功能,如端口转发(隧道模式)、代理认证、连接共享(连接复用)等。
(8).ssh服务端配置文件为/etc/ssh/sshd_config,注意和客户端的全局配置文件/etc/ssh/ssh_config区分开来。
(9).很重要却几乎被人忽略的一点,
ssh登录时会请求分配一个伪终端
。但有些身份认证程序如sudo可以禁止这种类型的终端分配,导致ssh连接失败。例如使用ssh执行sudo命令时sudo就会验证是否要分配终端给ssh。**
1.3 SSH认证过程分析
假如从客户端A(172.16.10.5)连接到服务端B(172.16.10.6)上,将包括主机验证和用户身份验证两个过程,以RSA非对称加密算法为例。
[root@xuexi ~]# ssh 172.16.10.6
服务端B上首先启动了sshd服务程序,即开启了ssh服务,打开了22端口(默认)。
//此时服务端开启了sshd服务程序(ssh守护程序,开放了22端口)
1.3.1 主机验证过程
当客户端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是否有连接过客户端A,方法是分别读取AB中的Known_hosts文件,搜索是否都包含服务器B信息(host key)
询问是否保存host key的过程如下所示:
[root@xuexi ~]# ssh 172.16.10.6
The authenticity of host '172.16.10.6 (172.16.10.6)' can't be established.
RSA key fingerprint is f3:f8:e2:33:b4:b1:92:0d:5b:95:3b:97:d9:3a:f0:cf.
Are you sure you want to continue connecting (yes/no)? yes
或者windows端使用图形界面ssh客户端工具时:
在说明身份验证过程前,先看下known_hosts文件的格式。以~/.ssh/known_hosts为例。
[root@xuexi ~]# cat ~/.ssh/known_hosts
172.16.10.6 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC675dv1w+GDYViXxqlTspUHsQjargFPSnR9nEqCyUgm5/32jXAA3XTJ4LUGcDHBuQ3p3spW/eO5hAP9eeTv5HQzTSlykwsu9He9w3ee+TV0JjBFulfBR0weLE4ut0PurPMbthE7jIn7FVDoLqc6o64WvN8LXssPDr8WcwvARmwE7pYudmhnBIMPV/q8iLMKfquREbhdtGLzJRL9DrnO9NNKB/EeEC56GY2t76p9ThOB6ES6e/87co2HjswLGTWmPpiqY8K/LA0LbVvqRrQ05+vNoNIdEfk4MXRn/IhwAh6j46oGelMxeTaXYC+r2kVELV0EvYV/wMa8QHbFPSM6nLz
该文件中,每行一个host key,行首是主机名,它是搜索
host key时的索引
,主机名后的内容即是host key部分。以此文件为例,它表示客户端A曾经试图连接过172.16.10.6这个主机B,并保存了主机B的host key,下次连接主机B时,将搜索主机B的host key,并与172.16.10.6传送过来的host key做比较,如果能匹配上,则表示该host key确实是172.16.10.6当前使用的host key,如果不能匹配上,则表示172.16.10.6修改过host key,或者此文件中的host key被修改过。
//在客户端A中,整个一个是一个host key 第一行是服务器B名,这个是搜索服务器B信息(host key)的索引,后面全是host key的内容
那么主机B当前使用的host key保存在哪呢?在/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。
[root@xuexi ~]# cat /etc/ssh/ssh_host_rsa_key.pub # 在主机B上查看
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC675dv1w+GDYViXxqlTspUHsQjargFPSnR9nEqCyUgm5/32jXAA3XTJ4LUGcDHBuQ3p3spW/eO5hAP9eeTv5HQzTSlykwsu9He9w3ee+TV0JjBFulfBR0weLE4ut0PurPMbthE7jIn7FVDoLqc6o64WvN8LXssPDr8WcwvARmwE7pYudmhnBIMPV/q8iLMKfquREbhdtGLzJRL9DrnO9NNKB/EeEC56GY2t76p9ThOB6ES6e/87co2HjswLGTWmPpiqY8K/LA0LbVvqRrQ05+vNoNIdEfk4MXRn/IhwAh6j46oGelMxeTaXYC+r2kVELV0EvYV/wMa8QHbFPSM6nLz
发现/etc/ssh/ssh_host_rsa_key.pub文件内容和~/.ssh/known_hosts中该主机的host key部分完全一致,只不过~/.ssh/known_hosts中除了host key部分还多了一个主机名,这正是搜索主机时的索引。
//在服务器B中,整个也是一个服务器B信息的host key但是没有写明服务器B名,客户端A要保留是因为客户端可以借此为索引搜索区分不同的主机信息
综上所述,
在主机验证阶段,服务端持有的是私钥,客户端保存的是来自于服务端的公钥。注意,这和身份验证阶段密钥的持有方是相反的。
//在主机验证阶段是服务器主动发送服务器B信息 hostkey,客户端A为接收所以客户端只需要保存服务端传来的公钥去解密信息即可,
实际上,ssh并非直接比对host key,因为host key太长了,比对效率较低。所以ssh将host key转换成host key指纹,然后比对两边的host key指纹即可。指纹格式如下:
[root@xuexi ~]# ssh 172.16.10.6
The authenticity of host '172.16.10.6 (172.16.10.6)' can't be established.
RSA key fingerprint is f3:f8:e2:33:b4:b1:92:0d:5b:95:3b:97:d9:3a:f0:cf.
Are you sure you want to continue connecting (yes/no)? yes
host key的指纹可由ssh-kegen计算得出。例如,下面分别是主机A(172.16.10.5)保存的host key指纹,和主机B(172.16.10.6)当前使用的host key的指纹。可见它们是完全一样的。
[root@xuexi ~]# 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)
[root@xuexi ~]# 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还支持host key模糊比较,即将host key转换为图形化的指纹。这样,图形结果相差大的很容易就比较出来。之所以说是模糊比较,是因为对于非常近似的图形化指纹,ssh可能会误判。图形化指纹的生成方式如下:只需在上述命令上加一个”-v”选项进入详细模式即可。
[root@xuexi ~]# ssh-keygen -lv -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)
+--[ RSA 2048]----+
| |
| |
| . |
| o |
| S. . + |
| . +++ + . |
| B.+.= . |
| + B. +. |
| o.+. oE |
+-----------------+
更详细的主机认证过程是:先进行密钥交换(DH算法)生成session key(rfc文档中称之为shared secret),然后从文件中读取host key,并用host key对session key进行签名,然后对签名后的指纹进行判断。(In SSH, the key exchange is signed with the host key to provide host authentication.来源:https://tools.ietf.org/html/rfc4419)
过程如下图:
1.3.2 身份验证过程
主机验证通过后,将进入身份验证阶段。SSH支持多种身份验证机制,
它们的验证顺序如下:gssapi-with-mic,hostbased,publickey,keyboard-interactive,password
,但常见的是密码认证机制(password)和公钥认证机制(public key)。当公钥认证机制未通过时,再进行密码认证机制的验证。这些认证顺序可以通过ssh配置文件(注意,不是sshd的配置文件)中的指令PreferredAuthentications改变。
//这里说的是身份验证不同机制的顺序,但是常见的是password和public key公钥认证机制
如果使用公钥认证机制,
客户端A需要将自己生成的公钥(
/.ssh/id_rsa.pub)发送到服务端B的
/.ssh/authorized_keys文件中
。当进行公钥认证时,客户端将告诉服务端要使用哪个密钥对,并告诉服务端它已经访问过密钥对的私钥部分~/.ssh/id_rsa(客户端从自己的私钥中推导,或者从私钥同目录下读取公钥,
计算公钥指纹后发送给服务端
。所以有些版本的ssh不要求存在公钥文件,有些版本的ssh则要求私钥和公钥同时存在且在同目录下),然后服务端将检测密钥对的公钥部分,判断该客户端是否允许通过认证。如果认证不通过,则进入下一个认证机制,以密码认证机制为例。
当使用密码认证时,将提示输入要连接的远程用户的密码,输入正确则验证通过。
1.3.3 验证通过
当主机验证和身份验证都通过后,分两种情况:直接登录或执行ssh命令行中给定某个命令。如:
[root@xuexi ~]# ssh 172.16.10.6
[root@xuexi ~]# ssh 172.16.10.6 'echo "haha"'
(1).前者ssh命令行不带任何命令参数,表示使用远程主机上的某个用户(此处为root用户)登录到远程主机172.16.10.6上,所以远程主机会为ssh分配一个伪终端,并进入bash环境。
(2).后者ssh命令行带有命令参数,表示在远程主机上执行给定的命令【echo “haha”】。ssh命令行上的远程命令是通过fork ssh-agent得到的子进程来执行的,当命令执行完毕,子进程消逝,ssh也将退出,建立的会话和连接也都将关闭。(之所以要在这里明确说明远程命令的执行过程,是为了说明后文将介绍的ssh实现端口转发时的注意事项)
实际上,在ssh连接成功,登录或执行命令行中命令之前,可以指定要在远程执行的命令,这些命令放在~/.ssh/rc或/etc/ssh/rc文件中,也就是说,ssh连接建立之后做的第一件事是在远程主机上执行这两个文件中的命令。