SSH2.0程式設計 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頭的結構體:
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演算法。
如果伺服器和客戶端雙方沒有共同的演算法,這次會話將會終止。
下面是程式碼實現和伺服器之間的版本協商
#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 ""
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;
}
用到的一些功能函式:
//向緩衝區填充字串,長度使用網路位元組序
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)
金鑰交換初始化,由客戶端先向伺服器傳送祕鑰交換請求的資料包,告知開始祕鑰交換。
//<<<<<<<<<<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能輕易生成特別大的數。
//<<<<<<<<<<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傳送給伺服器即可。
//<<<<<<<<<<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計算結果 |
//<<<<<<<<<<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演算法(在後文的有對該演算法的封裝)。
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();
整個協議用到的主要加密演算法的實現與封裝:
//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__
//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的協議與應用就應能很輕鬆地理解與實現了。