用OpenSSL编写SSL,TLS程序
http://zhoulifa.bokee.com/6134045.html
http://blog.sina.com.cn/s/blog_86ca13bb0100vaph.html
http://blog.chinaunix.net/uid-26575352-id-3048856.html
一、簡介:
SSL(SecureSocket Layer)是netscape公司提出的主要用于web的安全通信標準,分為2.0版和3.0版.TLS(TransportLayer Security)是IETF的TLS工作組在SSL3.0基礎之上提出的安全通信標準,目前版本是1.0,即RFC2246.SSL/TLS提供的安全機制可以保證應用層數據在互聯網絡傳輸不 被監聽,偽造和竄改.
openssl(www.openssl.org)是sslv2,sslv3,tlsv1的一份完整實現,內部包含了大量加密算法程序.其命令行提供了豐富的加密,驗證,證書生成等功能,甚至可以用其建立一個完整的CA.與其同時,它也提供了一套完整的庫函數,可用開發用SSL/TLS的通信程序.Apache的https兩種版本mod_ssl和apachessl均基于它實現的.openssl繼承于ssleay,并做了一定的擴展,當前的版本是0.9.5a.
openssl的缺點是文檔太少,連一份完整的函數說明都沒有,man page也至今沒做完整:-(,如果想用它編程序,除了熟悉已有的文檔(包括ssleay,mod_ssl,apachessl的文檔)外,可以到它的maillist上找相關的帖子,許多問題可以在以前的文章中找到答案.
基于OpenSSL的程序都要遵循以下幾個步驟:
(1 ) OpenSSL初始化
在使用OpenSSL之前,必須進行相應的協議初始化工作,這可以通過下面的函數實現:
int SSL_library_init(void);
(2 ) 選擇會話協議
在利用OpenSSL開始SSL會話之前,需要為客戶端和服務器制定本次會話采用的協議,目前能夠使用的協議包括TLSv1.0、SSLv2、SSLv3、SSLv2/v3。
需要注意的是,客戶端和服務器必須使用相互兼容的協議,否則SSL會話將無法正常進行。
(3 ) 創建會話環境
在OpenSSL中創建的SSL會話環境稱為CTX,使用不同的協議會話,其環境也不一樣的。
申請SSL會話環境的OpenSSL函數是:
SSL_CTX *SSL_CTX_new(SSL_METHOD * method);
當SSL會話環境申請成功后,還要根據實際的需要設置CTX的屬性,通常的設置是指定SSL握手階段證書的驗證方式和加載自己的證書。
制定證書驗證方式的函數是:
int SSL_CTX_set_verify(SSL_CTX *ctx,intmode,int(*verify_callback),int(X509_STORE_CTX *));
為SSL會話環境加載CA證書的函數是:
SSL_CTX_load_verify_location(SSL_CTX *ctx,const char *Cafile,constchar *Capath);
為SSL會話加載用戶證書的函數是:
SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file,inttype);
為SSL會話加載用戶私鑰的函數是:
SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx,const char* file,inttype);
在將證書和私鑰加載到SSL會話環境之后,就可以調用下面的函數來驗證私鑰和證書是否相符:
int SSL_CTX_check_private_key(SSL_CTX *ctx);
(4) 建立SSL套接字
SSL套接字是建立在普通的TCP套接字基礎之上,在建立SSL套接字時可以使用下面的一些函數:
SSL *SSl_new(SSL_CTX *ctx);??? //申請一個SSL套接字
int SSL_set_fd(SSL *ssl,int fd);??? //綁定讀寫套接字
int SSL_set_rfd(SSL *ssl,int fd);??? //綁定只讀套接字
int SSL_set_wfd(SSL *ssl,int fd);??? //綁定只寫套接字
(5) 完成SSL握手
在成功創建SSL套接字后,客戶端應使用函數SSL_connect( )替代傳統的函數connect( )來完成握手過程:
int SSL_connect(SSL *ssl);
而對服務器來講,則應使用函數SSL_ accept ( )替代傳統的函數accept ( )來完成握手過程:
int SSL_accept(SSL *ssl);
握手過程完成之后,通常需要詢問通信雙方的證書信息,以便進行相應的驗證,這可以借助于下面的函數來實現:
X509 *SSL_get_peer_certificate(SSL *ssl);
該函數可以從SSL套接字中提取對方的證書信息,這些信息已經被SSL驗證過了。
X509_NAME *X509_get_subject_name(X509 *a);
該函數得到證書所用者的名字。
(6) 進行數據傳輸
當SSL握手完成之后,就可以進行安全的數據傳輸了,在數據傳輸階段,需要使用SSL_read( )和SSL_write()來替代傳統的read( )和write( )函數,來完成對套接字的讀寫操作:
int SSL_read(SSL *ssl,void *buf,int num);
int SSL_write(SSL *ssl,const void *buf,int num);
(7 ) 結束SSL通信
當客戶端和服務器之間的數據通信完成之后,調用下面的函數來釋放已經申請的SSL資源:
int SSL_shutdown(SSL *ssl);??? //關閉SSL套接字
void SSl_free(SSL *ssl);??? //釋放SSL套接字
void SSL_CTX_free(SSL_CTX *ctx);???? //釋放SSL會話環境
客戶端與服務端編程框架:
程序分為兩部分,客戶端和服務器端,我們的目的是利用SSL/TLS的特性保證通信雙方能夠互相驗證對方身份(真實性),并保證數據的完整性, 私密性.
1.客戶端程序的框架為:
/*生成一個SSL結構*/
meth = SSLv23_client_method();
ctx = SSL_CTX_new (meth);
ssl = SSL_new(ctx);
/*下面是正常的socket過程*/
fd = socket();
connect();
/*把建立好的socket和SSL結構聯系起來*/
SSL_set_fd(ssl,fd);
/*SSL的握手過程*/
SSL_connect(ssl);
/*接下來用SSL_write(), SSL_read()代替原有的write(),read()即可*/
SSL_write(ssl,”Hello world”,strlen(“Hello World!”));
2.服務端程序的框架為:
/*生成一個SSL結構*/
meth = SSLv23_server_method();
ctx = SSL_CTX_new (meth);
ssl = SSL_new(ctx);
/*下面是正常的socket過程*/
fd = socket();
bind();
listen();
accept();
/*把建立好的socket和SSL結構聯系起來*/
SSL_set_fd(ssl,fd);
/*SSL的握手過程*/
SSL_connect(ssl);
/*接下來用SSL_write(), SSL_read()代替原有的write(),read()即可*/
SSL_read (ssl, buf, sizeof(buf));
根據RFC2246(TLS1.0)整個TLS(SSL)的流程如下:
Client????????????????????????????????????????????????Server
ClientHello?????????? ——–>
????????????????????????????????????????????????ServerHello
???????????????????????????????????????????????? Certificate*
??????????????????????????????????????????ServerKeyExchange*
????????????????????????????????????????????CertificateRequest*
?????????????????????????? <——–??????ServerHelloDone
Certificate*
ClientKeyExchange
CertificateVerify*
[ChangeCipherSpec]
Finished?????????????? ——–>
???????????????????????????????????????????? [ChangeCipherSpec]
????????????????????????????<——–???????????? Finished
Application Data?? <——->???? Application Data
對程序來說,openssl將整個握手過程用一對函數體現,即客戶端的SSL_connect和服務端的SSL_accept.而后的應用層數據交換則用SSL_read和 SSL_write來完成.
二、證書文件生成
除將程序編譯成功外,還需生成必要的證書和私鑰文件使雙方能夠成功驗證對方,步驟如下:
1.首先要生成服務器端的私鑰(key文件):
openssl genrsa -des3 -out server.key 1024
運行時會提示輸入密碼,此密碼用于加密key文件(參數des3便是指加密算法,當然也可以選用其他你認為安全的算法.),以后每當需讀取此文 件(通過openssl提供的命令或API)都需輸入口令.如果覺得不方便,也可以去除這個口令,但一定要采取其他的保護措施!
去除key文件口令的命令:
openssl rsa -in server.key -out server.key
2.openssl req -new -key server.key -out server.csr
生成Certificate Signing Request(CSR),生成的csr文件交給CA簽名后形成服務端自己的證書.屏幕上將有提示,依照其指示一步一步輸入要 求的個人信息即可.
3.對客戶端也作同樣的命令生成key及csr文件:
openssl genrsa -des3 -out client.key 1024
openssl req -new -key client.key -out client.csr
4.CSR文件必須有CA的簽名才可形成證書.可將此文件發送到verisign等地方由它驗證,要交一大筆錢,何不自己做CA呢.
首先生成CA的key文件:
openssl -des3 -out ca.key 1024
在生成CA自簽名的證書:
openssl req -new -x509 -key ca.key -out ca.crt
如果想讓此證書有個期限,如一年,則加上”-days 365”.
(“如果非要為這個證書加上一個期限,我情愿是..一萬年”)
5.用生成的CA的證書為剛才生成的server.csr,client.csr文件簽名:
可以用openssl中CA系列命令,但不是很好用(也不是多難,唉,一言難盡),一篇文章中推薦用mod_ssl中的sign.sh腳本,試了一下,確實方便了不 少,如果ca.csr存在的話,只需:
./sigh.sh server.csr
./sign.sh client.csr
相應的證書便生成了(后綴.crt).
現在我們所需的全部文件便生成了.
其實openssl中還附帶了一個叫CA.pl的文件(在安裝目錄中的misc子目錄下),可用其生成以上的文件,使用也比較方便,但此處就不作介紹了.
三、需要了解的一些函數:
1.int????SSL_CTX_set_cipher_list(SSL_CTX *,const char *str);
SSL_CTX_set_cipher_list() sets the list of available ciphers for?ctx?using the control string?str. The format of the string is described in?ciphers. The list of ciphers is inherited by all?ssl?objects created from?ctx.
根據SSL/TLS規范,在ClientHello中,客戶端會提交一份自己能夠支持的加密方法的列表,由服務端選擇一種方法后在ServerHello中通知客戶端, 從而完成加密算法的協商.
如果服務端只設置了一種加密套件,那么客戶端要么接受要么返回錯誤。加密套件的選擇是由服務端做出的。
可用的算法為:
EDH-RSA-DES-CBC3-SHA
EDH-DSS-DES-CBC3-SHA
DES-CBC3-SHA
DHE-DSS-RC4-SHA
IDEA-CBC-SHA
RC4-SHA
RC4-MD5
EXP1024-DHE-DSS-RC4-SHA
EXP1024-RC4-SHA
EXP1024-DHE-DSS-DES-CBC-SHA
EXP1024-DES-CBC-SHA
EXP1024-RC2-CBC-MD5
EXP1024-RC4-MD5
EDH-RSA-DES-CBC-SHA
EDH-DSS-DES-CBC-SHA
DES-CBC-SHA
EXP-EDH-RSA-DES-CBC-SHA
EXP-EDH-DSS-DES-CBC-SHA
EXP-DES-CBC-SHA
EXP-RC2-CBC-MD5
EXP-RC4-MD5
下面的openssl命令可以列出支持的所有算法,并按強度排序:
#?openssl?ciphers?-v?‘ALL:!ADH:@STRENGTH’
DHE-RSA-AES256-SHA??????SSLv3?Kx=DH???????Au=RSA??Enc=AES(256)??Mac=SHA1
DHE-DSS-AES256-SHA??????SSLv3?Kx=DH???????Au=DSS??Enc=AES(256)??Mac=SHA1
AES256-SHA??????????????SSLv3?Kx=RSA??????Au=RSA??Enc=AES(256)??Mac=SHA1
EDH-RSA-DES-CBC3-SHA????SSLv3?Kx=DH???????Au=RSA??Enc=3DES(168)?Mac=SHA1
EDH-DSS-DES-CBC3-SHA????SSLv3?Kx=DH???????Au=DSS??Enc=3DES(168)?Mac=SHA1
DES-CBC3-SHA????????????SSLv3?Kx=RSA??????Au=RSA??Enc=3DES(168)?Mac=SHA1
DES-CBC3-MD5????????????SSLv2?Kx=RSA??????Au=RSA??Enc=3DES(168)?Mac=MD5
DHE-RSA-AES128-SHA??????SSLv3?Kx=DH???????Au=RSA??Enc=AES(128)??Mac=SHA1
DHE-DSS-AES128-SHA??????SSLv3?Kx=DH???????Au=DSS??Enc=AES(128)??Mac=SHA1
AES128-SHA??????????????SSLv3?Kx=RSA??????Au=RSA??Enc=AES(128)??Mac=SHA1
DHE-DSS-RC4-SHA?????????SSLv3?Kx=DH???????Au=DSS??Enc=RC4(128)??Mac=SHA1
RC4-SHA?????????????????SSLv3?Kx=RSA??????Au=RSA??Enc=RC4(128)??Mac=SHA1
RC4-MD5?????????????????SSLv3?Kx=RSA??????Au=RSA??Enc=RC4(128)??Mac=MD5
RC2-CBC-MD5?????????????SSLv2?Kx=RSA??????Au=RSA??Enc=RC2(128)??Mac=MD5
RC4-MD5?????????????????SSLv2?Kx=RSA??????Au=RSA??Enc=RC4(128)??Mac=MD5
RC4-64-MD5??????????????SSLv2?Kx=RSA??????Au=RSA??Enc=RC4(64)???Mac=MD5
EXP1024-DHE-DSS-RC4-SHA?SSLv3?Kx=DH(1024)?Au=DSS??Enc=RC4(56)???Mac=SHA1?export
EXP1024-RC4-SHA?????????SSLv3?Kx=RSA(1024)?Au=RSA??Enc=RC4(56)???Mac=SHA1?export
EXP1024-DHE-DSS-DES-CBC-SHA?SSLv3?Kx=DH(1024)?Au=DSS??Enc=DES(56)???Mac=SHA1?export
EXP1024-DES-CBC-SHA?????SSLv3?Kx=RSA(1024)?Au=RSA??Enc=DES(56)???Mac=SHA1?export
EXP1024-RC2-CBC-MD5?????SSLv3?Kx=RSA(1024)?Au=RSA??Enc=RC2(56)???Mac=MD5??export
EXP1024-RC4-MD5?????????SSLv3?Kx=RSA(1024)?Au=RSA??Enc=RC4(56)???Mac=MD5??export
EDH-RSA-DES-CBC-SHA?????SSLv3?Kx=DH???????Au=RSA??Enc=DES(56)???Mac=SHA1
EDH-DSS-DES-CBC-SHA?????SSLv3?Kx=DH???????Au=DSS??Enc=DES(56)???Mac=SHA1
DES-CBC-SHA?????????????SSLv3?Kx=RSA??????Au=RSA??Enc=DES(56)???Mac=SHA1
DES-CBC-MD5?????????????SSLv2?Kx=RSA??????Au=RSA??Enc=DES(56)???Mac=MD5
EXP-EDH-RSA-DES-CBC-SHA?SSLv3?Kx=DH(512)??Au=RSA??Enc=DES(40)???Mac=SHA1?export
EXP-EDH-DSS-DES-CBC-SHA?SSLv3?Kx=DH(512)??Au=DSS??Enc=DES(40)???Mac=SHA1?export
EXP-DES-CBC-SHA?????????SSLv3?Kx=RSA(512)?Au=RSA??Enc=DES(40)???Mac=SHA1?export
EXP-RC2-CBC-MD5?????????SSLv3?Kx=RSA(512)?Au=RSA??Enc=RC2(40)???Mac=MD5??export
EXP-RC4-MD5?????????????SSLv3?Kx=RSA(512)?Au=RSA??Enc=RC4(40)???Mac=MD5??export
EXP-RC2-CBC-MD5?????????SSLv2?Kx=RSA(512)?Au=RSA??Enc=RC2(40)???Mac=MD5??export
EXP-RC4-MD5?????????????SSLv2?Kx=RSA(512)?Au=RSA??Enc=RC4(40)???Mac=MD5??export
這些算法按一定優先級排列,如果不作任何指定,將選用DES-CBC3-SHA.用SSL_CTX_set_cipher_list可以指定自己希望用的算法(實際上只是 提高其優先級,是否能使用還要看對方是否支持).
我們在程序中選用了RC4做加密,MD5做消息摘要(先進行MD5運算,后進行RC4加密).即
SSL_CTX_set_cipher_list(ctx,”RC4-MD5”);
在消息傳輸過程中采用對稱加密(比公鑰加密在速度上有極大的提高),其所用秘鑰(shared secret)在握手過程中中協商(每次對話過程均不同, 在一次對話中都有可能有幾次改變),并通過公鑰加密的手段由客戶端提交服務端.
SSL_set_cipher_list
int SSL_set_cipher_list(SSL *ssl, const char *str);
SSL_set_cipher_list() sets the list of ciphers only for?ssl.
RETURN VALUES
SSL_CTX_set_cipher_list() and SSL_set_cipher_list() return 1 if any cipher could be selected and 0 on complete failure.
2.void SSL_CTX_set_verify(SSL_CTX *ctx,int mode,int (*callback)(int, X509_STORE_CTX *));
缺省mode是SSL_VERIFY_NONE,如果想要驗證對方的話,便要將此項變成SSL_VERIFY_PEER.SSL/TLS中缺省只驗證server,如果沒有設置 SSL_VERIFY_PEER的話,客戶端連證書都不會發過來.
3.int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile,const char *CApath);
要驗證對方的話,當然裝要有CA的證書了,此函數用來便是加載CA的證書文件的.
4.int SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, int type);
加載自己的證書文件.
5.int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type);
加載自己的私鑰,以用于簽名.
6.int SSL_CTX_check_private_key(SSL_CTX *ctx);
調用了以上兩個函數后,自己檢驗一下證書與私鑰是否配對.
7.void RAND_seed(const void *buf,int num);
在win32的環境中client程序運行時出錯(SSL_connect返回-1)的一個主要機制便是與UNIX平臺下的隨機數生成機制不同(握手的時候用的到).具體描述可見mod_ssl的FAQ.解決辦法就是調用此函數,其中buf應該為一隨機的字符串,作為”seed”.
還可以采用一下兩個函數:
void RAND_screen(void);
int RAND_event(UINT, WPARAM, LPARAM);
其中RAND_screen()以屏幕內容作為”seed”產生隨機數,RAND_event可以捕獲windows中的事件(event),以此為基礎產生隨機數.如果一直有 用戶干預的話,用這種辦法產生的隨機數能夠”更加隨機”,但如果機器一直沒人理(如總停在登錄畫面),則每次都將產生同樣的數字.
這幾個函數都只在WIN32環境下編譯時有用,各種UNIX下就不必調了.
大量其他的相關函數原型,見crypto\rand\rand.h.
8.OpenSSL_add_ssl_algorithms()或SSLeay_add_ssl_algorithms()
其實都是調用int SSL_library_init(void)
進行一些必要的初始化工作,用openssl編寫SSL/TLS程序的話第一句便應是它.
9.void????SSL_load_error_strings(void );
如果想打印出一些方便閱讀的調試信息的話,便要在一開始調用此函數.
10.void ERR_print_errors_fp(FILE *fp);
如果調用了SSL_load_error_strings()后,便可以隨時用ERR_print_errors_fp()來打印錯誤信息了.
11.X509 *SSL_get_peer_certificate(SSL *s);
握手完成后,便可以用此函數從SSL結構中提取出對方的證書(此時證書得到且已經驗證過了)整理成X509結構.
12.X509_NAME *X509_get_subject_name(X509 *a);
得到證書所有者的名字,參數可用通過SSL_get_peer_certificate()得到的X509對象.
13.X509_NAME *X509_get_issuer_name(X509 *a)
得到證書簽署者(往往是CA)的名字,參數可用通過SSL_get_peer_certificate()得到的X509對象.
14.char *X509_NAME_oneline(X509_NAME *a,char *buf,int size);
將以上兩個函數得到的對象變成字符型,以便打印出來.
15.SSL_METHOD的構造函數,包括
SSL_METHOD *TLSv1_server_method(void);????/* TLSv1.0 */
SSL_METHOD *TLSv1_client_method(void);????/* TLSv1.0 */
SSL_METHOD *SSLv2_server_method(void);????/* SSLv2 */
SSL_METHOD *SSLv2_client_method(void);????/* SSLv2 */
SSL_METHOD *SSLv3_server_method(void);????/* SSLv3 */
SSL_METHOD *SSLv3_client_method(void);????/* SSLv3 */
SSL_METHOD *SSLv23_server_method(void);????/* SSLv3 but can rollback to v2 */
SSL_METHOD *SSLv23_client_method(void);????/* SSLv3 but can rollback to v2 */
在程序中究竟采用哪一種協議(TLSv1/SSLv2/SSLv3),就看調哪一組構造函數了.
四:程序源代碼:
服務器端:
客戶端:
編譯程序用下列命令:
gcc -Wall ssl-client.c -o client -L /usr/local/openssl/lib/ -Wl,-R /usr/local/openssl/lib/ -lssl -lcrypto
gcc -Wall ssl-server.c -o server -L /usr/local/openssl/lib/?-Wl,-R /usr/local/openssl/lib/ -lssl -lcrypto
運行程序用如下命令:
./server 7838 1 127.0.0.1 cacert.pem privkey.pem
./client 127.0.0.1 7838
用下面這兩個命令產生上述cacert.pem和privkey.pem文件:
openssl genrsa -out privkey.pem 2048
openssl req -new -x509 -key privkey.pem -out cacert.pem -days 1095
總結
以上是生活随笔為你收集整理的用OpenSSL编写SSL,TLS程序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 7旬老人不开空调高烧至40度进ICU 央
- 下一篇: Apollo进阶课程㊴丨Apollo安装