SSH(Secure SHell)是为远程登录, 远程通信等设计的安全通信协议, 由芬兰研究员于1995年提出,其目的是用于替代非安全的Telnet、rsh、rexec等不安全的远程Shell协议.
SSH提供身份认证,加密通信,完整性校验以及身份认证等功能. 工程人员经常使用SSH协议登录云服务器等远程计算机, FTP, RPC等协议也可以使用SSH提供的安全信道.
因为版权, 加密算法等原因目前广为使用OpenSSH提供SSH服务.
使用SSH
OpenSSH分为客户端和服务端, Linux和Mac OS系统一般会自带openssh-client。
本地计算机上需要安装客户端, 远程计算机上需要运行服务端。 SSH协议默认使用TCP22端口, 远程计算机需要确保远程计算机打开了22端口.
远程计算机安装服务端后可以使用sudo service ssh start
启动, 或者使用sudo /etc/init.d/ssh start
.
启动成功后可以使用service ssh status
或者ps -e
中查看ssh服务的状态.
远程主机配置完毕后, 在本地主机使用ssh user@host
命令可以用户user的身份登录远程主机host, 如ssh root@mycloud.com
.
ssh服务可以使用口令验证(password)或者使用私钥验证(private-key)。ssh优先使用私钥验证, 若未配置私钥则要求用户输入口令进行验证, 该口令为远程计算机上账户的登录口令.
客户端在要求客户输入口令之前, 会先会检查自己的knows_hosts
数据库中(一般为~/.ssh/know_hosts
文件)是否已经包含当前服务端的密钥指纹(ECDSA key fingerprint).
若包含则会继续建立连接, 否则由用户判断是否相信当前客户端. 若用户不相信则中断连接, 否则继续建立连接, 并将当前服务端加入known_hosts
中。
用户在判断是否相信当前服务端时需注意, 若该服务端为真正的目标服务端, 则此后与此服务端通信过程中基本不会再受到中间人的威胁. 若发现错误信任了假冒的服务端, 则应尽快在known_hosts中删除它的指纹, 避免计算机在通信时默认相信该假冒者.
配置密钥对
ssh-keygen
是用于生成密钥对的工具, 使用ssh-keygen生成RSA密钥对:
$ssh-keygen -t rsa -C finley@gmail.com
Generating public/private rsa key pair.
Enter file in which to save the key (~/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in ~/.ssh/id_rsa.
Your public key has been saved in ~/.ssh/id_rsa.pub.
The key fingerprint is:
上面的提示说明私钥保存在~/.ssh/id_rsa
, 公钥保存在~/.ssh/id_rsa.pub
. 如果必要的话也可以为私钥设置口令.
使用man ssh-keygen
可以看到该命令的帮助, 最常用的选项有:
-
-q
: 以静默方式生成 -
-b bits
: 指定位数 -
-t
: 指定加密方式, 包括: dsa | ecdsa | ed25519 | rsa | rsa1 -
-N new_passphrase
: 指定新的私钥密码 -
-C comment
: 添加注释, 通常是用户的签名 -
[-f output_keyfile]
: 保存密钥的文件, 默认为~/.ssh/id_rsa
打开~/.ssh/id_rsa.pub
可以看到自己的公钥, 将公钥的内容添加到远程计算机的~/.ssh/authorized_keys
中,然后重启远程计算机的sshd使新的配置生效。
为了保证安全,其它用户不能拥有修改密钥的权限,若系统检测到其它用户有密钥的写(w)权限将会拒绝使用该密钥。因此,我们将.ssh
目录权限设置为700
, 目录下所有相关文件权限设置为600
。
在终端中再次使用ssh命令登录时就不需要输入远程计算机的登录口令了。
通常情况下我们只需要一对密钥, 就像我们可以使用同一个身份证在不同的地方证明我们的身份一样, 只不过防伪的不是难以伪造的证件而是难以猜测的私钥.
传输文件
基于ssh传输文件是一个非常方便的功能, scp可以通过ssh通道在本地计算机和远程计算机之间传递文件.
scp会先使用ssh的认证方式建立安全链接, 然后完成文件传输, 完成传输后立即断开连接.
在本地计算机的终端中执行scp命令从远程计算机下载文件:
# format : scp user@host:remote_path local_path
$ scp ubuntu@qcloud:~/1.txt 1.txt
注意在本地终端执行, 而非使用ssh命令登录的远程终端.
scp命令与cp命令相似,第一个参数为源地址,第二个参数为目标地址, 只不过支持远程路径.
在复制目录下的所有内容时同样需要-r
参数:
# format: scp -r user@host:remote_path local_path
$ scp -r ubuntu@qcloud:~/workspace workspace
目的地址写为远程地址即可将本地文件上传到远程计算机:
# format: scp local_path user@host:remote_path
$ scp 1.txt ubuntu@qcloud:~/1.txt
# format scp -r local_path user@host:remote_path
$ scp -r workspace ubuntu@qcloud:~/workspace
使用跳板机
出于安全原因,通常集群中只有网关机可以从外网登录,内网中机器只能从网关机登录。若先登录网关机作为跳板,再从网关机上登录目标机则非常麻烦。
我们可以使用ssh的ProxyCommand功能自动使用跳板机登录内网:
$ ssh username@internal -o ProxyCommand='ssh username@gateway -W %h:%p'
其中,ProxyCommand是登录跳板机的ssh指令,internal
是内网中目标机的地址。ssh username@internal
将在跳板机上执行,internal
通常为目标机的内网ip地址。
注意,这种方式要求目标机的authorized_keys
中包含本地计算机的密钥。
配置主机别名
每次ssh登录都需要输入很长的指令是一件很麻烦的事情,我们可以在~/.ssh/config
文件中配置主机别名极大的简化登录指令:
Host gateway # 主机别名,使用`ssh gateway`命令可以直接登录该主机
Protocol 2 # ssh协议版本
HostName example.com # 主机地址,支持IP或域名
Port 22 # ssh服务端口号
User ubuntu # 登录用户名
IdentityFile ~/.ssh/id_rsa # 使用的私钥文件
Host internal # 主机别名,这是一个自动跳转示例
Protocol 2
HostName 192.168.1.111 # 目标机的内网ip地址,通常不使用域名
Port 22 # 内网中目标机的ssh服务端口号
User ubuntu # 登录目标机的用户名
ProxyCommand ssh gateway -W %h:%p # 登录跳板机的ssh指令,这里使用上一条配置的别名
IdentityFile ~/.ssh/id_rsa # 登录跳板机的私钥文件,该密钥必须包含在跳板机的authorized_keys中
Host 10.10.0.* # 使用通配符的示例,登录HostName符合通配符的主机都可以用该配置,如:`ssh ubuntu@10.10.0.1`
Port 22
User ubuntu
ProxyCommand ssh gateway -W %h:%p
端口转发
SSH可以通过端口转发为其它协议提供安全通信. 端口转发中涉及四台主机:
- 本地主机LocalHost: 运行SSH客户端的主机, 即用户操作的主机
- 远程主机RemoteHost: 运行SSH服务端的主机
- 源主机SrcHost: 发送请求的主机
- 目的主机DestHost: 请求要前往的主机
本地端口转发是由ssh客户端监听本地端口, 并将发往该端口的通信由远程主机发往目标:
ssh -L LocalPort:DestHost:DestPort user@RemoteHost
配置本地转发后:
- 本地SSH客户端将监听LocalPort端口
- 所有发往该本地端口的请求将被通过SSH信道发往远程主机RemoteHost
- 远程主机上的SSH服务端会将请求转发到目标主机DestHost上的DestPort端口
- 目标主机的响应将被RemoteHost和LocalHost依次转发, 原路返回来源主机
远程转发的过程正好与本地转发相反:
ssh -R RemotePort:DestHost:DestPort user@RemoteHost
配置远程转发后:
- 远程主机RemoteHost上的SSH服务端将监听RemoteHost上的RemotePort端口
- 所有发送到RemoteHost得RemotePort端口上的请求将通过SSH信道发往本地
- 本地主机上的SSH客户端将请求转发到目标主机DestHost上的DestPort端口
- 目标主机的响应将被LocalHost和RemoteHost依次转发, 原路返回来源主机
本地转发只能请求发往固定的目的主机和目的端口, 而动态转发则会根据请求的目的和协议决定转发到的目的主机和目的端口.
ssh -D LocalPort user@RemoteHost
ssh客户端将会监听所有发往本地LocalPort的请求, 并通过远程主机中转, 转发到请求的目的地址.
该过程与本地转发类似, 不过目的主机与目的端口是根据请求动态决定的.
安全通信原理
在进一步介绍认证过程之前, 需要先介绍一下对称加密和非对称加密的概念.
-
对称加密: 加密和解密的密钥相同. 如典型的移位密码: C代替A,D代替B...
这里向后移两位就是密钥, 得知向后移两位后可以将明文加密也可以将密文解密.
非对称加密: 加密密钥与解密密钥不同. 通常加密密钥公开(公钥), 解密密钥私有(私钥), 且很难通过公钥推断私钥.
非对称加密方式下, 所有人都可以使用公钥对明文进行加密, 但是只有持有私钥的人才可以解密密文.
一般来说, 对称加密方式的加解密速度比非对称方式更快.
现在, 我们可以开始介绍SSH认证和安全通信的原理了.
协议协商阶段
建立SSH安全链接的第一个阶段为协议协商阶段:
- 服务端监听端口等待客户端连接
- 客户端发起TCP连接请求, 服务端接收到该请求后,向客户端发送SSH协议版本信息.
- 客户端接根据该版本信息与自己的版本,决定将要使用的SSH版本,并向服务端发送选用的SSH版本信息
- 服务端检查是否支持客户端的决定使用的SSH版本
至此,双方完成协议协商。如果该过程中,客户端或服务端发送SSH版本无法兼容,任何一方都可以断开连接.
此时双方建立明文信道, 均无法确认对方身份.
服务端认证阶段
随后进入服务端认证阶段:
-
服务端向客户端发送下列信息:
- 服务端身份公钥(Host Key), 用于认证服务端身份
- 服务端通信公钥(Server Key), 用于生成通信加密密钥
客户端根据Host Key, 查询known_hosts或交由用户判断是否信任该服务端.
若信任则继续,否则断开连接客户端随机生成会话密钥(Session Key), 并使用服务端通信公钥(Server Key)加密后发给服务端
服务端用自己的私钥解密得到会话密钥(Session Key)
本阶段仍然使用明文信道, 当本阶段结束时双方都有了相同且安全的会话密钥. 此后的通信过程中, 双方将使用会话密钥对称加密进行安全通信.
客户端认证阶段
随后进入客户端认证阶段, 此阶段双方已建立对称加密安全信道.
口令方式: 客户端提供用户和口令,服务端进行匹配完成认证.
-
密钥方式:
- 客户端发起一个Public Key认证请求
- 服务端检查是否存在请求帐号的公钥(一般在~/.ssh/authorized_keys文件中),以及其拥有的访问权限, 如果没有则断开连接.
- 服务端使用客户端发送的公钥对一个随机的256位的字符串进行加密,并发送给客户端.
- 客户端使用私钥对字符串进行解密,并生成一个MD5值发送给服务端
- 服务端根据原始随机字符串生成MD5值进行匹配, 确认客户端身份
至此, 双方互相确认对方身份并建立加密信道, 可以正式进行安全通信。