ssh秘鑰交換詳解與實現 diffie-hellman-group-exchange-sha


ssh的DH秘鑰交換是一套復合幾種算法的秘鑰交換算法。在RFC4419中稱為diffie-hellman-groupX-exchange-shaX 的算法(也有另一種單純的 rsaX-shaX 交換算法)。本文就以diffie-hellman-group-exchange-sha256為例,詳盡地講解整個完整的秘鑰交換過程。

    筆者在RFC上和網上看了很久,也只是做了一個大致了解,對實現的幫助不大。實際在實現過程中,有太多的細節需要注意,在很多細節的分歧中,需要自己抱着勇氣去測試。(原諒我不看openssh源碼和使用openssl庫,我只想全部自己實現整個ssh)。在diffie-hellman-group-exchange-sha256中,數據的類型非常地重要,因為涉及到hash運算,一定要區別好整數與字符串還有進制。一個小的不同都會導致hash的結果大不一樣,hash錯了以后的工作都是徒勞。

    diffie-hellman-group-exchange-sha256的整個過程中一共要用到的秘鑰交換算法有:diffie-hellman、sha256、ssh-rsa(或其他算法協商的host key,不是單純的rsa,蝦米告訴我ssh用的是RSASSA-PKCS1-v1_5 scheme標准)。而要支持這些加密算法,又需要很多基礎算法:多進制大整數、高精度運算、快速模冪、離散算法...(還好自己有一定的acm基礎,本來當初是打算學習並實現ssh協議,結果在算法的道路上越走越遠,這次就當鍛煉自己了。表示以后要好好運用面向對象技術,再也不實現不必要的底層了,以后涉及到安全傳輸就直接用openssl了)

    以下為各個算法的講解,先說最重要的幾個基礎數據結構與計算方法:

    1、mpint: 二進制補碼格式的多精度整數,儲存為一個字符,每個字符8位,從高位到低位,負數最高位為1,正數最高位為0(只用到整數)。數據格式為:4字符長度+該長度字符的數值。例如:a1d8:00 00 00 03 00 a1 d8;   4:00 00 00 02 00 04

    2、多進制大整數:平時以16進制存儲,這樣便於使用的時候少轉化,轉二進制也很快,實現很麻煩,我雖然有自己的大整數模板,但終歸是不放心,我就用cryptopp自帶的大整數改寫過來用(人家可是用的匯編計算的)。

    3、高精度運算:主要實現加法乘法和求余即可,因為dh和rsa只需要用到乘法與求余。

    4、快速模冪:DH算法和RSA算法都會用到相似的運算:a^b%c 。因為b是一個大整數,因此對於他的冪運算我們將b進行二分,然后對a以及a計算后的結果進行計算,這樣能省下大量不必要的運算。幾個例子:3^8=3*3*3*3*3*3*3*3要進行7次運算。而這樣化解(3^4)*(3^4)=(3^4)^2=((3^2)*(3^2))^2=((3^2)^2)^2只需要進行3次運算。第一種方式的時間復雜度是O(n),第二種二分冪的方法時間復雜度是O(logn),當b為10位十進制的整數時,第一種方式要計算10^10次,而用二分冪的方式只需要計算大約30多次。因為我們要求的是余果,所以在進行冪運算的同時就進行模運算,也能極大減少運算量,否則內存可能都裝不下那么大的冪果。下面給出代碼實現:

 
 
[cpp]  view plain copy
 
  1.     Integer fastpower_comp(Integer a,Integer b,Integer c)  
  2.     {  
  3.       
  4.     /*sample unused fast power 
  5.     Integer re=1; 
  6.     for(int i=0;i<b;i++) 
  7.     { 
  8.         re*=a; 
  9.         re%=c; 
  10.     } 
  11.     return re; 
  12.     */  
  13.   
  14.     //fast power  
  15.     Integer n=c;  
  16.     c=1;  
  17.     while(b!=0)  
  18.     {  
  19.         if(b%2!=0)  
  20.         {  
  21.             b=b-1;  
  22.             c=(c*a)%n;  
  23.         }  
  24.         else  
  25.         {  
  26.             b=b/2;  
  27.             a=(a*a)%n;  
  28.         }  
  29.     }  
  30.     return c;  
  31.     }  

    

    密碼算法:

    1、diffie-hellman:

        服務器首先產成兩個數GP,P為一個非常大的素數,作為DH算法的G密碼發生器,也就是P的一個原根(不理解沒關系),服務器將這兩個數發給客戶端,用於秘鑰的交換。

        客戶端生成一個數(客戶端的私鑰)x(0<x<P,但x應該大一點,否則當G特別小時生成的秘鑰長度可能會很短,服務器會拒絕),計算e=(G^x)%P。得到的e就是客戶端的公鑰,客戶端將e發送給服務器。

        服務器也同客戶端一樣,生成一個數y,計算f=(G^y)%P。將服務器公鑰f發送給客戶端。

        現在客戶端與服務器都知道了對方的公鑰,雙方把對方的公鑰作為自己模冪運算的底數進行運算,服務器計算K1=(e^y)%P,客戶端計算K2=(f^x)%P 可以證明這里K1==K2 ,得到的K值便是雙方所交換的秘鑰。

        大素數的生成:我采用了一種猜測加枚舉的的方法。做過篩法算素數的都知道當數越大,出現連續素數的概率越高,而且連續的長度越長。我們可以通過隨機生成一個大數(最好是奇數),然后判斷該素數是否為素數,如果不是,將這個數加二再判斷,直到判斷為素數即可,以后每次再要取素數就可以把這個結果加再判斷(此時素數的幾率很高)。

        

[cpp]  view plain copy
 
  1. class m_dh  
  2. {  
  3. public:  
  4.     Integer dh_g,dh_p,dh_x,dh_e;  
  5.     Integer dh_y,dh_f;  
  6.     Integer dh_k;  
  7.   
  8.     void set_g_and_p(const Integer g,const Integer p)  
  9.     {  
  10.         dh_g=g;  
  11.         dh_p=p;  
  12.     }  
  13.     void set_y(Integer y)  
  14.     {  
  15.         dh_y=y;  
  16.     }  
  17.     void set_f(Integer f)  
  18.     {  
  19.         dh_f=f;  
  20.     }  
  21.     void comp_e();  
  22.     Integer get_e()  
  23.     {  
  24.         return dh_e;  
  25.     }  
  26.     void comp_k();  
  27.     Integer get_k()  
  28.     {  
  29.         return dh_k;  
  30.     }  
  31. };  
[cpp]  view plain copy
 
  1. void m_dh::comp_e()  
  2. {  
  3.       
  4.     dh_x=mkrandomnum(50)+1;  
  5.     dh_e=fastpower_comp(dh_g,dh_x,dh_p);  
  6. }  
  7. void m_dh::comp_k()  
  8. {  
  9.     dh_k=fastpower_comp(dh_f,dh_x,dh_p);  
  10. }  

 

     2、rsa:

        這里講的是裸的rsa算法。

        服務器生成兩個不同的素數pq,計算出模n=p*q,並計算歐拉函數φ(n) = (p-1)(q-1)。服務器再在1到φ(n) 之間生成一個與φ(n)互質的的數e找到另一個數d滿足(e*d)%φ(n)==1

         現在服務器有三個數n、e、d ne的組合為rsa的公鑰,nd為私鑰。服務器將公鑰發給客戶端。在以后的加密解密中,公鑰用於加密和簽名驗證,私鑰用於解密與簽名。

        加密數字K:計算C=(K^e)%n,C即為加密后的數據 解密C得到K:K=(C^d)%n 

        簽名采用相反的方式,即服務器用私鑰加密,客戶端用公鑰解密,驗證解密后的數據。

        然而ssh-rsa使用的是 RSASSA-PKCS1-v1_5 scheme標准,他還含有一些其他的填充值,實際實現的時候需要考慮周全。

[cpp]  view plain copy
 
  1. class m_rsa  
  2. {  
  3. public:  
  4.     Integer rsa_e;  
  5.     Integer rsa_n;  
  6.     void set_e_and_n(Integer e,Integer n)  
  7.     {  
  8.         rsa_e=e;  
  9.         rsa_n=n;  
  10.     }  
  11.     Integer comp_rsa_result(Integer num);  
  12. };  
  13.   
  14. Integer m_rsa::comp_rsa_result(Integer num)  
  15. {  
  16.     return fastpower_comp(num,rsa_e,rsa_n);  
  17. }  

    3、sha256

        散列算法沒什么可講的,主要注意sha256的密文長度是64位的16進制,在進行rsa加解密以及計算sessionid的時候一定要注意關於長度的問題。使用的重點在於需要哪些值以什么樣的一種組合方式去參與hash運算。

        我就直接使用cryptopp的hash算法實現了:

[cpp]  view plain copy
 
  1. class m_sha  
  2. {  
  3. public:  
  4.     string encode_sha1(string data);  
  5.     string encode_sha256(string data);  
  6. };  
  7.   
  8. string m_sha::encode_sha256(string data)  
  9. {  
  10.     string hash;   
  11.     SHA256 sha256;   
  12.     HashFilter hash_filter (sha256);   
  13.   
  14.     hash_filter.Attach(new HexEncoder(new StringSink(hash), false));   
  15.     hash_filter.Put((byte *)data.c_str(),data.length());  
  16.     hash_filter.MessageEnd();   
  17.   
  18.     return hash;   
  19. }  

 

 

 

    詳細過程:

    基本的算法了解了就可以來看diffie-hellman-group-exchange-sha256的整個過程了。

    

    整個交換過程有5個數據包:按順序分別是1、dh key exchange init;2、dh key exchange reply;3、dh gex init 4、dhgex reply 5、new keys

    1、dh key exchange init(30)

    客戶端告訴服務器開始DH交換。

 

    2、dh key exchange reply(31)

    服務器將生成的P和G發給客戶端。

 

3、dh gex init(32)

    客戶端收到服務器發過來的P和G后,自己計算出e返回給客戶端

 

4、dh gex reply(33)

    服務器收到客戶端的e后,根據算法計算出秘鑰值K。然后使用sha256算法將一些已知信息hash加密為H(具體過程后面會提到),並用rsa將hash值簽名。最后發送rsa的公鑰、dh的f值、rsa簽名后的hash信息發回客戶端。

 

5、new keys(21)

    客戶端根據服務器發回的f計算出同樣的k值,並根據同樣的已有信息hash計算得到H后使用服務器發來的rsa公鑰校驗服務器發回的hash值的簽名,根據得到的hash值H即會話用的session_id,再進行特定的hash運算(參見下文)即可得到以后用於數據加密的秘鑰。如果校驗無誤,返回new key(21),表示秘鑰交換的過程完畢,以后的數據都將由所得秘鑰進行加密。

 

 

    Hsession_id的計算: 

    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計算結果

    將以上內容按順序進行拼接,不要夾雜或尾隨多余字符。將拼接后的字符串進行sha256計算出結果H。這個H就是session_id(會話第一次的秘鑰交換生成的的H才是session_id,以后如果還要進行秘鑰交換,session_id不會改變)。

    

 

    加密秘鑰計算:

    這里的加密秘鑰指的是以后數據通信所用的秘鑰,一般用aes算法。

    計算方式:hash(K,H,單個字符,session_id);

    單個字符指的是單個大寫的ASCII字母,根據不同的加密秘鑰選擇不同的字符來計算。

字母 秘鑰
'A' 客戶端到服務器的初始IV(CBC)
'B' 服務器到客戶端的初始IV
'C' 客戶端到服務器的加密秘鑰(數據加解密秘鑰)
'D' 服務器到客戶端的加密秘鑰
'E' 客戶端到服務器的完整性秘鑰(HMAC)
'F' 服務器到客戶端的完整性秘鑰

    哈希計算得到字符串RE,如果我么想要的秘鑰長度比RE長,則在RE后面繼續加上一個hash值:hash(K,H,RE)成為一個加長的RE。還不夠繼續加上hash(K,H,RE),依次類推

 

 

    ssh秘鑰交換的過程就告一段落了。筆者在網上找不到合適資料,尤其是這些關於diffie-hellman-group-exchange-sha 的很多細節,自己苦逼了很長時間(本來打算兩下擼完去學其他的)終於完成了了。希望給想要自己實現該算法的朋友給予幫助。如還遇到其他的問題可Q我(WCHRT)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM