Secure Shell(安全外壳协议),简称 SSH,是一种加密的网络传输协议,用于客户端和主机之间的安全连接,并支持各种身份验证机制。通过 SSH,可以把所有传输的数据进行加密,也能够防止 DNS 欺骗和 IP 伪装。还有一个额外的好处就是传输的数据是经过压缩的,所以可以加快传输的速度。SSH 目前已经成为 Linux 系统的标准配置。
传统的网络服务程序,如 FTP、Pop 和 Telnet 其本质上都是不安全的;因为它们在网络上用明文传送数据、用户帐号和用户口令,很容易受到中间人(man-in-the-middle)攻击方式的攻击。
SSH 之所以能够保证安全,原因在于它采用了非对称加密技术(RSA)加密了所有传输的数据。
现在主要的加密方式有两种:
对称加密(也称密钥加密)
非对称加密(也称公钥加密)
所谓对称加密,指加密解密使用同一套秘钥。如下图所示:
对称加密使用同一个密钥来进行加密和解密,加密强度高的话很难破解,这样在传输时确实是安全可靠的,但是如何保证密钥不被泄露呢?在集群中,客户端的数量巨大,一旦任意一个客户端的密钥被窃取,那么整个系统的安全性也不复存在。
由于对称加密的弊端,产生了非对称加密,非对称加密中有两个密钥:公钥和私钥。公钥由私钥产生,但却无法推算出私钥。公钥加密后的密文,只能通过对应的私钥来解密。
在非对称加密中,由于只有公钥会被传输,而私钥保存在服务端本地,因此即便公钥被窃取,没有私钥进行解密,也无法得到原始密码,从而保证了系统的安全性。
那么这样 SSH 就绝对安全了吗?显然不,还有一种中间人攻击的方式。
SSH 虽然传输过程中很安全,但是在首次建立连接时并没有办法知道发来的公钥是否真的来自自己请求的服务器,如果有人在客户端请求服务器后拦截了请求,伪装成服务端并返回自己的公钥,这时候一旦建连,那么所有的数据就都能被攻击者用自己的私钥解密。这就是所谓的中间人攻击。
在该示例图中,存在 Hacker 服务器劫持了你的连接请求(如通过 DNS 劫持等方式),导致你与 Hacker 机器进行连接,因此它能够拿到你的明文密码,并可能通过该密码来攻击真实的服务器,然后你就 GG 了。
SSH 提供了两种级别的登录方式:
基于口令的安全登录(密码登录)
基于公钥的安全登录(免密登录)
只要你知道自己的帐号和口令,就可以登录到远程主机。所有传输的数据都会被加密,但是不能保证你正在连接的服务器就是你想连接的服务器。可能会有人冒充真正的服务器,即无法避免中间人攻击。
在中间人攻击的方式中,问题在于如何对服务端的公钥进行验证?在 HTTPS 中可以通过 CA 来进行公证,可是 SSH 的公钥和私钥都是自己生成的,没法公证。只能通过客户端自己对公钥进行确认。
通常 在第一次登录的时候,系统会出现如下提示信息:
The authenticity of host 'ssh-server.example.com (10.10.10.26)' can't be established.
RSA key fingerprint is 98:2e:d7:e0:de:9f:ac:67:28:c2:42:2d:37:16:58:4d.
Are you sure you want to continue connecting (yes/no)?
在这个认证信息中,可以看到提示:无法确认主机 ssh-server.example.com (10.10.10.26) 的真实性,不过知道它的公钥指纹,是否继续连接?
之所以用 fingerprint(公钥指纹)代替 key,主要是 key 过于长(RSA算法生成的公钥有1024位),很难直接比较。所以,对公钥进行 hash 生成一个 128 位的指纹,这样就方便比较了。
输入 yes 后,会出现如下确认信息:
Warning: Permanently added 'ssh-server.example.com,10.10.10.26' (RSA) to the list of known hosts.
Password: (enter password)
确认该服务器为可信任服务器,并添加到 known_hosts 文件中,下次连接时不用再次确认,然后输入密码进行验证。
这种简单粗暴的方式相当于让用户自己来判断目标服务器是否是真实服务器,显然这并不是最理想的方式,还是存在一定的安全风险。
在基于口令的安全登录中可以发现,每次登录都需要手动输入密码,不仅麻烦而且存在中间人攻击的风险,也不便于程序的自动化。所以 SSH 提供了另外一种可以免密登录的方式:公钥登录。流程如下:
先在客户端使用 ssh-keygen 生成一对密钥:公钥 + 私钥,手动将客户端公钥追加到服务端的 authorized_key 文件中,完成公钥认证操作。
与密码登录相比,公钥登录不仅加密所有传输的数据,也不需要在网络上传送口令,因此安全性更高,可以有效防止中间人攻击。
利用公钥登录的关键是必须手动将客户端的公钥添加到服务端,比如 GitHub 便有这一步骤,添加了之后便可免密登录。
下面将实践如何在 Linux 下配置 SSH 免密登录。
.ssh 目录下相关文件:
文件 | 说明 |
---|---|
id_rsa | 私钥 |
id_rsa.pub | 公钥 |
authorized_keys(手动创建) | 保存已授权的客户端公钥 |
known_hosts(自动生成) | 保存已认证的远程主机ID |
config(可选) | 保存不同主机密钥的配置 |
首先检查的主要目的是为了不覆盖现有密钥。运行以下 Is 命令查看,若密钥已存在,则可以直接使用这些密钥并跳过第二步,或选择备份旧密钥并生成新密钥。如果看到 No such file or directory 或 no matches found 意味着没有 SSH 密钥,则可以继续执行下一步并生成新密钥。
ls -al ~/.ssh/id_*.pub
运行结果:
ls: cannot access /root/.ssh/id_*.pub: No such file or directory
ssh-keygen 是生成密钥的工具,执行完成后生成公钥和密钥,这两个文件会默认保存在 ~/.ssh/ 路径下。
ssh-keygen 常用参数:
-t:指定生成密钥的类型,默认使用SSH2d的rsa
-f:指定生成密钥的文件名,默认id_rsa(私钥id_rsa,公钥id_rsa.pub)
-b:指定密钥长度(bits),RSA最小要求768位,默认是2048位;DSA密钥必须是1024位(FIPS 1862标准规定)
-C:添加注释,默认为user@hostname
-P:提供旧密码,空表示不需要密码(-P ‘’)
-N:提供新密码,空表示不需要密码(-N ’‘)
-R hostname:从known_hosts(第一次连接时就会在目录.ssh目录下生成该密钥文件)文件中删除所有属于hostname的密钥
-e:读取openssh的私钥或者公钥文件
-i:读取未加密的ssh-v2兼容的私钥/公钥文件,然后在标准输出设备上显示openssh兼容的私钥/公钥
-l:显示公钥文件的指纹数据
-q:静默模式
输入 ssh-keygen 命令,生成密钥,-P ‘’ 表示不设置密码。多数情况下,开发人员和系统管理员使用 SSH 不使用密码,因为它们对完全自动化的流程很有用。
ssh-keygen -t rsa -P ''
这里输入文件保存位置,直接 Enter 表示默认文件位置和文件名(推荐)
进入 .ssh 目录下查看文件
cd .ssh
ls
因为一台机器即能是客户端,又能是服务端,因此 .ssh 目录下可能同时存在 authorized_keys(在该机器为服务端时使用)和 known_hosts(在该机器为客户端时使用)。
本地主机免密登录
检查 .ssh 目录下 authorized_keys 文件是否存在,没有则创建一个,然后将 id_rsa.pub 的内容追加到 authorized_keys 文件中。
cat id_rsa.pub >> authorized_keys
有个小坑值得一提:authenrized_keys 的权限必须是 600 或更小,否则会连接失败。
保险起见,执行下面命令:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
远程主机免密登录
在本地生成 SSH 密钥对后,为实现免密登录到远程主机,需要将公钥复制到要远程主机。最简单方法是使用 ssh-copy-id 命令:
ssh-copy-id remote_username@server_ip_address
系统将提示输入 remote_username 的密码:
remote_username@server_ip_address's password:
用户通过身份验证后,公钥将添加到远程用户的 authorized_keys 文件,并且将关闭连接。如果由于某种原因, ssh-copy-id 本地主机上没有该程序,则可以使用以下命令复制公钥:
cat ~/.ssh/id_rsa.pub | ssh remote_username@server_ip_address
如此便完成了公钥安装,以后就可以 ssh 免密登录了,可以尽情的玩耍啦~
完成上述步骤后,就能够登录到远程主机而不会被提示输入密码,测试口令:
本地登录:
ssh localhost
远程登录:
ssh remote_username@server_ip_address
映射 ip 地址与主机名,简化登录命令:
echo "ip_address hostname" >> /etc/hosts
ssh hostname