SSH使用及协议分析.docx
《SSH使用及协议分析.docx》由会员分享,可在线阅读,更多相关《SSH使用及协议分析.docx(26页珍藏版)》请在冰豆网上搜索。
SSH使用及协议分析
SSH使用及协议分析
SSH是一个用来替代TELNET、FTP以及R命令的工具包,主要是想解决口令在网上明文传输的问题。
为了系统安全和用户自身的权益,推广SSH是必要的。
SSH有两个版本,我们现在介绍的是版本2。
一、安装SSH
具体步骤如下:
获得SSH软件包。
(ftp:
//:
/pub/unix/ssh-2.3.0.tar.gz)
成为超级用户(root).
#gzip–cdssh-2.3.0.tar.gz|tarxvf–
#cdssh-2.3.0
#./configure
注意,如果你希望用tcp_wrappers来控制SSH,那么在configure时需要加上选项“--with-libwrap=/path/to/libwrap/”,
用来告诉SSH关于libwrap.a和tcpd.h的位置。
#make
#makeinstall
和SSH有关的程序都放置在/usr/local/bin下,包括ssh,sftp,sshd2,ssh-keygen等。
二、配置
SSH的配置文件在/etc/ssh2下,其中包括sshd2的主机公钥和私钥:
hostkey和hostkey.pub。
这两个文件通常是在安装SSH时自动生成的。
你可以通过下面的命令重新来生成它们:
#rm/etc/ssh2/hostkey*
#ssh-keygen2–P/etc/ssh2/hostkey
而ssh2_config文件一般情形下无需修改。
三、启动sshd2
每个要使用SSH的系统都必须在后台运行sshd2。
用手工启动:
#/usr/local/bin/sshd2&
可以在“/etc/rc2.d/S99local”中加入该命令,这样系统每次启动时会自动启动sshd2。
四、用tcp_wrappers控制SSH
安装SSH的站点可以用tcp_wrappers来限制哪些IP地址可以通过ssh来访问自己。
比如,在/etc/hosts.allow中加入
sshd,sshd2:
10.0.0.1
那么只有10.0.0.1可以通过ssh来访问该主机。
以上都是系统管理员完成的工作。
下面我们说说普通用户如何使用SSH。
五、基本应用
每个用户在使用SSH之前,都要完成以下步骤:
在本地主机(比如,)上生成自己的ssh公钥和私钥。
命令如下:
local#ssh-keygen
Generating1024-bitdsakeypair
1oOo.oOo.o
Keygenerated.
1024-bitdsa,teng@ns,FriOct20200017:
27:
05
Passphrase:
************/*在此输入你的口令,以后访问这台主机时要用。
Again:
************/*
Privatekeysavedto/home1/teng/.ssh2/id_dsa_1024_a
Publickeysavedto/home1/teng/.ssh2/id_dsa_1024_a.pub
生成的私钥和公钥(id_dsa_1024_a和id_dsa_1024_a.pub)存放在你家目录的~/.ssh2目录下。
和用户相关的SSH配置文件都在~/.ssh2下。
私钥由用户保存在本地主机上,而公钥需传送到远地主机的你自己的帐号的~/.ssh2下,如果你要用ssh2访问本地主机的话。
在~/.ssh2下创建“identification”文件用来说明进行身份认证的私钥。
命令如下:
local:
~/.ssh2#echo"IdKeyid_dsa_1024_a">identification
3.同样地,在远地主机(比如,)上完成上面步骤。
4.将本地()下你自己(这里是“teng”)的公钥(id_dsa_1024_a.pub)拷贝到远地主机()上你自己家目录下的.ssh2目录下,可命名为“local.pub”,一般用ftp上传即可。
在远地主机上,你自己家目录的.ssh2目录下,创建“authorization”文件,其中指定用来进行身份认证的公钥文件。
命令如下:
remote:
~/.ssh2#echo“Keylocal.pub”>authorization
现在你可以从本地用ssh2登录到远地系统了。
命令如下:
local#ssh
Passphraseforkey"/home1/teng/.ssh2/id_dsa_1024_a"withcomment"1024-bitdsa,
teng@ns,FriOct20200017:
27:
05":
***********
这时会要你输入你的ssh口令(Passphrase)。
验证通过后,即登录到remote主机上。
第一部分:
协议概览
整个通讯过程中,经过下面几个阶段协商实现认证连接。
第一阶段:
1.由客户端向服务器发出TCP连接请求。
TCP连接建立后,客户端进入等待;
2.服务器向客户端发送第一个报文,宣告自己的版本号,包括协议版本号和软件版本号。
协议版本号由主版本号和次版本号两部分组成。
它和软件版本号一起构成形如:
"SSH-<主协议版本号>.<次协议版本号>-<软件版本号>\n"
的字符串。
其中软件版本号字符串的最大长度为40个字节,仅供调试使用。
3.客户端接到报文后,回送一个报文,内容也是版本号。
客户端响应报文里的协议版本号这样来决定:
当与客户端相比服务器的版本号较低时,如果客户端有特定的代码来模拟,则它发送较低的版本号;如果它不能,则发送自己的版本号。
当与客户端相比服务器的版本号较高时,客户端发送自己的较低的版本号。
按约定,如果协议改变后与以前的相兼容,主协议版本号不变;如果不相兼容,则主协议版本号升高。
4.服务器接到客户端送来的协议版本号后,把它与自己的进行比较,决定能否与客户端一起工作。
如果不能,则断开TCP连接;
5.如果能,则按照二进制数据包协议发送第一个二进制数据包,双方以较低的协议版本来一起工作。
到此为止,这
6.两个报文只是简单的字符串,你我等凡人直接可读。
第二阶段:
7.协商解决版本问题后,双方就开始采用二进制数据包进行通讯。
由服务器向客户端发送第一个包,内容为自己的RSA主机密钥(hostkey)的公钥部分、RSA服务密钥(serverkey)的公钥部分、支持的加密方法、支持的认证方法、次协议版本标志、以及一个64位的随机数(cookie)。
这个包没有加密,是明文发送的。
8.客户端接收包后,依据这两把密钥和被称为cookie的64位随机数计算出会话号(sessionid)和用于加密的会话密钥(sessionkey)。
随后客户端回送一个包给服务器,内容为选用的加密方法、cookie的拷贝、客户端次协议版本标志、以及用服务器的主机密钥的公钥部分和服务密钥的公钥部分进行加密的用于服务器计算会话密钥的32字节随机字串。
除这个用于服务器计算会话密钥的32字节随机字串外,这个包的其他内容都没有加密。
之后,双方的通讯就是加密的了,
9.服务器向客户端发第二个包(双方通讯中的第一个加密的包)证实客户端的包已收到。
第三阶段:
双方随后进入认证阶段。
可以选用的认证的方法有:
(1)~/.rhosts或/etc/hosts.equiv认证(缺省配置时不容许使用它);
(2)用RSA改进的~/.rhosts或/etc/hosts.equiv认证;
(3)RSA认证;
(4)口令认证。
如果是使用~/.rhosts或/etc/hosts.equiv进行认证,客户端使用的端口号必须小于1024。
10.认证的第一步是客户端向服务器发SSH_CMSG_USER包声明用户名,服务器检查该用户是否存在,确定是否需要进行认证。
11.如果用户存在,并且不需要认证,服务器回送一个SSH_SMSG_SUCCESS包,认证完成。
否则,服务器会送一个SSH_SMSG_FAILURE包,表示或是用户不存在,或是需要进行认证。
注意,如果用户不存在,服务器仍然保持读取从客户端发来的任何包。
除了对类型为SSH_MSG_DISCONNECT、SSH_MSG_IGNORE以及SSH_MSG_DEBUG的包外,对任何类型的包都以SSH_SMSG_FAILURE包。
用这种方式,客户端无法确定用户究竟是否存在。
12.如果用户存在但需要进行认证,进入认证的第二步。
客户端接到服务器发来的SSH_SMSG_FAILURE包后,不停地向服务器发包申请用各种不同的方法进行认证,直到时限已到服务器关闭连接为止。
时限一般设定为5分钟。
13.对任何一个申请,如果服务器接受,就以SSH_SMSG_SUCCESS包回应;如果不接受,或者是无法识别,则以SSH_SMSG_FAILURE包回应。
第四阶段:
14.认证完成后,客户端向服务器提交会话请求。
15.服务器则进行等待,处理客户端的请求。
在这个阶段,无论什么请求只要成功处理了,服务器都向客户端回应SSH_SMSG_SUCCESS包;否则回应SSH_SMSG_FAILURE包,这表示或者是服务器处理请求失败,或者是不能识别请求。
会话请求分为这样几类:
申请对数据传送进行压缩、申请伪终端、启动X11、TCP/IP端口转发、启动认证代理、运行shell、执行命令。
16.到此为止,前面所有的报文都要求IP的服务类型(TOS)使用选项IPTOS_THROUGHPUT。
第五阶段:
17.会话申请成功后,连接进入交互会话模式。
在这个模式下,数据在两个方向上双向传送。
18.此时,要求IP的服务类型(TOS)使用IPTOS_LOWDELAY选项。
19.当服务器告知客户端自己的退出状态时,交互会话模式结束。
20.(注意:
进入交互会话模式后,加密被关闭。
在客户端向服务器发送新的会话密钥后,加密重新开始。
用什么方法加密由客户端决定。
)
第二部分:
密钥的交换和加密的启动
在服务器端有一个主机密钥文件,它的内容构成是这样的:
1)私钥文件格式版本字符串;
2)加密类型(1个字节);
3)保留字(4个字节);
4)4个字节的无符号整数;
5)mp型整数;
6)mp型整数;
7)注解字符串的长度;
8)注解字符串;
9)校验字(4个字节);
10)mp型整数;
11)mp型整数;
12)mp型整数;
13)mp型整数;
其中4、5、6三个字段构成主机密钥的公钥部分;10、11、12、13四个字段构成主机密钥的私钥部分。
9、10、11、12、13
五个字段用字段2的加密类型标记的加密方法进行了加密。
4个字节的校验字交叉相等,即第一个字节与第三个字节相等,
第二个字节与第四个字节相等。
在服务器读取这个文件时进行这种交叉相等检查,如果不满足这个条件,则报错退出。
1.服务器程序运行的第一步,就是按照上面的字段划分读取主机密钥文件。
随后生成一个随机数,再调用函数
voidrsa_generate_key(
RSAPrivateKey*prv,
RSAPublicKey*pub,
RandomState*state,
unsignedintbits
);
2.生成服务密钥,服务密钥也由公钥和私钥两部分组成。
3.上面的这个函数第一个指针参数指向服务密钥的私钥部分,第二个指向公钥部分。
4.然后把主机密钥的公钥部分和服务密钥的公钥部分发送给客户端。
5.在等到客户端回应的包后,服务器用自己的主机密钥的私钥部分和服务密钥的私钥部分解密得到客户端发来的32字节随机字串。
然后计算自己的会话号,并用会话号的前16字节xor客户端发来的32字节随机字串的前16字节,把它作为自己的会话密钥。
6.注意,服务器把8个字节的cookie、主机密钥的公钥部分、和服务密钥的公钥部分作为参数来计算自己的会话号。
7.再来看客户端。
客户端启动后的第一步骤也是读取主机密钥。
然后等待服务器主机密钥、服务密钥、和8个字节的cookie。
8.注意,服务器发送来的只是主机密钥和服务密钥的公钥部分。
接到包后,客户端立即把从服务器端收到cookie、主机密钥、和服务密钥作为参数计算出会话号。
从上面可以看出,服务器和客户端各自计算出的会话号实际是一样的。
9.随后,客户端检查用户主机列表和系统主机列表,查看从服务器收到的主机密钥是否在列表中。
如果不在列表中,则把它加入列表中。
然后就生成32字节的随机字串,这个32字节的随机字串就是客户端的会话密钥。
客户端用16字节的会话密钥xor它的前16字节,把结果用服务器的主机密钥和服务密钥进行双重加密后发送给服务器。
产生32字节随机字串时,随机数种子由两部分组成,其中一部分从系统随机数种子文件中得到,这样来避免会话密钥被猜出。
从上面服务器和客户端各自计算会话密钥的过程可以看出,服务器和客户端计算出的会话密钥是一样的。
10.上面的这几步,总结起来就要交换确定会话密钥,因为无论是des、idea、3des、arcfour、还是blowfish都是对称加密方法,只有一把密钥,双方都知道了会话密钥才能启动加密。
但会话密钥不能在网络上明文传送,否则加密就失去意义了。
于是使用RSA公钥体系对会话密钥进行加密。
11.RSA公钥体系的办法是用公钥加密私钥解密,它依据这样的数学定理:
若p、q是相异的两个质数,整数r和m满足rm==1(mod(p-1)(q-1))
a是任意的整数,整数b、c满足b==a^m(modpq),
c==b^r(modpq)。
则
c==a(modpq)。
具体实现是这样的:
(1)找三个正整数p、q、r,其中p、q是相异的质数,r是与(p-1)、(q-1)互质的数。
这三个数p、q、r就是私钥(privatekey)。
(2)再找一个正整数m满足rm==1(mod(p-1)(q-1))。
计算n=pq,m、n就是公钥(publickey)。
(3)被加密对象a看成是正整数,设a若a>=n,将a表示成s(s(4)加密:
计算b==a^m(modn)(0<=b(5)解密:
计算c==b^r(modn)(0<=c从上面的数学定理可知,最后结果c=a。
计算RSA密钥的方法及过程是,调用下面的函数计算RSA公钥和RSA私钥:
_______________________________________________________
voidrsa_generate_key(
RSAPrivateKey*prv,RSAPublicKey*pub,
RandomState*state,unsignedintbits
)
{
MP_INTtest,aux;
unsignedintpbits,qbits;
intret;
mpz_init(&prv->q);
mpz_init(&prv->p);
mpz_init(&prv->e);
mpz_init(&prv->d);
mpz_init(&prv->u);
mpz_init(&prv->n);
mpz_init(&test);
mpz_init(&aux);
/*计算质数p、q的位数*/
pbits=bits/2;
qbits=bits-pbits;
retry0:
fprintf(stderr,"Generatingp:
");
/*生成随机质数p*/
rsa_random_prime(&prv->p,state,pbits);
retry:
fprintf(stderr,"Generatingq:
");
/*生成随机质数q*/
rsa_random_prime(&prv->q,state,qbits);
/*判断是否p==q,如果是返回重新生成*/
ret=mpz_cmp(&prv->p,&prv->q);
if(ret==0)
{
fprintf(stderr,"Generatedthesameprimetwice!
\n");
gotoretry;
}
if(ret>0)
{
mpz_set(&aux,&prv->p);
mpz_set(&prv->p,&prv->q);
mpz_set(&prv->q,&aux);
}
/*确定p、q是否很接近*/
mpz_sub(&aux,&prv->q,&prv->p);
mpz_div_2exp(&test,&prv->q,10);
if(mpz_cmp(&aux,&test)<0)
{
fprintf(stderr,"Theprimesaretooclosetogether.\n");
gotoretry;
}
/*Makecertainpandqarerelativelyprime(incase
oneorbothwerefalsepositives...Thoughthisis
quiteimpossible).*/
mpz_gcd(&aux,&prv->p,&prv->q);
if(mpz_cmp_ui(&aux,1)!
=0)
{
fprintf(stderr,"Theprimesarenotrelativelyprime!
\n");
gotoretry;
}
/*从质数p、q导出私钥*/
fprintf(stderr,"Computingthekeys...\n");
derive_rsa_keys(&prv->n,&prv->e,&prv->d,&prv->u,&prv->p,&prv->q,5);
prv->bits=bits;
/*从质数p、q导出公钥*/
pub->bits=bits;
mpz_init_set(&pub->n,&prv->n);
mpz_init_set(&pub->e,&prv->e);
/*测试公钥和密钥是否有效*/
fprintf(stderr,"Testingthekeys...\n");
rsa_random_integer(&test,state,bits);
mpz_mod(&test,&test,&pub->n);/*mustbelessthann.*/
rsa_private(&aux,&test,prv);
rsa_public(&aux,&aux,pub);
if(mpz_cmp(&aux,&test)!
=0)
{
fprintf(stderr,"****private+publicfailedtodecrypt.\n");
gotoretry0;
}
rsa_public(&aux,&test,pub);
rsa_private(&aux,&aux,prv);
if(mpz_cmp(&aux,&test)!
=0)
{
fprintf(stderr,"****public+privatefailedtodecrypt.\n");
gotoretry0;
}
mpz_clear(&aux);
mpz_clear(&test);
fprintf(stderr,"Keygenerationcomplete.\n");
}
_______________________________________________________
在上面的函数成一对密钥时,首先调用函数
_______________________________________________________
voidrsa_random_prime
(
MP_INT*ret,RandomState*state,
unsignedintbits
)
{
MP_INTstart,aux;
unsignedintnum_primes;
int*moduli;
longdifference;
mpz_init(&start);
mpz_init(&aux);
retry:
/*挑出一个随机的足够大的整数*/
rsa_random_integer(&start,state,bits);
/*设置最高的两位*/
mpz_set_ui(&aux,3);
mpz_mul_2exp(&aux,&aux,bits-2);
mpz_ior(&start,&start,&aux);
/*设置最低的两位为奇数*/
mpz_set_ui(&aux,1);
mpz_ior(&start,&start,&aux);
/*启动小质数的moduli数*/
moduli=malloc(MAX_PRIMES_IN_TABLE*sizeof(moduli[0]));
if(moduli==NULL)
{
printf(stderr,"Cann'tgetmemoryformoduli\n");
exit
(1);
}
if(bits<16)
num_primes=0;
/*Don\'tusethetableforverysmallnumbers.*/
else
{
for(num_primes=0;
small_primes[num_primes]!
=0;num_primes++)
{
mpz_mod_ui(&aux,&start,sma