SSH2.0编程 ssh协议过程实现(转)
SSh协议:
全称为Secure Shell,即很安全的shell,主要目的是用来取代传统的telnet和r系列命令(rlogin,rsh,rexec等)远程登录和远程执行命令的工具,实现远程登录和远程执行命令加密,防止由于网络监听而出现的密码泄露,从而对系统构成威胁。(telnet协议采用明文传送密码,数据传送过程中也不加密)
ssh协议目前有ssh1 和ssh2,其实现在我们主要使用的也是openssh。ssh不仅在登录过程中对密码进行加密传送,而且在登录后执行的命令的数据也进行加密,这样即使别人在网络上监听并截获了你的数据包,他也看不到其中的内容。
之前为了自己做一套SSH,先自己实现了一套telnet。但经过这么多天的苦逼,发现以前的工作都是徒劳。ssh的协议很繁杂,核心的内容在于密码算法,而且自己很难在网上找到周全的细节讲解与详细的实现,只有靠自己刷RFC和问大神还有就是靠强X我的服务器艰难地完成。
现计算了下时间,自己做SSH耗费了进两个月的时间,虽然期间也夹着其他的繁杂事物,但自己在这方面确是是耗费了非常大的精力。因为这方面详细资料的匮乏,自己以前也几乎没有接触过密码学方面的东西,很多只有靠自己摸索,所以我得经常拿我自己的服务器来做黑盒测试,我现在服务器上的ss服务器日志全是一些非法连接的记录(—_—|||)。早知当初就不那么作死非要自己实现他的加密算法和过程,用openssl就很快搞定了。但我还是觉得这次做SSH的精力是我受益匪浅,不仅熟悉了各种加密,并且能靠自己实现并熟练应用了。可能这些对自己帮助不大,但至少和信安的小伙伴也有点吹牛的谈资了~
这篇文章希望能帮助到想了解ssh2.0协议或是亲手实现ssh协议的小伙伴。
首先对数据包的格式进行说明:
数据包由包长度(Packet Length)、填充长读(Padding Length)、信息代码(Msg code)、信息内容与填充值(Padding String) 这5部分组成。信息内容中的一些字符串以4字节长度+该长度数量的字符组成,数值按照网络序排列,例如:abc: 00 00 00 03 (char)a (char)b (char)c 。另外有一种大整数的情况,负数和字符串的表示方式一样,正数需要前导0,例如 4b64: 00 00 00 03 00 4b 64 。
ssh头的结构体:
1
2
3
4
5
6
7
|
struct sshhead { unsigned int tlen; unsigned char plen; unsigned char msgcode; sshhead(){tlen=6;} }; |
就拿通过ssh远程控制的一个完整个过程来讲,ssh的过程可分为以下3部分:
一、版本协商
二、算法协商与密钥交换
三、加密通信(可能含有2、3部分)
这其中第二部分是ssh最为核心的过程,该过程决定了以后通信所要使用的密钥,下面按顺序对每个部分对比着数据包进行详细的讲解并给出实现的过程。
一、版本协商:
在建立连接后,客户端与服务器分别向对方发送自己ssh的版本信息(这里的数据格式不同于其他包,只有一行版本号),以\r\n结束。版本的格式如下:
SSH-ssh协议版本-详细版本\r\n (几乎只有ssh协议版本之前的信息有效)
比如我linux上的就是:SSH-2.0-OpenSSH_5.3\r\n
Putty的是: SSH-2.0-PuTTY_Release_0.63\r\n
一般来说,在建立连接后,是先由服务器发版本号过来,单线程处理版本协商的朋友需要注意下。
在双方收到对方发来的版本号后,会根据两者之中最小的版本来进行接下来的通讯。
二、算法协商与秘钥交换:
这部分的内容将会占该文章总篇幅的一半以上。
首先给大家看下整个过程的数据包大概:
整个部分是从第6条开始到第15条结束,除去中间的非协议部分,总共有7条数据包。看起来只有这么几条数据包,但其中包含了非常多的过程与隐秘的信息。
1、算法协商:
位第6、9数据,分别为双发向对法发送的自己在不同密码需求上支持的算法。
该数据包的格式:
按顺序分别是:
cookie(随机的值,16byte)
kex_algorithms(秘钥租交换算法)
server_host_key_algorithms(服务器主机秘钥,正常情况用处不大,甚至可以不用)
encryption_algorithms_client_to_server(两端通信使用的加密算法)
encryption_algorithms_server_to_client
mac_algorithms_client_to_server(数据校验用的hash算法)
mac_algorithms_server_to_client
compression_algorithms_client_to_server(压缩算法)
compression_algorithms_server_to_client
languages_client_to_server
languages_server_to_client
first_kex_packet_follows
0(4byte整数,扩展用的)
每个算法类型可能会有多个不同的算法,这些算法之间使用逗号隔开。
现在双方知道对方支持的算法,但是应该怎样决定每个类型实际所使用的算法呢?
每个算法类型列表的第一个算法必须是首选的算法,服务器应以客户端的算法优先级作为考虑,就拿交换算法举例:
现在服务器有三个算法dh1,dh2,dh3
客户端有两个算法dh3,dh2
那么服务器的首选算法是dh1,而客户端是dh3,客户端此时知道服务器有dh3算法,因此客户端就确认使用dh3算法。服务器发现自己的首选算法与客户端不同,而自己拥有客户端的首选算法,因此服务器也确认使用dh3算法。
再看另一个情况
服务器:dh1,dh2,dh3
客户端:dh4,dh3,dh1
这时服务器没有客户端的首选算法,客户端会使用第二个算法dh3,此时服务器也支持第二个算法,双方将确定使用dh3算法。
如果服务器和客户端双方没有共同的算法,这次会话将会终止。
下面是代码实现和服务器之间的版本协商
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#define KEI (char)20 #define NK (char)21 //算法名 #define VER "SSH-2.0-WCHRT_1.0\r\n" #define COOKIE "0123456789ABCDEF" #define VKEX "diffie-hellman-group-exchange-sha256" #define VSHK "ssh-rsa" #define VECS "aes128-cbc" #define VESC "aes128-cbc" #define VMCS "hmac-sha1" #define VMSC "hmac-sha1" #define VCCS "none" #define VCSC "none" #define VLCS "" #define VLSC "" #define KFPF "" |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
bool key_exchange() { sshhead sshh; sshh.msgcode=KEI; sshh.tlen-=4; //将算法列表与信息分别写入缓冲区 mstrin(COOKIE,sshh.tlen); mstrin(VKEX,sshh.tlen); mstrin(VSHK,sshh.tlen); mstrin(VECS,sshh.tlen); mstrin(VESC,sshh.tlen); mstrin(VMCS,sshh.tlen); mstrin(VMSC,sshh.tlen); mstrin(VCCS,sshh.tlen); mstrin(VCSC,sshh.tlen); mstrin(VLCS,sshh.tlen); mstrin(VLSC,sshh.tlen); //ed for ( int i=0;i<5;sshh.tlen++,i++) { data[sshh.tlen]=( char )0; } //载荷的计算与总长度的写入都放在最后 //count padding length count_padding(sshh); sshheadin(sshh); //没有封装socket len=send(sock,data,sshh.tlen+4,0); mrecv(10); //printf("(%d)",len); if (data[5]==KEI) { return true ; } return false ; } |
用到的一些功能函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
//向缓冲区填充字符串,长度使用网络字节序 void mstrin(string s,unsigned int &tlen) { data[tlen]=( char )(s.length()/(256*256*256)); data[tlen+1]=( char )(s.length()/(256*256)); data[tlen+2]=( char )(s.length()/256); data[tlen+3]=( char )(s.length()); tlen+=4; for ( int i=0;i<s.length();tlen++,i++) { data[tlen]=s[i]; } data[tlen]= '\0' ; } void sshheadin(sshhead &sshh) { sshh.tlen-=4; data[0]=( char )(sshh.tlen/(256*256*256)); data[1]=( char )(sshh.tlen/(256*256)); data[2]=( char )(sshh.tlen/256); data[3]=( char )(sshh.tlen); data[4]=( char )sshh.plen; data[5]=sshh.msgcode; } void count_padding(sshhead &sshh) { int k=2; if (sshh.tlen%8<4) { k=1; } sshh.plen=(sshh.tlen/8+k)*8-sshh.tlen; sshh.tlen=(sshh.tlen/8+k)*8; } |
2、秘钥交换
在算法协商成功过后,双方便立马进行秘钥组的交换。ssh2.0版本所使用的秘钥组交换协议算法主要使用diffie-hellman-group-exchange-sha算法。
鉴于该部分内容特别多,我特意在另一篇单独的文章中予以详细介绍,再阅读下文前请先参考该文章:dh-gex-sha算法详解
我们数据包的第10到15条都是该部分的内容
1、dh key exchange init (C)
密钥交换初始化,由客户端先向服务器发送秘钥交换请求的数据包,告知开始秘钥交换。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//<<<<<<<<<<DH KEX INIT<<<<<<<<< sshh.tlen=6; sshh.msgcode=DHKEI; //payload mintin(0x1000,sshh.tlen); count_padding(sshh); sshheadin(sshh); len=send(sock,data,sshh.tlen+4,0); //dh: set I_C dhdata.set_i_c(string(data+4,sshh.tlen)); //dhdata.set_i_c(string(data,len)); //>>>>>>>>>>>>>>>>>>>>>>>> |
2、dh key exchange reply (S)
服务器收到客户端发起交换的请求后,将自己用于dh算法的P、G发送给客户端,用于客户端生成dh公私钥。这里的P是一个大素数,而G是大于1的数,G不必过大,10位以内最后,因为按幂运算G能轻易生成特别大的数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
//<<<<<<<<<<DH KEX REPLAY<<<<<<<<< mrecv(10); if (data[5]!=DHKER) { puts ( "DH KEX REPLAY error" ); return false ; } //dh: set I_S dhdata.set_i_s(string(data+4,len-4)); //dhdata.set_i_s(string(data,len)); //dh: read P pos=6; intlen=readstrint(data+pos); pos+=4; Integer p=readstrbigint(data+pos,intlen); pos+=intlen; //dh: read G intlen=readstrint(data+pos); pos+=4; Integer g=readstrbigint(data+pos,intlen); pos+=intlen; //dh: set G and P dhdata.set_g_and_p(g,p); //cout<<dhdata.dh_p<<" "<<dhdata.dh_g<<endl; //>>>>>>>>>>>>>>>>>>>>>>>> |
3、dh gex init (C)
客户端收到服务器发过来的P、G后,自己变成根据P、G生成并计算出自己的公钥e。这一步也只需要客户端将生成的e发送给服务器即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//<<<<<<<<<<DH GEX INIT<<<<<<<<< sshh.tlen=6; sshh.msgcode=DHGI; dhdata.comp_e(); string e=inttostr(dhdata.get_e(),256); mstrin(e,sshh.tlen); count_padding(sshh); sshheadin(sshh); len=send(sock,data,sshh.tlen+4,0); //debugstr(data,len); //>>>>>>>>>>>>>>>>>>>>>>>> |
4、dhgex reply (S)
重要的来了,服务器收到了客户端发来的e后,便能计算出共享秘钥K,并根据现有信息计算出生成所需秘钥的H。
这个数据包里面含有如下信息:
KEX DH host key(K_S):
主机公钥,一般为rsa公钥。完整的格式为:总长度+算法名长度+算法名+证书(n)长度+证书(n)+公钥长度+公钥。
DH server f :
服务器的dh公钥值,客户端收到后便能用f计算出同样的共享秘钥K。
KEX DH H signature (签名后的H):
服务器用主机私钥对计算出的hash值H进行签名的结果。格式为:总长度+算法名长度+算法名+签名数据长度+签名值。
H的计算方法: H=hash(V_C||V_S||I_C||I_S||K_S||e||f||K);
按顺序用到的值(注意类型):
类型 | 值 | 说明 |
string | V_C | 客户端的初始报文(版本信息:SSH-2.0-xxx,不含结尾的CR和LF) |
string | V_S | 服务器的初始报文 |
string | I_C | 客户端 SSH_MSG_KEX_INIT的有效载荷(不含开头的数据长度值) |
string | I_S | 服务器的同上 |
string | K_S | 主机秘钥(dh gex reply(33)过程服务器发送host key (RSA公钥)) |
mpint | e | 客户端DH公钥 |
mpint | f | 服务器DH公钥 |
mpint | K | 共同DH计算结果 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
//<<<<<<<<<<DH GEX REPLAY<<<<<<<<< mrecv(10); if (data[5]!=DHGR) { puts ( "DH GEX REPLAY error" ); system ( "pause" ); return false ; } int padlen=data[4]; //dh: set server host key pos=6; intlen=readstrint(data+pos); //host key all length dhdata.set_k_s(string(data+pos+4,intlen)); pos+=4; intlen=readstrint(data+pos); //host key name pos+=4; pos+=intlen; intlen=readstrint(data+pos); //get rsa e and n pos+=4; Integer ee=readstrbigint(data+pos,intlen); pos+=intlen; intlen=readstrint(data+pos); pos+=4; Integer nn=readstrbigint(data+pos,intlen); //set rsa e and n pos+=intlen; dhdata.set_e_and_n(ee,nn); //dh: set dh server f intlen=readstrint(data+pos); pos+=4; dhdata.set_f(readstrbigint(data+pos,intlen)); pos+=intlen; //dh: set shka_name pos+=4; //h's total length intlen=readstrint(data+pos); pos+=4; dhdata.set_shka_name(string(data+pos,intlen)); pos+=intlen; //dh: set server h intlen=readstrint(data+pos); pos+=4; dhdata.set_s_h(string(data+pos,intlen)); pos+=intlen; pos+=padlen; //and other MAC// dhdata.comp_k(); dhdata.comp_h(); |
5、new keys (C)
客户端收到服务器的信息后计算出K,并用同样的方式计算出H(服务器和客户端的H都是同一个值)。并使用服务器发过来的K_S验证服务器发过来的签名后的H,如果验证一致,则说明此次秘钥交换成功。客户端向服务器发送new key,标志秘钥交换过程的结束。如果此次秘钥交换是整个会话的第一次交换,则计算出的H也是整个会话的会话ID(session_id)。
秘钥基本信息在网络上的传输与交换,接下来就分别是服务器和客户端各自使用现有信息计算出以后加解密所要使用的秘钥。秘钥计算:
这里的加密秘钥指的是以后数据通信所用的秘钥,一般用aes算法。
计算方式:hash(K,H,单个字符,session_id);
单个字符指的是单个大写的ASCII字母,根据不同的加密秘钥选择不同的字符来计算。
字母 | 秘钥 |
'A' | 客户端到服务器的初始IV(CBC) |
'B' | 服务器到客户端的初始IV |
'C' | 客户端到服务器的加密秘钥(对称秘钥) |
'D' | 服务器到客户端的加密秘钥 |
'E' | 客户端到服务器的完整性秘钥(HMAC) |
'F' | 服务器到客户端的完整性秘钥 |
就以aes-cbc为例子,aes对称加解密所需要用到的值有初始IV与对称秘钥。这里的初始IV指的是cbc模式中加解密的初始向量,第二次加解密需要IV的值,以后的每次的加解密都要依赖于上一次加解密的数据。
三、加密通信
此时双方都拥有协商好的算法以及用于加解密的秘钥,现在开始所有传输的全部数据都要进行加密(包含总长度),并使用同样的。
在加密通信的过程中,双方允许重新发送KEX秘钥交换请求。这时整个秘钥交换过程的数据将会使用现有密钥加解密。在该次秘钥交换的过程中也会生成一个H值,但该H值不会影响到此次会话的session_id,session_id只是会话第一次秘钥交换生成的H值。在秘钥交换最后客户端发出new keys请求时。双方会放弃当前使用的秘钥,使用新协商的秘钥继续通信。
在远程数据的通信过程中,双方使用SSH_MSG_CHANNEL_DATA标志消息类型进行数据传输。
在秘钥交换完成后第一次对发送数据加密时,首先需要对AES向量进行初始化,即设置对应的IV。aes部分我使用的是CRYPTOPP的aes-cbc算法(在后文的有对该算法的封装)。
1
2
3
4
5
6
7
|
en_c_to_s.set_iv(dhdata.comp_encry_key(IVCSF,32)); en_c_to_s.set_k(dhdata.comp_encry_key(ECSF,32)); en_c_to_s.init(); de_s_to_c.set_iv(dhdata.comp_encry_key(IVSCF,32)); de_s_to_c.set_k(dhdata.comp_encry_key(ESCF,32)); de_s_to_c.init(); |
整个协议用到的主要加密算法的实现与封装:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
//mycrypt.h #ifndef _MYCRYPT_H__ #define _MYCRYPT_H__ #include<cstring> #include<string> #include <iostream> #include<cmath> #include "integer.h" #include "files.h" #include "hex.h" #include "sha.h" #include "modes.h" #include "osrng.h" using namespace std; using namespace CryptoPP; Integer mkrandomnum( int len); string inttostr(Integer num,unsigned int radix); string inttostrnum(Integer num,unsigned int radix); //大整数转mpint string inttompint(Integer num,unsigned int radix); string strtostrnum(string s); //大整数快速幂运算 Integer fastpower_comp(Integer a,Integer b,Integer c); //sha算法封装 class m_sha { public : string encode_sha1(string data); string encode_sha256(string data); }; //dh算法实现 class m_dh { public : Integer dh_g,dh_p,dh_x,dh_e; Integer dh_y,dh_f; Integer dh_k; void set_g_and_p( const Integer g, const Integer p) { dh_g=g; dh_p=p; } void set_y(Integer y) { dh_y=y; } void set_f(Integer f) { dh_f=f; } void comp_e(); Integer get_e() { return dh_e; } void comp_k(); Integer get_k() { return dh_k; } }; //rsa算法实现 class m_rsa { public : Integer rsa_e; Integer rsa_n; void set_e_and_n(Integer e,Integer n) { rsa_e=e; rsa_n=n; } Integer comp_rsa_result(Integer num); }; //dh gex协议算法实现 class m_dh_gex_sha: public m_dh, public m_sha, public m_rsa { public : string v_c,v_s; string i_c,i_s; string k_s; string dh_h,s_h; string shka_name; void set_v_c(string x) { v_c=x; } void set_v_s(string x) { v_s=x; } void set_i_c(string x) { i_c=x; } void set_i_s(string x) { i_s=x; } void set_k_s(string x) { k_s=x; } void set_s_h(string x) { s_h=x; } void set_shka_name(string x) { shka_name=x; } void comp_h(); string get_h() { return dh_h; } string comp_encry_key( char c, const int len); }; //aes-cbc算法封装 class m_aes_cbc { public : string aes_k; string aes_iv; void set_k(string x) { aes_k=x; } void set_iv(string x) { aes_iv=x; } CBC_Mode<AES>::Encryption *aes_Encryptor; CBC_Mode<AES>::Decryption aes_Decryptor; void init(); string encode(string data); string decode(string data); }; #endif //_MYCRYPT_H__ |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
|
//mycrypt.cpp #include "mycrypt.h" Integer mkrandomnum( int len) { Integer re=0; for ( int i=0;i<len;i++) { re*=10; re+= abs ( rand ())%10; } return re; } string inttostr(Integer num,unsigned int radix) { string s1= "" ; unsigned int k; while (num>0) { k=num%radix; num/=radix; //cout<<num<<" "; //printf("%d\n",k); s1+=( char )k; } string s2= "" ; for ( int i=s1.length()-1;i>=0;i--) { s2+=s1[i]; } return s2; } string inttostrnum(Integer num,unsigned int radix) { string s1= "" ; unsigned int k; while (num>0) { k=num%radix; num/=radix; if (k<10) { s1+= '0' +k; } else { s1+= 'a' +k-10; } } string s2= "" ; for ( int i=s1.length()-1;i>=0;i--) { s2+=s1[i]; } return s2; } string inttompint(Integer num,unsigned int radix) { string s= "" ; s+=( char )0; s+=inttostr(num,radix); int len=s.length(); string k= "" ; while (len>0) { k+=( char )(len%256); len/=256; } while (k.length()<4) { k+=( char )0; } string re; re+=k[3]; re+=k[2]; re+=k[1]; re+=k[0]; re+=s; return re; } string strtostrnum(string s) { string re= "" ; int k,p; for ( int i=0;i<s.length();i++) { p=s[i]; if (p<0) { p=(256+s[i]); } k=p/16; for ( int j=0;j<2;j++) { // if (k<10) { re+=( '0' +k); } else { re+=( 'a' +k-10); } k=p%16; } } return re; } int intlength(Integer num) { int re=0; while (num>0) { num/=10; re++; } return re; } Integer fastpower_comp(Integer a,Integer b,Integer c) { /*unused fast power Integer re=1; for(int i=0;i<b;i++) { re*=a; re%=c; } return re; */ //fast power Integer n=c; c=1; while (b!=0) { if (b%2!=0) { b=b-1; c=(c*a)%n; } else { b=b/2; a=(a*a)%n; } } return c; } void m_dh::comp_e() { dh_x=mkrandomnum(50)+1; dh_e=fastpower_comp(dh_g,dh_x,dh_p); /* cout<<"//"<<endl; cout<<dh_g<<endl; cout<<dh_x<<endl; cout<<dh_p<<endl; cout<<dh_e<<endl; cout<<"//"<<endl; */ } void m_dh::comp_k() { dh_k=fastpower_comp(dh_f,dh_x,dh_p); } Integer m_rsa::comp_rsa_result(Integer num) { return fastpower_comp(num,rsa_e,rsa_n); } void m_dh_gex_sha::comp_h() { string data= "" ; /* data+=strtostrnum(v_c); data+=strtostrnum(v_s); data+=strtostrnum(i_c); data+=strtostrnum(i_s); data+=strtostrnum(k_s); data+=inttostrnum(dh_e,16); data+=inttostrnum(dh_f,16); data+=inttostrnum(dh_k,16); */ data+=v_c; data+=v_s; data+=i_c; data+=i_s; data+=k_s; data+=inttompint(dh_e,16); data+=inttompint(dh_f,16); data+=inttompint(dh_k,16); //cout<<endl<<"|"<<data<<"||"<<endl; dh_h=encode_sha256(data); } string m_dh_gex_sha::comp_encry_key( char c, const int len) { string re; string data= "" ; data+=inttompint(dh_k,16); data+=dh_h; data+=c; data+=dh_h; re=encode_sha256(data); while (re.length()<len) { data=inttompint(dh_k,16); data+=dh_h; data+=re; re+=encode_sha256(data); } while (re.length()>len) { re.pop_back(); } return re; } string m_sha::encode_sha1(string data) { string hash; SHA1 sha1; HashFilter hash_filter (sha1); hash_filter.Attach( new HexEncoder( new StringSink(hash), false )); hash_filter.Put((byte *)data.c_str(),data.length()); hash_filter.MessageEnd(); return hash; } string m_sha::encode_sha256(string data) { string hash; SHA256 sha256; HashFilter hash_filter (sha256); hash_filter.Attach( new HexEncoder( new StringSink(hash), false )); hash_filter.Put((byte *)data.c_str(),data.length()); hash_filter.MessageEnd(); return hash; } void m_aes_cbc::init() { aes_Encryptor= new CBC_Mode<AES>::Encryption((unsigned char *)aes_k.c_str(), aes_k.length(), (unsigned char *)aes_iv.c_str()); aes_Decryptor= new CBC_Mode<AES>::Decryption ((unsigned char *)aes_k.c_str(), aes_k.length(), (unsigned char *)aes_iv.c_str()); } string m_aes_cbc::encode(string data) { string re; StringSource(data, true , new StreamTransformationFilter(*aes_Encryptor, new StringSink(re), BlockPaddingSchemeDef::BlockPaddingScheme::ONE_AND_ZEROS_PADDING, true ) ); return re; } string m_aes_cbc::decode(string data) { string re; StringSource(data, true , new StreamTransformationFilter(*aes_Decryptor, new StringSink(re), BlockPaddingSchemeDef::BlockPaddingScheme::ONE_AND_ZEROS_PADDING, true ) ); return re; } |
ssh的实现就到此终于结束了,截图留念。
笔者在之初就想使用crypto++来帮助实现ssh过程的密码算法。而刚接触这东西完全搞不懂怎么用,什么编码器、生成器、过滤器、sink...这些概念根本就不懂,网上的使用文档直接就拿这一堆概念加上一堆组合出来的代码来实现一个加密算,没有什么密码学知识,想要快速掌握crypto++几乎是不可能的,当时研究了很久就只是会使用它的hash加密。而后自己硬着头皮实现了整个dh-gex,到后面aes后,发现自己能很自然得理解crypto++的用法了,便自己封装了crypto++的aes算法供使用。
总之都是好事,以后遇到其他的基于ssl的协议与应用就应能很轻松地理解与实现了。
http://www.cnblogs.com/wchrt/p/4550208.html
SSH2.0编程 ssh协议过程实现(转)的更多相关文章
- SSH2.0编程 ssh协议过程实现
之前为了自己做一套SSH,先自己实现了一套telnet.但经过这么多天的苦逼,发现以前的工作都是徒劳.ssh的协议很繁杂,核心的内容在于密码算法,而且自己很难在网上找到周全的细节讲解与详细的实现,只有 ...
- SSH工作过程简介和SSH协议算法集简介
SSH简介 SSH是Secure Shell(安全外壳)的简称,是一种在不安全的网络环境中,通过加密机制和认证机制,实现安全的远程访问以及文件传输等业务的网络安全协议. SSH协议采用了典型的客户端/ ...
- SSH协议(1)-工作原理及过程
转载. https://blog.csdn.net/cjx529377/article/details/77659199 SSH全称是Secure Shell,SSH协议是基于应用层的协议,为远程登录 ...
- Java 实现 SSH 协议的客户端登录认证方式--转载
背景 在开篇之前,让我们先对 SSH 协议有个宏观的大致了解,这样更有利于我们对本文的加深了解.首先要提到的就是计算机网络协议,所谓计算机网络协议,简单的说就是定义了一套标准和规则,使得不同计算机之间 ...
- SSH协议详解(转)
转发的http://blog.csdn.net/macrossdzh/article/details/5691924 很透彻啊,学习了 一.什么是SSH SSH是英文Secure Shell的简写形式 ...
- 何为SSH协议?
该文来自百度百科,自我收藏. SSH 为 Secure Shell 的缩写,由 IETF 的网络小组(Network Working Group)所制定:SSH 为建立在应用层基础上的安全协议.SSH ...
- 【Jsch】使用SSH协议连接到远程Shell执行脚本
如果大家熟悉Linux的话,一定对ssh,sftp,scp等命令非常熟悉,ssh是一个安全协议,用来在不同系统或者服务器之间进行安全连接,SSH 在连接和传送的过程中会加密所有的数据. 但是SSH一般 ...
- 网络编程TCP协议-聊天室
网络编程TCP协议-聊天室(客户端与服务端的交互); <span style="font-size:18px;">1.客户端发数据到服务端.</span> ...
- 【C#】教你纯手工用C#实现SSH协议作为GIT服务端
SSH(Secure Shell)是一种工作在应用层和传输层上的安全协议,能在非安全通道上建立安全通道.提供身份认证.密钥更新.数据校验.通道复用等功能,同时具有良好的可扩展性.本文从SSH的架构开始 ...
随机推荐
- Python多线程2:sched
sched模块提供了一个类的事件安排. scheduler类定义 class sched.scheduler(timefunc=time.monotonic, delayfunc=time.sleep ...
- Ubuntu9.04更新源
1.sudo gedit /etc/apt/sources.list 编辑你的源列表,加入以下列表中你认为适合你的,拷贝到你的列表中,然后保存列表. Archive.ubuntu.com更新serve ...
- UVA1455 - Kingdom(并查集 + 线段树)
UVA1455 - Kingdom(并查集 + 线段树) 题目链接 题目大意:一个平面内,给你n个整数点,两种类型的操作:road x y 把city x 和city y连接起来,line fnum ...
- 在C#或者SWT上跨进程访问SWT控件的问题
可能为了进程安全,无论是C#的Form还是Eclipse的SWT,都不允许跨进程访问控件. 通俗一点说就是: A进程创建了控件Widget,若想在B进程中访问控件Widget就会报错,必须在创建Wid ...
- hdu2861(递推)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2861 题意:n个板凳有m个人坐,求刚好将序列分成k段的方式. 分析: a[n][m][k]=a[n-1 ...
- java 线程关闭小结(转)
首先,要说的是java中没有一种停止线程的方法是绝对安全的.线程的中断Thread.interrput()方法很容易给人一种误会,让人感觉是一个线程使另外一个正在运行的线程停止工作,但实际上inter ...
- hdu1542(线段树——矩形面积并)
题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=1542 分析:离散化+扫描线+线段树 #pragma comment(linker,"/STA ...
- Java学习之道:空指针错误求解救????????????
运行下面这个主方法红色代码的地方就报如下错: https://api.weibo.com/2/users/show.json?access_token=2.00szM9cCc4R_EC6ebcf150 ...
- 自己定义 ViewGroup 支持无限循环翻页之三(响应回调事件)
大家假设喜欢我的博客,请关注一下我的微博,请点击这里(http://weibo.com/kifile),谢谢 转载请标明出处,再次感谢 ################################ ...
- HDU 3046Pleasant sheep and big big wolf(切最小网络流)
职务地址:HDU 3046 最小割第一发!事实上也没什么发不发的. ..最小割==最大流.. 入门题,可是第一次入手最小割连入门题都全然没思路... sad..对最小割的本质还是了解的不太清楚.. 这 ...