from編程
http://rangercyh.blog.51cto.com/1444712/430652安全
上週幫一個童鞋作一個數字認證的實驗,要求是編程實現一個基於X.509證書認證的過程,唉!可憐我那點薄弱的計算機網絡安全的知識啊!只得惡補一下了。服務器
首先來看看什麼是X.509。所謂X.509實際上是一種很是通用的證書,什麼是證書?唉!這麼說吧!當兩我的須要進行遠程通訊而又不想讓第三我的知 道時就必須創建一種安全措施,由於看不到對方的臉,又不能經過電話直接詢問對方,就得想點別的辦法,好比我設計一個密碼,讓後發短信告訴你,這樣當咱們在 網上交流以前就能夠對一下密碼,暗號之類的。確認後就能夠證實你的身份了。這個證書就至關於這裏的密碼,只是確實比密碼要複雜一些,想了解的朋友能夠 google之~~網絡
那麼什麼是SSL呢?SSL是一種網絡通訊的安全協議,在傳輸層對網絡鏈接進行加密,簡單點說就是提過一種方法使咱們經過網絡進行通訊的安全性獲得提升。而X.509證書認證的過程就能夠經過這個協議來實現。app
好了,知道了這些就然咱們來開始寫這個程序吧!慢着,若是實現這個SSL和X.509呢?咱們貌似仍是不知道啊!好吧,既然是一種加密通訊過程確定 和加密分不開吧,隨便查了一下,SSL涉及到的加密方法就不下數十種,並且極其艱深複雜,難道我要本身實現這個過程?本身構建X.509證書?固然不會, 互聯網就是好啊!讓我發現了存在OpenSSL這麼個東西,讓咱們感謝Eric A. Young和Tim J. Hudson這兩我的吧!由於他們在1995年就開始作我上面想作的事情了,並且寫出了OpenSSL這個沒有太多限制的開放源代碼的軟件包。有了這玩意 什麼事都好辦了。socket
我先從網上下了openssl-0.9.8的壓縮包,而後琢磨了兩個多小時終於明白如何安裝這麼個軟件包了。我把本身的安裝心得記錄下來你們一塊兒分享。tcp
在使用這個軟件包以前須要安裝PERL組件——ActivePerl-5.8.0.806-MSWin32-x86(PERL和OpenSSL網上 有不少免費下載的,這裏我就不給出下載地址了),這個安裝倒很簡單,直接使用安裝包就好了。判斷perl是否安裝成功的方法是經過命令行進入到perl安 裝目錄下的\eg目錄裏,執行perl example.pl命令,若是顯示「Hello from ActivePerl!」,則說明Perl安裝成功。而後咱們開始安裝OpenSSL。ide
一、將openssl.0.9.8.tar.gz解壓到d:盤下。google
二、打開命令行窗口進入d: \openssl-0.9.8目錄下鍵入如下命令:perl Configure VC-WIN32。編碼
三、而後鍵入ms\do_ms命令。
四、而後鍵入nmake –f ms\ntdll.mak會成功編譯不少c文件,若是不成功的話通常就是提示「cl」沒法找到之類的錯誤,這時就須要在系統環境變量PATH里加入VC的bin路徑,這樣才能找到「cl」命令。
五、接着鍵入mkdir c:\caroot,這樣就會在C盤根目錄下生成一個caroot文件夾。
六、而後須要在系統環境變量PATH路徑中加入openssl-0.9.8\out32dll路徑,並在VC(我使用的是VC編譯器)中把 include路徑增長一個:D:\openssl-0.9.8\include,把library路徑增長一個:D:\openssl-0.9.8 \out32dll,將D:\openssl-0.9.8\apps下的openssl.cnf文件拷貝到c:\caroot目錄下。
到這裏能夠說咱們的安裝已經完成了,下面咱們須要生成幾個證書和密鑰,密鑰是用來加密的,證書是用來認證的。好比服務器證書、密鑰,客戶端證書、密 鑰。但這裏咱們漏了一個最重要的證書和密鑰,就是CA證書和密鑰。CA是個什麼東東呢?這個解釋起來很麻煩,他就像一箇中間證實人,證實兩方的身份都是真 實可信的,這樣雙方拿着證書才能相互信任,詳細解釋能夠參看這裏:http://baike.baidu.com/view/23691.htm#7,維基百科的解釋更加詳細,不過是英文的我以爲喜歡英文的朋友能夠看看:http://en.wikipedia.org/wiki/Certificate_authority。
好吧,讓咱們把這些該死的證書密鑰都生成而後開始編碼吧:
在上面C:\caroot目錄下(其實能夠不在這目錄下)輸入以下:
#產生CA自簽名證書
openssl.exe genrsa -out private\ca.key -rand private\.rnd -des 2048
openssl.exe req -new -x509 -days 3650 -key private\ca.key -out private\ca.crt -config openssl.cnf
openssl.exe x509 -in private\ca.crt -noout -text
#產生server的證書過程
openssl.exe genrsa -out private\server.key 1024
openssl.exe req -new -key private\server.key -out newcerts\server.csr -config openssl.cnf
openssl.exe ca -in newcerts\server.csr -cert private\ca.crt -keyfile private\ca.key
-config openssl.cnf -policy policy_anything -out certs\server.crt
openssl.exe x509 -in certs\server.crt -noout -text
#產生proxy的證書過程
openssl.exe genrsa -out private\proxy.key 1024
openssl.exe req -new -key private\proxy.key -out newcerts\proxy.csr -config openssl.cnf
openssl.exe ca -in newcerts\proxy.csr -cert private\ca.crt -keyfile private\ca.key -config openssl.cnf -policy policy_anything -out certs\proxy.crt
openssl.exe x509 -in certs\proxy.crt -noout -text
#產生client的證書過程
openssl.exe genrsa -out private\client.key 1024
openssl.exe req -new -key private\client.key -out newcerts\client.csr -config openssl.cnf
openssl.exe ca -in newcerts\client.csr -cert private\ca.crt -keyfile private\ca.key -config openssl.cnf -policy policy_anything -out certs\client.crt
openssl.exe x509 -in certs\client.crt -noout -text
上面的命令中會給出提示讓用戶輸入一些證書的信息,只要正常輸入就能夠了。執行上述操做後C:\caroot目錄以下:
private目錄以下:
newcerts目錄以下:
certs目錄以下:
其中證書:
ca.crt爲自簽名證書;
server.crt,server.key爲服務器端的證書和私鑰文件;
proxy.crt,proxy.key爲代理服務器端的證書和私鑰文件;
client.crt,client.key爲客戶端的證書和私鑰文件。
亂七八糟的事情終於作完了,下面總算能夠開始編碼了。看了一下實驗要求,要編寫簡單的Client程序和Server程序,實現Client程序與Server程序之間基於X509證書和SSL協議身份認證和通訊加密,服務器可以接收而且顯示客戶端發送來的文字消息。
看來須要兩個程序,一個服務端的,一個客戶端的。
具體編程你們直接看代碼就好了,涉及到socket通訊,其實至關簡單,懂一點點就好了,我這裏就不要王婆賣瓜了,直接上代碼吧。
客戶端程序代碼:
- //client
- #include <winsock2.h>
- #include <conio.h>
- #include <stdio.h>
- #include "openssl/x509.h"
- #include "openssl/ssl.h"
- #include "openssl/err.h"
- #include "openssl/rand.h"
- #define PORT 1111
- #define SERVER "127.0.0.1"
- #define CACERT "ca.crt"
- #define MYCERTF "yuliding.crt"
- #define MYKEYF "yuliding.key"
- #define MSGLENGTH 1024
- int main()
- {
- WSADATA wsadata;
- WSAStartup(MAKEWORD(2,2), &wsadata);
- sockaddr_in sin;
- int seed_int[100]; /*存放隨機序列*/
- SSL*ssl;
- SSL_METHOD *meth;
- SSL_CTX *ctx;
- //SSL初始化
- OpenSSL_add_ssl_algorithms();
- //SSL錯誤信息初始化
- SSL_load_error_strings();
- //建立本次會話所使用的協議
- meth = TLSv1_client_method();
- //申請SSL會話的環境
- ctx = SSL_CTX_new(meth);
- if (NULL == ctx)
- exit(1);
- //設置會話的握手方式並加載CA證書
- SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
- SSL_CTX_load_verify_locations(ctx, CACERT, NULL);
- //加載本身的證書
- if (0 >= SSL_CTX_use_certificate_file(ctx, MYCERTF, SSL_FILETYPE_PEM)) {
- ERR_print_errors_fp(stderr);
- exit(1);
- }
- //加載本身的私鑰
- if (0 >= SSL_CTX_use_PrivateKey_file(ctx, MYKEYF, SSL_FILETYPE_PEM)) {
- ERR_print_errors_fp(stderr);
- exit(1);
- }
- //檢查本身的證書和私鑰是否匹配
- if (!SSL_CTX_check_private_key(ctx)) {
- printf("Private key does not match the certificate public key\n");
- exit(1);
- }
- /*構建隨機數生成機制,WIN32平臺必需*/
- srand((unsigned)time(NULL));
- for (int i = 0; i < 100; i++)
- seed_int[i] = rand();
- RAND_seed(seed_int, sizeof(seed_int));
- //加密方式
- SSL_CTX_set_cipher_list(ctx, "RC4-MD5");
- //處理握手屢次
- SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
- /*如下是正常的TCP socket創建過程 .............................. */
- SOCKET sock;
- printf("Begin tcp socket...\n");
- sock = socket(AF_INET, SOCK_STREAM, 0);
- if (sock == INVALID_SOCKET) {
- printf("SOCKET有問題. \n");
- }
- memset(&sin, '\0', sizeof(sin));
- sin.sin_family = AF_INET;
- sin.sin_addr.s_addr = inet_addr(SERVER); /* Server IP */
- sin.sin_port = htons(PORT); /* Server Port number */
- int icnn = connect(sock, (sockaddr *)&sin, sizeof(sin));
- if (icnn == SOCKET_ERROR) {
- printf("連不上服務器\n", GetLastError());
- exit(1);
- }
- /* TCP 連接已創建.開始 SSL 握手過程.......................... */
- //綁定套接字
- ssl = SSL_new(ctx);
- if (NULL == ssl)
- exit(1);
- if (0 >= SSL_set_fd(ssl, sock)) {
- printf("Attach to Line fail!\n");
- exit(1);
- }
- //SSL握手
- //SSL_connect(ssl);
- int k = SSL_connect(ssl);
- if (0 >= k) {
- printf("%d\n", k);
- printf("SSL connect fail!\n");
- exit(1);
- }
- printf("鏈接服務器成功\n");
- char sendmsg[MSGLENGTH] = "\0";
- char revmsg[MSGLENGTH] = "\0";
- int err = SSL_read(ssl, revmsg, sizeof(revmsg));
- revmsg[err] = '\0';
- printf("%s\n", revmsg);
- while (1) {
- printf("請輸入所要發送的數據:\n");
- scanf("%s", sendmsg);
- SSL_write(ssl, sendmsg, strlen(sendmsg));
- printf("發送消息「 %s 」成功!\n", sendmsg);
- }
- //關閉套接字
- SSL_shutdown(ssl);
- SSL_free(ssl);
- SSL_CTX_free(ctx);
- closesocket(sock);
- WSACleanup();
- getch();
- return 0;
- }
服務端程序代碼:
- //server
- #include <winsock2.h>
- #include <conio.h>
- #include <stdio.h>
- #include <winsock.h>
- #include "openssl/x509.h"
- #include "openssl/ssl.h"
- #include "openssl/err.h"
- #define MSGLENGTH 1024
- #define PORT 1111
- #define CACERT "ca.crt"
- #define SVRCERTF "server.crt"
- #define SVRKEYF "server.key"
- int main()
- {
- WSADATA wsaData;
- WSAStartup(MAKEWORD(2,2), &wsaData);
- SOCKET sock;
- SSL_METHOD *meth;
- SSL_CTX* ctx;
- SSL* ssl;
- //SSL初始化
- OpenSSL_add_ssl_algorithms();
- //SSL錯誤信息初始化
- SSL_load_error_strings();
- //建立本次會話所使用的協議
- meth = TLSv1_server_method();
- //申請SSL會話的環境
- ctx = SSL_CTX_new(meth);
- if (NULL == ctx)
- exit(1);
- //設置會話的握手方式並加載CA證書
- SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
- SSL_CTX_load_verify_locations(ctx, CACERT, NULL);
- //加載服務器端的證書
- if (0 >= SSL_CTX_use_certificate_file(ctx, SVRCERTF, SSL_FILETYPE_PEM)) {
- ERR_print_errors_fp(stderr);
- exit(1);
- }
- //加載服務器端的私鑰
- if (0 >= SSL_CTX_use_PrivateKey_file(ctx, SVRKEYF, SSL_FILETYPE_PEM)) {
- ERR_print_errors_fp(stderr);
- exit(1);
- }
- //檢查服務器端的證書和私鑰是否匹配
- if (!SSL_CTX_check_private_key(ctx)) {
- printf("Private key does not match the certificate public key\n");
- exit(1);
- }
- //加密方式
- SSL_CTX_set_cipher_list(ctx, "RC4-MD5");
- //處理握手屢次
- SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
- /*如下是正常的TCP socket創建過程 .............................. */
- printf("Begin tcp socket...\n");
- sock = socket(AF_INET, SOCK_STREAM, 0);
- if (sock == INVALID_SOCKET) {
- printf("SOCKET有問題. \n");
- return 0;
- }
- sockaddr_in addr;
- memset(&addr, '\0', sizeof(addr));
- addr.sin_family = AF_INET;
- addr.sin_port = htons(PORT); /* Server Port number */
- addr.sin_addr.s_addr = INADDR_ANY;
- //綁定sock
- int nResult = bind(sock, (sockaddr *)&addr, sizeof(addr));
- if (nResult == SOCKET_ERROR) {
- printf("綁定SOCKET有問題. \n");
- return 0;
- }
- printf("服務器啓動成功,端口:%d\n正在等待鏈接\n", PORT);
- /*接受TCP連接*/
- sockaddr_in sa_cli;
- int err = listen(sock, 5);
- if (-1 == err)
- exit(1);
- int client_len = sizeof(sa_cli);
- int ss = accept(sock, (struct sockaddr *) &sa_cli, &client_len);
- if (ss == -1) {
- exit(1);
- }
- closesocket(sock);
- printf("Connection from %d, port %d\n", sa_cli.sin_addr.s_addr, sa_cli.sin_port);
- /* TCP 連接已創建.開始 SSL 握手過程.......................... */
- //綁定套接字
- ssl = SSL_new(ctx);
- if (NULL == ssl)
- exit(1);
- if (0 >= SSL_set_fd(ssl, ss)) {
- printf("Attach to Line fail!\n");
- exit(1);
- }
- //SSL握手
- //SSL_accept(ssl);
- int k = SSL_accept(ssl);
- if (0 >= k) {
- printf("%d\n", k);
- printf("SSL connect fail!\n");
- exit(1);
- }
- //進行信息驗證
- X509 *client_cert;
- client_cert = SSL_get_peer_certificate(ssl);
- printf("發現客戶端嘗試鏈接\n");
- if (client_cert != NULL) {
- printf ("Client certificate:\n");
- //讀取證書subject名並顯示
- char *str = X509_NAME_oneline(X509_get_subject_name(client_cert), 0, 0);
- if (NULL == str) {
- printf("認證出錯!\n");
- exit(1);
- }
- printf("subject: %s\n", str);
- //讀取證書的issuer名並顯示
- str = X509_NAME_oneline(X509_get_issuer_name(client_cert), 0, 0);
- if (NULL == str) {
- printf("證書名爲空\n");
- exit(1);
- }
- printf("issuer: %s\n", str);
- printf("鏈接成功\n");
- X509_free (client_cert);/*如再也不須要,需將證書釋放 */
- OPENSSL_free(str);
- }
- else {
- printf("找不到客戶端的認證證書\n");
- exit(1);
- }
- char buf[MSGLENGTH];
- SSL_write(ssl, "Server is connect to you!\n", strlen("Server is connect to you!\n"));
- printf("Listen to the client: \n");
- while (1) {
- err = SSL_read(ssl, buf, sizeof(buf));
- buf[err] = '\0';
- printf("%s\n", buf);
- }
- //關閉套接字
- SSL_shutdown(ssl);
- SSL_free(ssl);
- SSL_CTX_free(ctx);
- WSACleanup();
- getch();
- return 0;
- }
編寫完後後首先運行服務端程序,而後運行客戶端程序,效果以下:
鏈接成功後服務端的狀態以下:
紅色部分爲我在生成證書文件時輸入的一些信息,具體的你能夠經過我上面講解創建證書時試驗一下。這裏我經過代碼把這些證書信息打印出來是爲了判斷獲取的證書是否正確。
鏈接成功後客戶端的狀態:
這樣,就能夠實現經過客戶端向服務端發送信息,以下:
細心的朋友能夠發現上面我顯示Connection from後面貌似是一串亂碼,其實否則,那是一串IP地址,只不過是反序排列的,我曾經在作SIP TRUNK項目時也遇到過這個問題,感興趣的朋友能夠本身寫個轉義程序把他轉成IP地址試試看。
最後提醒一點,由於個人代碼中把證書和密鑰文件的目錄設爲當前目錄了,因此想要程序正確運行必須把證書文件和密鑰文件拷貝到當前工程的目錄下,以下:
客戶端是同樣的。或者你能夠把目錄改到你生成證書和密鑰的目錄下。
能夠看見,上面的程序有衆多漏洞和缺陷,若是有朋友想使用的話請必定仔細debug一下,把不少判斷的條件加上,這裏我就偷下懶羅~~分享快樂~~