简介

OpenSSH 的客户端是二进制程序 ssh。它在 Linux/Unix 系统的位置是/usr/local/bin/ssh,Windows 系统的位置是\Program Files\OpenSSH\bin\ssh.exe

Linux 系统一般都自带 ssh,如果没有就需要安装。

  1. # Ubuntu 和 Debian
  2. $ sudo apt install openssh-client
  3. # CentOS 和 Fedora
  4. $ sudo dnf install openssh-clients

安装以后,可以使用-V参数输出版本号,查看一下是否安装成功。

  1. $ ssh -V

基本用法

ssh 最常见的用途就是登录服务器,这要求服务器安装并正在运行 SSH 服务器软件。

ssh 登录服务器的命令如下。

  1. $ ssh hostname

上面命令中,hostname是主机名,它可以是域名,也可能是 IP 地址或局域网内部的主机名。不指定用户名的情况下,将使用客户端的当前用户名,作为远程服务器的登录用户名。如果要指定用户名,可以采用下面的语法。

  1. $ ssh user@hostname

上面的命令中,用户名和主机名写在一起了,之间使用@分隔。

用户名也可以使用ssh-l参数指定,这样的话,用户名和主机名就不用写在一起了。

  1. $ ssh -l username host

ssh 默认连接服务器的22端口,-p参数可以指定其他端口。

  1. $ ssh -p 8821 foo.com

上面命令连接服务器foo.com的8821端口。

连接流程

ssh 连接远程服务器后,首先有一个验证过程,验证远程服务器是否为陌生地址。

如果是第一次连接某一台服务器,命令行会显示一段文字,表示不认识这台机器,提醒用户确认是否需要连接。

  1. The authenticity of host 'foo.com (192.168.121.111)' can't be established.
  2. ECDSA key fingerprint is SHA256:Vybt22mVXuNuB5unE++yowF7lgA/9/2bLSiO3qmYWBY.
  3. Are you sure you want to continue connecting (yes/no)?

上面这段文字告诉用户,foo.com这台服务器的指纹是陌生的,让用户选择是否要继续连接(输入 yes 或 no)。

所谓“服务器指纹”,指的是 SSH 服务器公钥的哈希值。每台 SSH 服务器都有唯一一对密钥,用于跟客户端通信,其中公钥的哈希值就可以用来识别服务器。

下面的命令可以查看某个公钥的指纹。

  1. $ ssh-keygen -l -f /etc/ssh/ssh_host_ecdsa_key.pub
  2. 256 da:24:43:0b:2e:c1:3f:a1:84:13:92:01:52:b4:84:ff (ECDSA)

上面的例子中,ssh-keygen -l -f命令会输出公钥/etc/ssh/ssh_host_ecdsa_key.pub的指纹。

ssh 会将本机连接过的所有服务器公钥的指纹,都储存在本机的~/.ssh/known_hosts文件中。每次连接服务器时,通过该文件判断是否为陌生主机(陌生公钥)。

在上面这段文字后面,输入yes,就可以将当前服务器的指纹也储存在本机~/.ssh/known_hosts文件中,并显示下面的提示。以后再连接的时候,就不会再出现警告了。

  1. Warning: Permanently added 'foo.com (192.168.121.111)' (RSA) to the list of known hosts

然后,客户端就会跟服务器建立连接。接着,ssh 就会要求用户输入所要登录账户的密码。用户输入并验证密码正确以后,就能登录远程服务器的 Shell 了。

服务器密钥变更

服务器指纹可以防止有人恶意冒充远程主机。如果服务器的密钥发生变更(比如重装了 SSH 服务器),客户端再次连接时,就会发生公钥指纹不吻合的情况。这时,客户端就会中断连接,并显示一段警告信息。

  1. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  2. @ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
  3. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  4. IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
  5. Someone could be eavesdropping on you right now (man-in-the-middle attack)!
  6. It is also possible that the RSA host key has just been changed.
  7. The fingerprint for the RSA key sent by the remote host is
  8. 77:a5:69:81:9b:eb:40:76:7b:13:04:a9:6c:f4:9c:5d.
  9. Please contact your system administrator.
  10. Add correct host key in /home/me/.ssh/known_hosts to get rid of this message.
  11. Offending key in /home/me/.ssh/known_hosts:36

上面这段文字的意思是,该主机的公钥指纹跟~/.ssh/known_hosts文件储存的不一样,必须处理以后才能连接。这时,你需要确认是什么原因,使得公钥指纹发生变更,到底是恶意劫持,还是管理员变更了 SSH 服务器公钥。

如果新的公钥确认可以信任,需要继续执行连接,你可以执行下面的命令,将原来的公钥指纹从~/.ssh/known_hosts文件删除。

  1. $ ssh-keygen -R hostname

上面命令中,hostname是发生公钥变更的主机名。

除了使用上面的命令,你也可以手工修改known_hosts文件,将公钥指纹删除。

删除了原来的公钥指纹以后,重新执行 ssh 命令连接远程服务器,将新的指纹加入known_hosts文件,就可以顺利连接了。

执行远程命令

SSH 登录成功后,用户就进入了远程主机的命令行环境,所看到的提示符,就是远程主机的提示符。这时,你就可以输入想要在远程主机执行的命令。

另一种执行远程命令的方法,是将命令直接写在ssh命令的后面。

  1. $ ssh username@hostname command

上面的命令会使得 SSH 在登录成功后,立刻在远程主机上执行命令command

下面是一个例子。

  1. $ ssh foo@server.example.com cat /etc/hosts

上面的命令会在登录成功后,立即远程执行命令cat /etc/hosts

采用这种语法执行命令时,ssh 客户端不会提供互动式的 Shell 环境,而是直接将远程命令的执行结果输出在命令行。但是,有些命令需要互动式的 Shell 环境,这时就要使用-t参数。

  1. # 报错
  2. $ ssh remote.server.com emacs
  3. emacs: standard input is not a tty
  4. # 不报错
  5. $ ssh -t server.example.com emacs

上面代码中,emacs命令需要一个互动式 Shell,所以报错。只有加上-t参数,ssh 才会分配一个互动式 Shell。

加密参数

SSH 连接的握手阶段,客户端必须跟服务端约定加密参数集(cipher suite)。

加密参数集包含了若干不同的加密参数,它们之间使用下划线连接在一起,下面是一个例子。

  1. TLS_RSA_WITH_AES_128_CBC_SHA

它的含义如下。

  • TLS:加密通信协议
  • RSA:密钥交换算法
  • AES:加密算法
  • 128:加密算法的强度
  • CBC:加密算法的模式
  • SHA:数字签名的 Hash 函数

下面是一个例子,客户端向服务器发出的握手信息。

  1. Handshake protocol: ClientHello
  2. Version: TLS 1.2
  3. Random
  4. Client time: May 22, 2030 02:43:46 GMT
  5. Random bytes: b76b0e61829557eb4c611adfd2d36eb232dc1332fe29802e321ee871
  6. Session ID: (empty)
  7. Cipher Suites
  8. Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
  9. Suite: TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
  10. Suite: TLS_RSA_WITH_AES_128_GCM_SHA256
  11. Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
  12. Suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA
  13. Suite: TLS_RSA_WITH_AES_128_CBC_SHA
  14. Suite: TLS_RSA_WITH_3DES_EDE_CBC_SHA
  15. Suite: TLS_RSA_WITH_RC4_128_SHA
  16. Compression methods
  17. Method: null
  18. Extensions
  19. Extension: server_name
  20. Hostname: www.feistyduck.com
  21. Extension: renegotiation_info
  22. Extension: elliptic_curves
  23. Named curve: secp256r1
  24. Named curve: secp384r1
  25. Extension: signature_algorithms
  26. Algorithm: sha1/rsa
  27. Algorithm: sha256/rsa
  28. Algorithm: sha1/ecdsa
  29. Algorithm: sha256/ecdsa

上面的握手信息(ClientHello)之中,Cipher Suites字段就是客户端列出可选的加密参数集,服务器在其中选择一个自己支持的参数集。

服务器选择完毕之后,向客户端发出回应。

  1. Handshake protocol: ServerHello
  2. Version: TLS 1.2
  3. Random
  4. Server time: Mar 10, 2059 02:35:57 GMT
  5. Random bytes: 8469b09b480c1978182ce1b59290487609f41132312ca22aacaf5012
  6. Session ID: 4cae75c91cf5adf55f93c9fb5dd36d19903b1182029af3d527b7a42ef1c32c80
  7. Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
  8. Compression method: null
  9. Extensions
  10. Extension: server_name
  11. Extension: renegotiation_info

上面的回应信息(ServerHello)中,Cipher Suite字段就是服务器最终选定的加密参数。

ssh 命令行配置项

ssh 命令有很多配置项,修改它的默认行为。

-c

-c参数指定加密算法。

  1. $ ssh -c blowfish,3des server.example.com
  2. # 或者
  3. $ ssh -c blowfish -c 3des server.example.com

上面命令指定使用加密算法blowfish3des

-C

-C参数表示压缩数据传输。

  1. $ ssh -C server.example.com

-D

-D参数指定本机的 Socks 监听端口,该端口收到的请求,都将转发到远程的 SSH 主机,又称动态端口转发,详见《端口转发》一章。

  1. $ ssh -D 1080 server

上面命令将本机 1080 端口收到的请求,都转发到服务器server

-f

-f参数表示 SSH 连接在后台运行。

-F

-F参数指定配置文件。

  1. $ ssh -F /usr/local/ssh/other_config

上面命令指定使用配置文件other_config

-i

-i参数用于指定私钥,意为“identity_file”,默认值为~/.ssh/id_dsa(DSA 算法)和~/.ssh/id_rsa(RSA 算法)。注意,对应的公钥必须存放到服务器,详见《密钥登录》一章。

  1. $ ssh -i my-key server.example.com

-l

-l参数指定远程登录的账户名。

  1. $ ssh -l sally server.example.com
  2. # 等同于
  3. $ ssh sally@server.example.com

-L

-L参数设置本地端口转发,详见《端口转发》一章。

  1. $ ssh -L 9999:targetServer:80 user@remoteserver

上面命令中,所有发向本地9999端口的请求,都会经过remoteserver发往 targetServer 的 80 端口,这就相当于直接连上了 targetServer 的 80 端口。

-m

-m参数指定校验数据完整性的算法(message authentication code,简称 MAC)。

  1. $ ssh -m hmac-sha1,hmac-md5 server.example.com

上面命令指定数据校验算法为hmac-sha1hmac-md5

-N

-N参数用于端口转发,表示建立的 SSH 只用于端口转发,不能执行远程命令,这样可以提供安全性,详见《端口转发》一章。

-o

-o参数用来指定一个配置命令。

  1. $ ssh -o "Keyword Value"

举例来说,配置文件里面有如下内容。

  1. User sally
  2. Port 220

通过-o参数,可以把上面两个配置命令从命令行传入。

  1. $ ssh -o "User sally" -o "Port 220" server.example.com

使用等号时,配置命令可以不用写在引号里面,但是等号前后不能有空格。

  1. $ ssh -o User=sally -o Port=220 server.example.com

-p

-p参数指定 SSH 客户端连接的服务器端口。

  1. $ ssh -p 2035 server.example.com

上面命令连接服务器的2035端口。

-q

-q参数表示安静模式(quiet),不向用户输出任何警告信息。

  1. $ ssh q foo.com
  2. roots password:

上面命令使用-q参数,只输出要求用户输入密码的提示。

-R

-R参数指定远程端口转发,详见《端口转发》一章。

  1. $ ssh -R 9999:targetServer:902 local

上面命令需在跳板服务器执行,指定本地计算机local监听自己的 9999 端口,所有发向这个端口的请求,都会转向 targetServer 的 902 端口。

-t

-t参数在 ssh 直接运行远端命令时,提供一个互动式 Shell。

  1. $ ssh -t server.example.com emacs

-v

-v参数显示详细信息。

  1. $ ssh -v server.example.com

-v可以重复多次,表示信息的详细程度,比如-vv-vvv

  1. $ ssh -vvv server.example.com
  2. # 或者
  3. $ ssh -v -v -v server.example.com

上面命令会输出最详细的连接信息。

-V

-V参数输出 ssh 客户端的版本。

  1. $ ssh V
  2. ssh: SSH Secure Shell 3.2.3 (non-commercial version) on i686-pc-linux-gnu

上面命令输出本机 ssh 客户端版本是SSH Secure Shell 3.2.3

-X

-X参数表示打开 X 窗口转发。

  1. $ ssh -X server.example.com

-1,-2

-1参数指定使用 SSH 1 协议。

-2参数指定使用 SSH 2 协议。

  1. $ ssh -2 server.example.com

-4,-6

-4指定使用 IPv4 协议,这是默认值。

  1. $ ssh -4 server.example.com

-6指定使用 IPv6 协议。

  1. $ ssh -6 server.example.com

客户端配置文件

位置

SSH 客户端的全局配置文件是/etc/ssh/ssh_config,用户个人的配置文件在~/.ssh/config,优先级高于全局配置文件。

除了配置文件,~/.ssh目录还有一些用户个人的密钥文件和其他文件。下面是其中一些常见的文件。

  • ~/.ssh/id_ecdsa:用户的 ECDSA 私钥。
  • ~/.ssh/id_ecdsa.pub:用户的 ECDSA 公钥。
  • ~/.ssh/id_rsa:用于 SSH 协议版本2 的 RSA 私钥。
  • ~/.ssh/id_rsa.pub:用于SSH 协议版本2 的 RSA 公钥。
  • ~/.ssh/identity:用于 SSH 协议版本1 的 RSA 私钥。
  • ~/.ssh/identity.pub:用于 SSH 协议版本1 的 RSA 公钥。
  • ~/.ssh/known_hosts:包含 SSH 服务器的公钥指纹。

主机设置

用户个人的配置文件~/.ssh/config,可以按照不同服务器,列出各自的连接参数,从而不必每一次登录都输入重复的参数。下面是一个例子。

  1. Host *
  2. Port 2222
  3. Host remoteserver
  4. HostName remote.example.com
  5. User neo
  6. Port 2112

上面代码中,Host *表示对所有主机生效,后面的Port 2222表示所有主机的默认连接端口都是2222,这样就不用在登录时特别指定端口了。这里的缩进并不是必需的,只是为了视觉上,易于识别针对不同主机的设置。

后面的Host remoteserver表示,下面的设置只对主机remoteserver生效。remoteserver只是一个别名,具体的主机由HostName命令指定,UserPort这两项分别表示用户名和端口。这里的Port会覆盖上面Host *部分的Port设置。

以后,登录remote.example.com时,只要执行ssh remoteserver命令,就会自动套用 config 文件里面指定的参数。
单个主机的配置格式如下。

  1. $ ssh remoteserver
  2. # 等同于
  3. $ ssh -p 2112 neo@remote.example.com

Host命令的值可以使用通配符,比如Host *表示对所有主机都有效的设置,Host *.edu表示只对一级域名为.edu的主机有效的设置。它们的设置都可以被单个主机的设置覆盖。

配置命令的语法

ssh 客户端配置文件的每一行,就是一个配置命令。配置命令与对应的值之间,可以使用空格,也可以使用等号。

  1. Compression yes
  2. # 等同于
  3. Compression = yes

#开头的行表示注释,会被忽略。空行等同于注释。

主要配置命令

下面是 ssh 客户端的一些主要配置命令,以及它们的范例值。

  • AddressFamily inet:表示只使用 IPv4 协议。如果设为inet6,表示只使用 IPv6 协议。
  • BindAddress 192.168.10.235:指定本机的 IP 地址(如果本机有多个 IP 地址)。
  • CheckHostIP yes:检查 SSH 服务器的 IP 地址是否跟公钥数据库吻合。
  • Ciphers blowfish,3des:指定加密算法。
  • Compression yes:是否压缩传输信号。
  • ConnectionAttempts 10:客户端进行连接时,最大的尝试次数。
  • ConnectTimeout 60:客户端进行连接时,服务器在指定秒数内没有回复,则中断连接尝试。
  • DynamicForward 1080:指定动态转发端口。
  • GlobalKnownHostsFile /users/smith/.ssh/my_global_hosts_file:指定全局的公钥数据库文件的位置。
  • Host server.example.com:指定连接的域名或 IP 地址,也可以是别名,支持通配符。Host命令后面的所有配置,都是针对该主机的,直到下一个Host命令为止。
  • HostKeyAlgorithms ssh-dss,ssh-rsa:指定密钥算法,优先级从高到低排列。
  • HostName myserver.example.com:在Host命令使用别名的情况下,HostName指定域名或 IP 地址。
  • IdentityFile keyfile:指定私钥文件。
  • LocalForward 2001 localhost:143:指定本地端口转发。
  • LogLevel QUIET:指定日志详细程度。如果设为QUIET,将不输出大部分的警告和提示。
  • MACs hmac-sha1,hmac-md5:指定数据校验算法。
  • NumberOfPasswordPrompts 2:密码登录时,用户输错密码的最大尝试次数。
  • PasswordAuthentication no:指定是否支持密码登录。不过,这里只是客户端禁止,真正的禁止需要在 SSH 服务器设置。
  • Port 2035:指定客户端连接的 SSH 服务器端口。
  • PreferredAuthentications publickey,hostbased,password:指定各种登录方法的优先级。
  • Protocol 2:支持的 SSH 协议版本,多个版本之间使用逗号分隔。
  • PubKeyAuthentication yes:是否支持密钥登录。这里只是客户端设置,还需要在 SSH 服务器进行相应设置。
  • RemoteForward 2001 server:143:指定远程端口转发。
  • SendEnv COLOR:SSH 客户端向服务器发送的环境变量名,多个环境变量之间使用空格分隔。环境变量的值从客户端当前环境中拷贝。
  • ServerAliveCountMax 3:如果没有收到服务器的回应,客户端连续发送多少次keepalive信号,才断开连接。该项默认值为3。
  • ServerAliveInterval 300:客户端建立连接后,如果在给定秒数内,没有收到服务器发来的消息,客户端向服务器发送keepalive消息。如果不希望客户端发送,这一项设为0
  • StrictHostKeyChecking yesyes表示严格检查,服务器公钥为未知或发生变化,则拒绝连接。no表示如果服务器公钥未知,则加入客户端公钥数据库,如果公钥发生变化,不改变客户端公钥数据库,输出一条警告,依然允许连接继续进行。ask(默认值)表示询问用户是否继续进行。
  • TCPKeepAlive yes:客户端是否定期向服务器发送keepalive信息。
  • User userName:指定远程登录的账户名。
  • UserKnownHostsFile /users/smith/.ssh/my_local_hosts_file:指定当前用户的known_hosts文件(服务器公钥指纹列表)的位置。
  • VerifyHostKeyDNS yes:是否通过检查 SSH 服务器的 DNS 记录,确认公钥指纹是否与known_hosts文件保存的一致。