SSH原理与运用(一):远程登录

作者: 阮一峰

日期: 2011年12月21日

SSH是每一台Linux电脑的标准配置。

随着Linux设备从电脑逐渐扩展到手机、外设和家用电器,SSH的使用范围也越来越广。不仅程序员离不开它,很多普通用户也每天使用。

SSH具备多种功能,可以用于很多场合。有些事情,没有它就是办不成。本文是我的学习笔记,总结和解释了SSH的常见用法,希望对大家有用。

虽然本文内容只涉及初级应用,较为简单,但是需要读者具备最基本的"Shell知识"和了解"公钥加密"的概念。如果你对它们不熟悉,我推荐先阅读《UNIX / Linux 初学者教程》《数字签名是什么?》

=======================================

SSH原理与运用

作者:阮一峰

一、什么是SSH?

简单说,SSH是一种网络协议,用于计算机之间的加密登录。

如果一个用户从本地计算机,使用SSH协议登录另一台远程计算机,我们就可以认为,这种登录是安全的,即使被中途截获,密码也不会泄露。

最早的时候,互联网通信都是明文通信,一旦被截获,内容就暴露无疑。1995年,芬兰学者Tatu Ylonen设计了SSH协议,将登录信息全部加密,成为互联网安全的一个基本解决方案,迅速在全世界获得推广,目前已经成为Linux系统的标准配置。

需要指出的是,SSH只是一种协议,存在多种实现,既有商业实现,也有开源实现。本文针对的实现是OpenSSH,它是自由软件,应用非常广泛。

此外,本文只讨论SSH在Linux Shell中的用法。如果要在Windows系统中使用SSH,会用到另一种软件PuTTY,这需要另文介绍。

二、最基本的用法

SSH主要用于远程登录。假定你要以用户名user,登录远程主机host,只要一条简单命令就可以了。

  $ ssh user@host

如果本地用户名与远程用户名一致,登录时可以省略用户名。

  $ ssh host

SSH的默认端口是22,也就是说,你的登录请求会送进远程主机的22端口。使用p参数,可以修改这个端口。

  $ ssh -p 2222 user@host

上面这条命令表示,ssh直接连接远程主机的2222端口。

三、中间人攻击

SSH之所以能够保证安全,原因在于它采用了公钥加密。

整个过程是这样的:(1)远程主机收到用户的登录请求,把自己的公钥发给用户。(2)用户使用这个公钥,将登录密码加密后,发送回来。(3)远程主机用自己的私钥,解密登录密码,如果密码正确,就同意用户登录。

这个过程本身是安全的,但是实施的时候存在一个风险:如果有人截获了登录请求,然后冒充远程主机,将伪造的公钥发给用户,那么用户很难辨别真伪。因为不像https协议,SSH协议的公钥是没有证书中心(CA)公证的,也就是说,都是自己签发的。

可以设想,如果攻击者插在用户与远程主机之间(比如在公共的wifi区域),用伪造的公钥,获取用户的登录密码。再用这个密码登录远程主机,那么SSH的安全机制就荡然无存了。这种风险就是著名的"中间人攻击"(Man-in-the-middle attack)。

SSH协议是如何应对的呢?

四、口令登录

如果你是第一次登录对方主机,系统会出现下面的提示:

  $ ssh user@host

  The authenticity of host 'host (12.18.429.21)' 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)?

这段话的意思是,无法确认host主机的真实性,只知道它的公钥指纹,问你还想继续连接吗?

所谓"公钥指纹",是指公钥长度较长(这里采用RSA算法,长达1024位),很难比对,所以对其进行MD5计算,将它变成一个128位的指纹。上例中是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)? yes

系统会出现一句提示,表示host主机已经得到认可。

  Warning: Permanently added 'host,12.18.429.21' (RSA) to the list of known hosts.

然后,会要求输入密码。

  Password: (enter password)

如果密码正确,就可以登录了。

当远程主机的公钥被接受以后,它就会被保存在文件$HOME/.ssh/known_hosts之中。下次再连接这台主机,系统就会认出它的公钥已经保存在本地了,从而跳过警告部分,直接提示输入密码。

每个SSH用户都有自己的known_hosts文件,此外系统也有一个这样的文件,通常是/etc/ssh/ssh_known_hosts,保存一些对所有用户都可信赖的远程主机的公钥。

五、公钥登录

使用密码登录,每次都必须输入密码,非常麻烦。好在SSH还提供了公钥登录,可以省去输入密码的步骤。

所谓"公钥登录",原理很简单,就是用户将自己的公钥储存在远程主机上。登录的时候,远程主机会向用户发送一段随机字符串,用户用自己的私钥加密后,再发回来。远程主机用事先储存的公钥进行解密,如果成功,就证明用户是可信的,直接允许登录shell,不再要求密码。

这种方法要求用户必须提供自己的公钥。如果没有现成的,可以直接用ssh-keygen生成一个:

  $ ssh-keygen

运行上面的命令以后,系统会出现一系列提示,可以一路回车。其中有一个问题是,要不要对私钥设置口令(passphrase),如果担心私钥的安全,这里可以设置一个。

运行结束以后,在$HOME/.ssh/目录下,会新生成两个文件:id_rsa.pub和id_rsa。前者是你的公钥,后者是你的私钥。

这时再输入下面的命令,将公钥传送到远程主机host上面:

  $ ssh-copy-id user@host

好了,从此你再登录,就不需要输入密码了。

如果还是不行,就打开远程主机的/etc/ssh/sshd_config这个文件,检查下面几行前面"#"注释是否取掉。

  RSAAuthentication yes
  PubkeyAuthentication yes
  AuthorizedKeysFile .ssh/authorized_keys

然后,重启远程主机的ssh服务。

  // ubuntu系统
  service ssh restart

  // debian系统
  /etc/init.d/ssh restart

六、authorized_keys文件

远程主机将用户的公钥,保存在登录后的用户主目录的$HOME/.ssh/authorized_keys文件中。公钥就是一段字符串,只要把它追加在authorized_keys文件的末尾就行了。

这里不使用上面的ssh-copy-id命令,改用下面的命令,解释公钥的保存过程:

  $ ssh user@host 'mkdir -p .ssh && cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub

这条命令由多个语句组成,依次分解开来看:(1)"$ ssh user@host",表示登录远程主机;(2)单引号中的mkdir .ssh && cat >> .ssh/authorized_keys,表示登录后在远程shell上执行的命令:(3)"$ mkdir -p .ssh"的作用是,如果用户主目录中的.ssh目录不存在,就创建一个;(4)'cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub的作用是,将本地的公钥文件~/.ssh/id_rsa.pub,重定向追加到远程文件authorized_keys的末尾。

写入authorized_keys文件后,公钥登录的设置就完成了。

==============================================

关于shell远程登录的部分就写到这里,下一次接着介绍《远程操作和端口转发》

(完)

珠峰培训

简寻

留言(55条)

必须:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

第一次接触ssh是git,这东西还是挺方便的。虽然我只是同步自己的小代码

chmod 700 ~/.ssh 必须

chmod 600 ~/.ssh/authorized_keys 非必须

这篇也不错,http://wowubuntu.com/ssh-tips.html,作者可以参考下,

'mkdir .ssh && cat >> .ssh/authorized_keys'
或者可以改为:
'mkdir .ssh ; cat >> .ssh/authorized_keys'

mkdir -p .ssh ,不然可能出错,后面的命令不执行。

我觉得好像有点儿不对劲啊……
远程主机上也不一定存在 authorized_keys 文件
所以,是不是应该这样:
'mkdir -p .ssh/authorized_keys && cat >> .ssh/authorized_keys'

呃,我错了……
'mkdir -p .ssh && cat >> .ssh/authorized_keys' 就好,其它的会自己处理的

ssh(ssl/tls)的原理很复杂,如果不对网络安全和数学有深入研究,很难讲得清楚。

很详细的文章
谢谢博主

用虚拟主机非常方便

能否顺便讲讲非对称加密是如何做到的?

博主的文章总是能让人看的明白。
自己知道还能讲解的让别人也知道,
我想这就是博主自身功力深厚的证明吧。

中间一段错了,公钥永远是用来加密的,私钥永远是用来解密。

不会是你看到我昨天用ssh,随便写了3个生成自己的ssh失败了,

你就以为我不会就写了这篇吧

我总怀疑你是个虚拟的人或者这文章不是你写的

自从kernel.org被爆,现在多数改为公钥认证方式,password我平时只用在内网服务器。不过像VPS,如果开password认证,从auth.log可以看到,经常有人在试你密码。
这个是比较恶心的

我参考你的这篇博客,做了个视频,谢谢
http://casts.happypeter.org/episodes/3

>>$ ssh user@host 'mkdir .ssh && cat >> .ssh/authorized_keys'

这里最好用mkdir -p .ssh,保证不管.ssh是否已经存在都正确执行。

@Todd:

谢谢指出。

我前面没有意识到,如果目录.ssh已经存在,mkdir .ssh会报错。我这就改过来,加上参数p。

用ssh-copy-id <host>木有成功,但也木有任何出错提示,完了之后ssh远端主机,还是需要输入密码。查看远端的authorized_keys文件,发现木有公钥字符串。
改用$ ssh <host> 'mkdir -p .ssh && cat >> .ssh/authorized_keys'

不知道方法一哪里出问题了。

@simenyu:

参考下面的文章,好像是说远程端的权限设置的原因。

http://superuser.com/questions/189376/ssh-copy-id-does-not-work

五、公钥登录,这部分应该写错了。
应该是:公匙是用来加密的,私匙用来解密的。

引用feynixs的发言:

五、公钥登录,这部分应该写错了。
应该是:公匙是用来加密的,私匙用来解密的。

公私钥是对等的吧,公加私解或者私加公解都只是用法问题

不错,虽然每天工作都要接触到SSH,文章提到的这些操作之前也有用过,但根本不知道原理,只是知道这样做可以达到怎么样的效果,现在看完这篇文章后清晰了很多,谢谢阮老师。

引用沙渺的发言:

公私钥是对等的吧,公加私解或者私加公解都只是用法问题

我觉得“五、公钥登录”这部分是有问题的,我认为应该是这样:

所谓"公钥登录",原理很简单,就是用户将自己的公钥储存在远程主机上。登录的时候,远程主机会向用户发送一段用用户公钥加密的随机字符串,用户用自己的私钥解密后,再发回来。远程主机把这个信息和原始信息匹配,如果成功,就证明用户是可信的,直接允许登录shell,不再要求密码。(其实,用户用私钥解密之后得到原始信息X,还要用会话号P和X一起算md5发回给服务器,服务器用之前保存的会话号P和原始信息X做md5运算,和用户发回的信息匹配,如果成功,就证明用户是可信的)

引用yinzhezq的发言:

最好改为这样:'mkdir -p .ssh && cat >> .ssh/authorized_keys'

'mkdir -p .ssh && umask 066 && cat >> .ssh/authorized_keys'

解释的再清晰不过了

$ ssh user@host 'mkdir -p .ssh && cat >> .ssh/authorized_keys' 这条命令不成功,报错找不到~/.ssh/id_rsa.pub

引用南蛮虫的发言:

$ ssh user@host 'mkdir -p .ssh && cat >> .ssh/authorized_keys'
这条命令不成功,报错找不到~/.ssh/id_rsa.pub

检查本地有没有~/.ssh/id_rsa.pub

@凯凯:

看他写的第三节「三、中间人攻击」,我估计是故意只强调客户端密钥的。

$ ssh user@example.com 'mkdir -p .ssh && touch .ssh/authorized_keys && chmod 644 .s
sh/authorized_keys && cat >> .ssh/authorized_keys'

引用feynixs的发言:

五、公钥登录,这部分应该写错了。
应该是:公匙是用来加密的,私匙用来解密的。

在"加密数据"时,是公钥加密,私钥解密;但是在"身份认证"时,是私钥加密,公钥解密,你想啊,私钥就一个,公钥可以有多个,即多个公钥对应一个私钥,加密的数据用公钥解出来了,那肯定就知对应的私钥是谁了,这就验证了身份了。

引用凯凯的发言:

我觉得“五、公钥登录”这部分是有问题的,我认为应该是这样:

所谓"公钥登录",原理很简单,就是用户将自己的公钥储存在远程主机上。登录的时候,远程主机会向用户发送一段用用户公钥加密的随机字符串,用户用自己的私钥解密后,再发回来。远程主机把这个信息和原始信息匹配,如果成功,就证明用户是可信的,直接允许登录shell,不再要求密码。(其实,用户用私钥解密之后得到原始信息X,还要用会话号P和X一起算md5发回给服务器,服务器用之前保存的会话号P和原始信息X做md5运算,和用户发回的信息匹配,如果成功,就证明用户是可信的)


如果远程主机上面有多个公钥,那它选择那个公钥来对数据进行加密呢?但是如果发送的是明文,用户用私钥加密,发送到远程主机,远程主机就可以遍历公钥来对数据解密了。不要说远程主机会遍历公钥来对数据加密发送给用户啊,这明显是浪费时间的。

引用沙渺的发言:

公私钥是对等的吧,公加私解或者私加公解都只是用法问题

明显身份认证的时候私加公解划算啊。

稍稍纠正一下, centos 重启应该是 sudo /etc/init.d/sshd restart 对么?
另外补充一点跳坑信息, 如果是刚刚创建 ~/.ssh/authorized_keys 的话, 要注意该文件的权限, 可以改成如: chmod 640 ~/.ssh/authorized_keys

写得实在太好了!简洁明了!非常感谢!

写的很清楚,阮老师

感谢,
口令登陆ssh遵循公钥加密、公钥解密;win客户端登陆 ①向远端主机发送请求 ②远程主机发给win客户一个公钥(类似一个没有上锁的盒子),win客户将密码放入上锁后再次还给远程主机 ③远程主机用私钥打开公钥(盒子),验证密码一致允许登陆。
ssh公钥登陆同样遵循私钥加密、公钥解密;linux客户端先生成ssh公钥事先给远程主机更名为authorized_keys,①linux客户端向远程主机发送连接,远程主机随意发给linux客户端一串字符串 ②linux客户端用私钥加密字符串返还给远程主机 ③远程主机用事先存储的公钥进行解密,如果和事先发送的字符串一致则允许登陆。

“……middle attack)。
SSH协议是如何应对的呢?
四、口令登录”

这个“如何应对”指的是应对中间人攻击吗?

很是实用,感谢博主

对这句执行的顺序有点不明白
cat >> .ssh/authorized_keys'

楼主这一篇讲的太好了,井井有条。

所谓"公钥登录",原理很简单,就是用户将自己的公钥储存在远程主机上。登录的时候,远程主机会向用户发送一段随机字符串,用户用自己的私钥加密后,再发回来。远程主机用事先储存的公钥进行解密,如果成功,就证明用户是可信的,直接允许登录shell,不再要求密码。

这部分应该是有问题的。不会使用私钥去加密,公钥是解密的。参考这里的解释,http://www.unixwiz.net/techtips/ssh-agent-forwarding.html#chal

下面这个摘录自 skypeGNU1 的BLOG 的解释就合理。


当需要连接到SSH服务器上时,客户端软件就会向服务器发出请求,请求使用客户端的密钥进行安全验证。服务器收到请求之后,先在该用户的根目录下寻找共有密钥,然后把它和发送过来的公有密钥进行比较。如果两个密钥一致,服务器就用公有的密钥加密“质询”,并把它发送给客户端软件(putty,xshell等)。客户端收到质询之后,就可以用本地的私人密钥解密再把它发送给服务器,这种方式是相当安全的。

感觉 公钥登录 完全没有解决中间人攻击的问题

下面是原文摘抄 我加了些标号
“所谓"公钥登录",原理很简单,就是用户将自己的公钥储存在远程主机上。1 登录的时候,远程主机会向用户发送一段随机字符串,2 用户用自己的私钥加密后,再发回来。3 远程主机用事先储存的公钥进行解密,如果成功,就证明用户是可信的,直接允许登录shell,不再要求密码。”

我的观点:
如果2过程中发送的数据被中间人截取了, 中间人发送到了远程主机, 远程主机会把中间人作为可信用户 对吧? 如此如何防止中间人攻击?

引用daryl的发言:

中间一段错了,公钥永远是用来加密的,私钥永远是用来解密。

公钥和私钥本身都是既可以加密也可以解密的,公钥加密私钥解密是正常的非对称加密过程,私钥加密公钥解密应该就是数字签名的过程了。

卧槽,误导啊,公钥私钥谁加密谁解密都没弄明白,还来说ssh?

引用沙渺的发言:

公私钥是对等的吧,公加私解或者私加公解都只是用法问题

从非对称算法角度上, 有两个数e和d, 不等于d,通过公开函数f1(e,p)得到m
同样,也可以通过公开函数f2(d,m)得到p, 其中m是原始文本而p是经过变换后的文本。
一般而言成为为【编码】和【解码】, 其中持有e的人编码后的数据只有持有d的人能解码,
同样持有d的人编码后只有e能解。 这里习惯性的吧e定义为【公钥】,d定义为【私钥】
作为公钥私钥的生成着会保存【私钥】(大多数情况下私钥文件里面也会包含公钥),
然后讲【公钥】广而告之。


注意【编码】和【解码】和【加密】【解密】是有差别的。
密码学里面, 对文字进行编码后,只有特定的对象(对方)能够解码。 符合这种语义
的编解码方式称之为【加密】和【解密】。
显然用【私钥】去【编码】后的数据不能叫【加密】的,因为【公钥】所有人都知道,失去了
加密的意义。 在公钥私钥系统中, 统一采用【公钥】加密和【私钥】解密。

而且【私钥】去编码数据【公钥】再去解码数据也并不是没有意义,只是习惯性称之为【签名】
即保证这块数据由【私钥】的持有者生成而不是不让人看到数据是什么
(因为大家都有公钥都能解都能证明--比如约定解出来的数据带一个尾巴hahaha,那么所有符合xxxxhahaha的数据都可以认为是私钥发送的别人不能伪造的,发送
的有效数据是xxxx)

其中用e进行 一个公钥对应一个私钥,

引用daryl的发言:

中间一段错了,公钥永远是用来加密的,私钥永远是用来解密。

瞎扯

引用paranonia的发言:

chmod 700 ~/.ssh 必须

chmod 600 ~/.ssh/authorized_keys 非必须

谢谢!

请问这样就可以远程登录主机了吗?我租的服务器一直上不去,好奇怪。

还需要设置~/.ssh/authorized_keys的权限,否则在/var/log/secure里可以看到提示Authentication refused: bad ownership or modes for file。经测试,只有文件所有者有写权限时,才可以登录服务器。所以authorized_keys为640,600都可以登录服务器。但不能是660,620等。而执行完上面的命令,authorized_keys为660,需要修改这个文件的权限。

发现还需要设置.ssh的权限,因为.ssh目录必须700才允许登录,而mkdir -p .ssh新建的目录权限是755.

阮老师: 我有一个疑问,还望解答,谢谢.

中间人攻击那一端里面有如下一段:
整个过程是这样的:(1)远程主机收到用户的登录请求,把自己的公钥发给用户。(2)用户使用这个公钥,将登录密码加密后,发送回来。(3)远程主机用自己的私钥,解密登录密码,如果密码正确,就同意用户登录。

经测试, 1. 远程主机 foo 用户并未生成公钥和密钥,也就是 /home/foo/.ssh/目录下并未存在 id_rsa.pub和id_rsa;
2. 本地主机 bar 用户并未生成公钥和密钥,也就是/home/bar/.ssh/目录下并未存在 id_rsa.pub和id_rsa;
3. 本地主机 bar 用户运行 ssh foo@remote_serv, 输入yes加入known_hosts后再输入密码可成功登入 remote_serv
疑问: 远程主机未生成公钥和私钥,如何发送自己的公钥给本地主机呢?

实验结果和讲解不符, 不知道是不是我理解错了呢?

发现,known_hosts里存入的不知道是什么东西,反正和对应主机的公钥不同,能详细说一下,这个中间是怎么加密的吗,用的什么加密算法

很赞的文章。之前一直没理解为什么第一次会提示“The authenticity of host 'host (12.18.429.21)' can't be established.”,感谢。

另外,在“公钥登陆”章节,您提到“登录的时候,远程主机会向用户发送一段随机字符串,用户用自己的私钥加密后,再发回来。远程主机用事先储存的公钥进行解密,如果成功,就证明用户是可信的,直接允许登录shell,不再要求密。” 这段话,个人有几点想交换下意见的地方:
(1)通常术语上私钥加密说成签名,公钥解密说成验签。从数学上是类似操作,但用途上签名和验签是为了证明“对方是对方”,而加密和解密是为了“发来的信息没被人偷看”,更严谨的安全会话是加密和签名同时用,因为正如您说的加密不能证明发消息的人是靠谱的。
(2)客户端与服务器是多对1的关系。“远程主机用事先储存的公钥进行解密”,如何从一堆公钥中找到当前客户端的公钥,少了一个公钥索引的点。

我要发表看法

«-必填

«-必填,不公开

«-我信任你,不会填写广告链接