深度了解ssh原理

分享于:2019-01-31 09:50:26

SSH是什么?


熟悉Linux的人肯定都知道SSH(Secure Shell)。SSH是一种用于安全访问远程服务器的网络协议。是建立在应用层和传输层基础上的安全协议,主要用于计算机之间的加密登录与数据传输。 它将客户端与服务端之间的消息通过加密保护起来,这样就无法被窃取或篡改了。


在Linux中,可使用命令远程登录另外一Linux服务器:

ssh -p port user@host

表示要以user这个用户的身份登录host这台网络机器。也可以省略前面的user,这样来用ssh host,表示以当前本地登录的用户名登录host这台网络机器。


早期,人们主要是通过telnet协议进行计算机之间的登录操作,但是它有一个很严重的安全隐患就是“数据是明文传输的”,登录时传输的包括用户名和密码在内的所有信息都有可能会被恶意拦截而暴露。而SSH则是将登录信息全部加密后进行传输的,因此使用SSH进行登录是安全的,即使数据在传输过程中被截获,里面的密码已经被加密而不会泄露。


现在SSH作为互联网安全的一个基本解决方案,已经在全世界获得推广,且目前已经成为Linux系统的标准配置。需要说明的是,SSH只是一种协议,它有多种软件实现,既有商业的,也有开源的。OpenSSH是当前使用最为广泛的一个SSH协议的开源实现。


我们都了解过https了,其实http与telnet一样属于明文传输,不安全,不宜传输敏感信息;https与ssh一样属于加密传输,安全有保障。还有FTP与sftp,sftp是安全FTP文件传输协议。


为了理解SSH,大家必须深入了解两个重要概念:对称加密非对称加密


细聊数据加密算法:对称加密和非对称加密

https原理


SSH工作原理


其实SSH是充分利用了非对称加密 、对称加密 和 单向加密 来实现数据安全登录的。在使用SSH进行通信时,通信过程分为以下几个步骤:


1)生成会话秘钥: 这个会话秘钥,不是非对称加密中的公钥或私钥,而是通过密钥协商技术生成的秘钥(用于对称加密)。这个秘钥会被服务端的公钥加密后传输,用于后续所有(对称加密方式)加密通信。这个过程与https的颇具相似。https原理


2)用户身份认证(登录):使用ssh协议远程登录计算机。计算机的登录不同于web https请求,https也是加密,但https不对用户做身份验证(https请求,身份验证属于应用程序逻辑中的功能,和https本身没有关系)。但ssh不一样,某用户登录一Linux服务器,必须做好身份验证。①通过账号密码认证②免输密码基于公钥认证。(下面再做详细介绍),不做好身份验证,是个人都做服务器管理员了,服务器还有什么安全可言。


这个认证的过程中传输的所有数据都是通过上一步生成的秘钥加密过的。


3)数据加密通信:身份验证通过后,后面就是正常的数据交流了。所有通信数据基于第1步生成的密钥进行数据加密传输(对称加密)。


大体流程我们已了解,下面就展开具体细节探讨:


预防中间人攻击


细聊数据加密算法:对称加密和非对称加密》中有介绍,目标服务端的公钥有可能会被伪造,这就是中间人攻击(Man-in-the-middle attack)。


文章中也提到过,验证公钥的合法性有两种方式:

1)验证公钥的官方发布的公钥数据指纹。

2)通过权威的结构进行验证


SSH主要用于机器之间的安全登录,不像https协议,SSH协议的公钥是没有证书中心(CA)公证的。因此通常不会通过权威的机构去签发证书,它主要是通过验证数据指纹的方式来验证公钥的合法性的。


主机A向主机B发送连接请求,公钥的合法性验证时间为:主机A接收到主机B发送的公钥之后,主机A向主机B协商产生会话秘钥之前。具体的工作流程如下:


1)假设主机A也是Linux系统,主机A会去当前用户家目录下的.ssh/known_hosts文件中查找是否存在主机B的公钥,如果不存在,表示主机A是第一次与主机B进行通信,那么主机A会计算出该公钥的数据指纹并要求用户对该指纹进行合法性确认。就是我们经常看到的的这样子:


1.png


如果主机A是Windows,(SecureCRT)公钥的位置有可能在C:\Users\Administrator\AppData\Roaming\VanDyke\Config\KnownHosts

1.png

1.png


所谓"公钥指纹",【就是上图的主机密钥指纹】,是指公钥长度较长(这里采用RSA算法,长达1024位),很难比对,所以对其进行MD5计算,将它变成一个128位的指纹。


2)用户需要把目标主机(主机B)管理员公布的公钥的数据指纹主机A计算得到的数据指纹进行比对,如果一致,则说明该公钥是合法的;如果不一致则说明不合法;


很自然的一个问题就是,用户怎么知道远程主机的公钥指纹应该是多少?回答是:没有好的办法,远程主机必须对外贴出公钥指纹(比如在自己的网站上),以便用户自行核对


3)用户如果确认该公钥是合法的,则输入yes表示继续后面的连接,主机A则会把这个公钥的内容保存到当前用户家目录下的.ssh/known_hosts文件中,然后提示用户输入密码,如下图所示:


1.png


下次再登录,执行到步骤1时,主机A发现该公钥已经在.ssh/known_hosts文件中存在了,就不用要求再次确认了,而是会直接提示输出密码:


1.png


Windows中也是这样的。比如SecureCRT第一次会提示,第二次不提示。而SecureCRT还有自动保存密码的功能,所以第二次连密码都不用输入。


当然这一步,不仅验证公钥有没有存在,还会验证主机B发来的公钥与已存在的公钥是否一样。不一样会发出警告!需要重新确认。例如SecureCRT:

1.png


4)至此,主机B的身份合法性验证就结束了。


Linux系统中,每个用户都有自己的kown_hosts文件,它们是相互独立的。我们也可以为所有用户保存一份公共的可信赖的远程主机的公钥,这个文件通常是/etc/ssh/ssh_known_hosts。


新的问题:假如之前我们通过ssh登录过的一台机器的IP被绑定到其他机器上了会出现什么情况?


当机器A接收到机器B的公钥指纹时,发现knowns_hosts文件中虽然有机器B的公钥,但是计算得出的公钥指纹与机器B发送过来的公钥指纹不一致。这肯定是不一致的,因为每台机器的密钥对都是随机生成的,几乎不可能出现重复。因此,我们会看到如下提示信息:


1.png


上面的大概意思是,主机A发现主机B的公钥指纹对不上了,怀疑我们正在遭受中间人攻击(即有人在冒充主机B),并且密码验证方式和键盘交互验证方式都被禁止使用了。其实,我们自己知道是因为IP被绑定到其他机器上引起的这个问题,所以我们如果想继续登录新的主机B,只需要在.ssh/known_hosts文件中把原来保存的主机B的公钥删掉就可以了


又有一个问题,Linux主机对外的密钥在哪?


每台Linux机器都有自己的(非对称加密)密钥对儿(通常放在/etc/ssh目录下),这个密钥对儿跟具体的用户无关。


Linux服务器上的对外公钥内容

1.png


楠神本地(SecureCRT)获取到的公钥

1.png


可以看出来是一样的。


主机A(使用SecureCRT)连接主机B,SecureCRT只会在本地生成一个主机B的公钥文件,就算分别使用了nanshen1、nanshen2两个用户登录了主机B,也只会生成一个主机B的公钥文件。


生成会话秘钥的算法:


还是主机A向主机B发送连接请求,当主机A拿到主机B的公钥,它俩是怎么协商出来的会话秘钥(对称加密的密钥)。


实际上,已经存在一种专门用于秘钥交换的算法--Diffie-Hellman加密算法。该加密算法本身仅限于秘钥的交换用途,被许多商用产品用作秘钥交换技术。这种秘钥交换技术的目的在于使得两个用户安全的交换一个密钥,以便用于之后的数据对称加密。也就是说,通信双方可以通过这个技术,动态的协商生成一个用于对称加密的密钥,而不用管理很多静态的密钥,这样就解决了密钥的管理问题。


密钥协商是通过Diffie - Hellman算法来实现的。具体过程是:


1)服务端和客户端共同选定一个大素数,叫做种子值;

2)服务端和客户端各自独立地选择另外一个只有自己才知道的素数;

3)双方使用相同的加密算法(如AES),由种子值和各自的私有素数生成一个密钥值,并将这个值发送给对方;

4)在收到密钥值后,服务端和客户端根据种子值和自己的私有素数,计算出一个最终的密钥。这一步由双方分别独立进行,但是得到的结果应该是相同的。

5)双方使用上一步得到的结果作为会话秘钥来加密和解密通信内容。


基于密码的认证


主机A向主机B发送连接请求,会话秘钥生成完毕,下一步开始做身份认证。意思是主机A的当前用户怎么证明你有权利可以远程ssh连接主机B?通用的是,使用账号密码通过身份认证。其工作流程:


1)在主机A上向主机B发送连接请求;

2)主机B在与用户建立连接后,把自己的公钥发送给主机A;

3)验证公钥无误,主机A通过密钥协商技术产生一个随机密钥,然后使用主机B的公钥对这个随机密钥进行加密后发送给主机B;

4) 主机B接收到主机A发送过来的密文形式的密钥后,通过自己的私钥进行解密,得到对称加密使用的密钥明文;至此,会话秘钥已经生成完毕了;

5)主机A通过生成的会话秘钥对账号和密码等信息进行加密然后发送给主机B;

6)主机B接收到加密信息后,使用会话秘钥进行解密,从而得到明文的账号和密码进行账号验证;

7)主机B在验证账号和密码后通知主机A是否登录成功;


基于公钥的认证(实现免密码登录)

使用密码登录,每次都必须输入密码,浪费时间,而且非常麻烦。尤其是密码超级复杂,维护的服务器又比较多的情况下。好在SSH还提供了公钥(public key)登录,可以省去输入密码的步骤。


优点:


SSH免密钥登录是通过公钥认证的,用户登录时只需要提供用户名,而不需要输入密码。其实其优点不止这一个:


1)使用账号和密码进行登录时,由于用户无法设置空密码,因此每次登录都要输入密码。而且即使系统允许给用户设置空密码,也是十分危险的行为。而公钥认证允许用户给私钥设置空密码,同时还能保证安全性。


2)使用账号和密码进行登录时密码容易被人看到,且密码也容易被猜到;而公钥认证所使用的密钥不用手动输入,而且内容很长,因此安全性比较高。


3)使用账号和密码进行登录时,服务器上的一个账号如果想给多个人同时使用,机器密码维护工作会变得很繁琐,因为他们所有人都需要知道密码是什么,当修改密码也要通知他们每个人。而使用公钥认证只需要把它们的公钥保存在服务器上,如果要取消某个人的操作权限,只需要把这个人的公钥删掉,而不需要修改服务器密码。


原理:


登录的过程就是服务端对客户端进行“身份验证”的过程,前面是通过账号和密码来验证用户身份,因为密码应该只有该账号的拥有者才知道。而我们知道非对称加密算法中,用公钥加密的数据只能由与其配对的私钥才能解密,而私钥只有用户自己才有。那么,我们是否可以通过这种方式来验证用户身份呢?实际上SSH免密钥登录就是这样的原理。


比如,我们想在主机A(假设是Linux系统)上以root用户以SSH免密钥的方式登录主机B,登录验证过程是这样的:


1)主机A与主机B协商产生会话秘钥(用于对称加密);

2)主机A会向主机B发送一个登录请求(如:root@192.168.1.2),发送的信息包括用户名rootroot的公钥指纹,且所有信息都是通过会话秘钥加密过的。

3)主机B通过会话秘钥解密主机A发送的数据得到请求登录的用户名rootroot的公钥指纹,然后读取root用户家目录下的所有公钥数据(/root/.ssh/autorized_keys文件中),并分别通过单向加密算法(如md5)获取各公钥的数据指纹与主机A发送过来的公钥指纹做对比,从而找到主机A上的root用户的公钥;(这一步,主机A事先在主机B的/root/.ssh/autorized_keys文件里存放了root用户的公钥)

4)主机B使用找到的root用户的公钥一个随机数进行加密发送给主机A;

5)主机A使用root用户的私钥对主机B发送的随机数密文进行解密,从而获得这个随机数。主机A把随机数会话秘钥计算出md5值,发回给主机B。

6)主机B同样把原始的随机数与会话秘钥计算md5值,并与从主机A发来的数据做验证。如果一致,则对root用户的身份验证成功;


流程图:


1.png


1)Client将自己的公钥存放在Server上,追加在文件authorized_keys中。

2)Server端接收到Client的连接请求后,会在authorized_keys中匹配到Client的公钥pubKey,并生成随机数R,用Client的公钥对该随机数进行加密得到pubKey(R),然后将加密后信息发送给Client。

3)Client端通过私钥进行解密得到随机数R,然后对随机数R和本次会话的SessionKey利用MD5生成摘要Digest1,发送给Server端。

4)Server端会也会对R和SessionKey利用同样摘要算法生成Digest2。

5)Server端会最后比较Digest1和Digest2是否相同,完成认证过程。


在步骤1中,Client将自己的公钥存放在Server上。需要用户手动将公钥copy到server上。这就是在配置ssh的时候进行的操作。


它的安全性:


提前在Server上设置公钥【等同于为某一账号设置了一个密码】,公钥好比是请求的账号密码,内容很长,除非泄漏,基本上根本不可能破解,安全很高。


具体实现:


主机A连接主机B,假设主机A是Linux系统。


1)生成密钥对(非对称加密):在当前机器A上,可以通过ssh-keygen命令生成一个ssh密钥对,一路回车就可以;生成的密钥对默认保存在当前登录用户家目录下的.ssh目录,也可以指定保存目录。我们当前是以root用户登录,因此是保存在/root/.ssh目录:


1.png

1.png


ssh-keygen是用于生产密钥的工具,它常用参数:

-t:指定生成密钥类型(rsa、dsa、ecdsa等)
-P:指定passphrase,用于确保私钥的安全
-f:指定存放密钥的文件(公钥文件默认和私钥同目录下,不同的是,存放公钥的文件名需要加上后缀.pub)
-b:采用长度为1024字节的公钥/私钥对,最长4096字节,一般1024或2048就可以了,太长的话加密解密需要的时间也长。


一些说明与注意:

①创建密钥对时,要你输入的密码,为进行密钥对验证时输入的密码(和linux角色登录的密码完全没有关系); 

如果我们要进行的是SSH免密码连接,那么这里密码为空跳过即可。 

如果在这里你输入了密码,那么进行SSH密钥对匹配连接的时候,就需要输入这个密码了。(此密码为独立密码) 

用户家目录下的.ssh隐藏目录下会生成:id_rsa id_rsa.pub 两个文件。id_rsa是用户的私钥;id_rsa.pub则是公钥。

⑤用户自己的.ssh目录必须不能有其他人可写可读可执行的权限 .ssh 目录的权限必须是 700,否则ssh服务器会拒绝登录。这样做也是为了保护私钥不被窃取,客户端公钥不被修改

⑥root用户生成的密钥对可以复制给其他用户使用。

⑦当主机A上通过 ssh root@主机B进行登录时,主机A会尝试读取root家目录下的私钥文件(主机A会读取/root/.ssh/id_rsa文件作为私钥),也可以通过-i选项指定要使用的私钥文件;


~/.ssh中的四个文件,分别介绍下:


id_rsa:私钥文件。

id_rsa.pub:公钥文件。

以上两个文件默认没有,当执行ssh-keygen命令后生成,这两个密钥是一对,主要用于公钥无密码登录。


authorized_keys:保存已授权的客户端公钥。

这个文件Linux默认就是存在的,为空。主机A连接主机B,主机A生成了密钥对,主机A的公钥(id_rsa.pub)要放到主机B的这个文件中。

注意:此文件权限必须是600


known_hosts:保存已认证的远程主机ID。

上面三个文件是用于实现无密码登录的,而这个文件是用于最开始的非对称加密,安全传输会话秘钥


四个角色的关系如下图所示:

1.png


一台主机可能既是Client,也是Server。所以会同时拥有authorized_keys和known_hosts。


2)我们需要手动把公钥的内容复制到主机B相应用户(如root)家目录下的指定文件中:/home/root/.ssh/autorized_keys;


可以使用下面的命令直接完成

ssh-copy-id root@主机B


也可以通过复制粘贴的方式来完成;


好了,经过以上两步之后,从此你再登录,就不需要输入密码了。此命令执行后,远程主机(主机B)直接将用户的公钥保存在 ~/.ssh/authorized_keys 文件中。


主机B可以使用公钥登录,那密码登录可以关闭了,关闭方法:

去配置文件/etc/ssh/sshd_config,把PasswordAuthentication参数改成no,然后重启sshd服务。