1、 安全算法在 P2P 聊天程序中的应用1实验目的本实验主要在 P2P 聊天程序中添加 DES 和 SHA 算法以使聊天过程中传输的数据是安全的。练习本实验的目的有以下几个方面:(1)掌握 WINDOWS SOCKET API 编程,能比较熟练的使用 SOCKET API 函数,深刻理解 C/S 模式;(2)掌握应用程序开发的一般流程;(3)另一方面深刻理解 DES 和 SHA 加密算法的原理和应用场合。2程序流程图由于本软件无论客户端还是服务端都是同样的程序,因而软件的源代码的主要流程图与经典的客户端/服务器模式的流程图有些区别。下面给出了程序大致流程图:开始下面对上面的流程图作如下解释:程序
2、启动后,先创建侦听套接字,然后Socket()Bind()Listen()Connect()Accept()Recv()Send()Close()没有消息创建线程创建线程未连接成功连接成功绑定侦听套接字,接着进行侦听,上述步骤是典型服务器端的步骤。接着,创建创建一个客户端套接字,并与从窗口获取的服务器 IP 和端口进行连接。该步骤是典型客户端步骤。如果连接失败,说明本机作为服务器端,接着进行典型服务器端的操作 Accept(),有连接消息则建立接收和发送线程。如果在Connect()时是成功的,则说明本机是客户机,接着进行的客户端的典型操作(即建立接收和发送线程) 。通过上述步骤将客户端和服务
3、器端的融合,就实现了客户端和服务器端的软件是同一个软件。而加解密算法分别添加到发送和接收线程调用的函数中。3程序详细设计1.建立 MFC 工程中后,主要添加的文件及相关文件的解释建立 MFC 工程后,主要添加了 MySocket.cpp, Des.cpp , Sha.cpp , sha1.cpp 以及对应的文件和 Stdin.h。下面对这些文件中的主要函数进行解释:MySocket.cpp 和 MySocket.h 文件:文件中主要定义了了 MySocket 类,该类继承 CSocket 类,并对以下函数进行重载:void CMySocket:OnReceive(int nErrorCode)
4、 MessageBox(NULL,“有客户端信息“,“,MB_OKCANCEL);CSocket:OnReceive(nErrorCode);void CMySocket:OnConnect(int nErrorCode) MessageBox(NULL,“有客户端连接“,“,MB_OKCANCEL);CSocket:OnConnect(nErrorCode);void CMySocket:Init(CChatDlg * dlg)dlg=dlg;Des.cpp 及 Des.h 文件:文件主要实现了 DES 加解密算法,并提供了一个统一的接口函数。bool Des_Go(char *Out, c
5、har *In, long datalen, const char *Key, int keylen, bool Type)该接口函数中参数说明:Out 指向加解密后的字符串存放的位置;In 指向需要要加解密字符串存放的位置;Datalen 表示要加解密字符串的数据长度;Key 表示加解密时所要用的密钥;Keylen 表示密钥长度;Type 表示进行操作的类型:ENCRYPT 表示加密,DECRYPT 表示解密。Sha.cpp 及 Sha.h 文件:文件主要通过调用 sha1.cpp 中的函数来实现 SHA 算法,并提供一个统一的接口函数。void shaEncrypt(char *input
6、Buff,uint8_t *Message_Digest);该接口函数中参数说明:inputBuff 指向需要进行摘要操作的字符串。Message_Digest 指向进行摘要操作之后存放摘要的位置。Sha1.cpp,sha.h 以及 Stdin.h 文件:上面三个文件都是为 Sha.cpp 服务的。其中 Stdin.h 中主要是对进行一些数据类型的重新定义,使人在阅读代码时更加直观。Sha1.cpp 和 Sha1.h 文件定义了一些数据结构及一些函数等:typedef struct SHA1Contextuint32_t Intermediate_HashSHA1HashSize/4; /*
7、Message Digest */uint32_t Length_Low; /* Message length in bits */uint32_t Length_High; /* Message length in bits */* Index into message block array */int_least16_t Message_Block_Index;uint8_t Message_Block64; /* 512-bit message blocks */int Computed; /* Is the digest computed? */int Corrupted; /* I
8、s the message digest corrupted? */ SHA1Context;上述结构将会控制上下文消息#define SHA1HashSize 20SHA1HashSize 表示进行摘要操作后摘要的长度。int SHA1Reset( SHA1Context *);该函数主要对数据初始化操作。int SHA1Input( SHA1Context *,const uint8_t *,unsigned int);该函数接收单位长度为 8 字节倍数的消息int SHA1Result( SHA1Context *,uint8_t Message_DigestSHA1HashSize);
9、该函数将会返回一个 160 比特的消息摘要队列或者输出计算错误2.MFC 框架中主要添加代码是:(1)OnInitDialog()函数BOOL CChatDlg:OnInitDialog()CDialog:OnInitDialog();./ -/canExit 变量为 TRUE 时表示聊天窗口是关闭的canExit=FALSE;/未建立客户端套接字时,执行 IF 中语句if(clientSocket=NULL)/对话框中端口号初始设置成 5678GetDlgItem(IDC_PORT)-SetWindowText(“5678“);/对话框中 IP 号初始设置成 127.0.0.1GetDlgI
10、tem(IDC_IP)-SetWindowText(“127.0.0.1“);/对话框中 IDOK 标识的按钮命名为开启服务GetDlgItem(IDOK)-SetWindowText(“开启服务.“);/对话框中 IDC_SEND 标识的按钮置无效GetDlgItem(IDC_SEND)-EnableWindow(FALSE);else.return FALSE;OnInitDialog()添加代码主要是程序启动后对界面进行的相应控件显示数值初始化操作(2)OnOK()函数 void CChatDlg:OnOK() UpdateData(true);CString temp;GetDlgIt
11、em(IDOK)-GetWindowText(temp);if(temp.Find(“开启服务“)!=-1)CString sPort;GetDlgItem(IDC_PORT)-GetWindowText(sPort);port=atoi(LPCSTR)sPort); / 端口m_IP.GetWindowText(ipAddr);/ IP 地址if(Listening()clientSocket=socket(AF_INET, SOCK_STREAM, 0);if(clientSocket= INVALID_SOCKET) GetDlgItem(IDC_MESSAGE)-SetWindowTe
12、xt(“创建 Socket 失败!“);closesocket(listeningSocket);return;/设置客户端要同步的服务器信息sockaddr_in serverAddr;serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(port);serverAddr.sin_addr.s_addr = inet_addr(ipAddr); GetDlgItem(IDOK)-EnableWindow(FALSE);/连接服务器 if(connect(clientSocket, (sockaddr*)GetDlgItem(I
13、DC_MESSAGE)-SetWindowText(“连接失败,等待客户端连接.“);GetDlgItem(IDOK)-SetWindowText(“等待连接.“);/启动一线程来处理客户端连接请求AfxBeginThread(ServiceAccept,this);return;GetDlgItem(IDC_MESSAGE)-SetWindowText(“连接成功,可以发送信息.“);GetDlgItem(IDOK)-SetWindowText(“发送信息“);GetDlgItem(IDOK)-EnableWindow(TRUE);/产生公钥和密钥/启动一线程来处理客户端连接请求AfxBeg
14、inThread(ServiceAccept,this);/* 连接请求后,启动线程收发数据GetDlgItem(IDC_SEND)-EnableWindow(TRUE);AfxBeginThread(ClientReceiveNetMessage,this);AfxBeginThread(ClientSendNetMessage,this);return;if(m_hEventSend)SetEvent(m_hEventSend); 该函数是点击 OnOK 键后进行的响应,由于该开始时用于进行服务器与客户端进行通信连接,连接好后,有变成发送消息的按钮。所以开始就检查是否是“开启服务”字样。如
15、果是则按照本文档第二部分程序流程图进行的流程执行,如果不是“开启服务”字样,则表明客户端和服务端已近连接好,此时,设置事件按钮,从而使唤醒发送线程。(3)Listening()函数BOOL CChatDlg:Listening()sockaddr_in serverAddr;serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(port);serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); int len=sizeof(SOCKADDR);if(listeningSocket = so
16、cket(AF_INET, SOCK_STREAM, 0) = INVALID_SOCKET) GetDlgItem(IDC_MESSAGE)-SetWindowText(“创建 Socket 失败.“);return FALSE;if(bind(listeningSocket, (sockaddr*)closesocket(listeningSocket);listeningSocket=NULL;return FALSE;if(listen(listeningSocket, 5) = SOCKET_ERROR) /监听客户端请求GetDlgItem(IDC_MESSAGE)-SetWind
17、owText(“监听客户端失败.“);closesocket(listeningSocket);listeningSocket=NULL;return FALSE;GetDlgItem(IDC_MESSAGE)-SetWindowText(“等待连接 .“);getsockname(listeningSocket, (sockaddr*)return TRUE;Listening()函数中添加的代码主要是实现了服务器端的 3 个操作:套接字建立,绑定本地套接字,侦听(4)ServiceAccept()函数UINT ServiceAccept(LPVOID pParam)/线程入口函数CChat
18、Dlg * dlg=(CChatDlg *)pParam;sockaddr_in saClient;int nLengthAddr = sizeof(SOCKADDR);SOCKET serviceSocket = NULL; /通信 socket 对象serviceSocket = accept(listeningSocket, (sockaddr*)if(serviceSocket = INVALID_SOCKET )if(WSAGetLastError() != WSAEINTR / 重新等待新的连接serviceSocket=NULL;AfxBeginThread(ServiceAcc
19、ept,dlg);return 1;if(dlg-clientSocket=NULL)dlg-clientSocket=serviceSocket;dlg-GetDlgItem(IDC_SEND)-EnableWindow(TRUE);AfxBeginThread(ClientReceiveNetMessage,dlg);AfxBeginThread(ClientSendNetMessage,dlg);/*/dlg-GetDlgItem(IDC_MESSAGE)-SetWindowText(“客户端成功连接到本机.“);dlg-GetDlgItem(IDOK)-SetWindowText(“发
20、送信息“);dlg-GetDlgItem(IDOK)-EnableWindow(TRUE);CString temp;temp.Format(“%u“,ntohs(saClient.sin_port);/ 11798dlg-GetDlgItem(IDC_PORT)-SetWindowText(temp);temp.Format(“%u.%u.%u.%u“,saClient.sin_addr.S_un.S_un_b.s_b1,saClient.sin_addr.S_un.S_un_b.s_b2,saClient.sin_addr.S_un.S_un_b.s_b3,saClient.sin_add
21、r.S_un.S_un_b.s_b4);dlg-GetDlgItem(IDC_IP)-SetWindowText(temp);/ 重新等待新的连接AfxBeginThread(ServiceAccept,dlg);return 1;else/ 重新等待新的连接AfxBeginThread(ServiceAccept,dlg);CChatDlg newDlg(serviceSocket,saClient);newDlg.DoModal();closesocket(serviceSocket);return 0;ServiceAccept()函数主要是实现服务器端接收客户端连接的操作。(5)发送信
22、息线程,主要代码如下:UINT ClientSendNetMessage(LPVOID pParam)CChatDlg * dlg=(CChatDlg *)pParam;while(1)/ -dlg-m_hEventSend=CreateEvent(NULL, TRUE, FALSE, NULL); DWORD ThreadState = WaitForSingleObject(dlg-m_hEventSend, INFINITE);CloseHandle(dlg-m_hEventSend);if(dlg-canExit)break;/ 回复数据CString strMsg;char buf2
23、55;char key=0,2,0,0,9,3,5,1,9,8,0,0,9,1,7;/des 密钥char *str;uint8_t Message_Digest20;if(dlg-m_sendMessage!=“)strMsg =dlg-m_sendMessage;str=strMsg.GetBuffer(256);/DES 加密Des_Go(buf,str, sizeof(str),key, sizeof(key), ENCRYPT);/SHA 摘要生产shaEncrypt(buf,Message_Digest);strMsg=buf;strMsg+=Message_Digest;else
24、strMsg =“IGNORED“;int nLen = strMsg.GetLength();/ -int nBytesSent;/ *?if(nBytesSent = send(dlg-clientSocket, strMsg, nLen, 0) = SOCKET_ERROR) dlg-GetDlgItem(IDC_MESSAGE)-SetWindowText(“发送数据失败,退出!“);dlg-canExit=TRUE;return 1;if (nBytesSent = nLen) / 发送成功dlg-GetDlgItem(IDC_MESSAGE)-SetWindowText(“客户端发
25、送数据成功!“);dlg-m_messageList.InsertString(-1,dlg-m_sendMessage);strMsg=“加密后:“;strMsg+=buf;strMsg+=Message_Digest;dlg-m_messageList.InsertString(-1,strMsg);return 0;发送线程主要是在点击发送按钮时(即 ID 为 OnOK 的按钮)后,收到事件信号时,进行发送的操作。其中,在发送聊天内容之前,先调用 DES 算法进行加密,在调用 SHA算法进行摘要生成。然后再将加密后的内容发送出去。6.接收信息线程,主要代码如下:UINT ClientRe
26、ceiveNetMessage(LPVOID pParam)CChatDlg * dlg=(CChatDlg *)pParam;uint8_t Message_Digest20;uint8_t Message_Digest120;char m_pReadBuf256;char databuff256;char key=0,2,0,0,9,3,5,1,9,8,0,0,9,1,7;/des 密钥while(1)if(dlg-canExit)break;memset(m_pReadBuf,0,256);/接收数据int nBytesReceived;if(nBytesReceived = recv(
27、dlg-clientSocket, m_pReadBuf, 255, 0) = SOCKET_ERROR)dlg-GetDlgItem(IDC_MESSAGE)-SetWindowText(“接收数据失败,断开连接.“);dlg-canExit=TRUE;return 1; if (nBytesReceived 0 | nBytesReceived 6)int i,j;char strCommand9;memset(strCommand,0,9);strncpy(strCommand,m_pReadBuf,7);CString strAckMsg;if (strcmp(strCommand,“
28、IGNORED“) = 0) /数据头 dlg-GetDlgItem(IDC_MESSAGE)-SetWindowText(“接收数据成功,并忽略!“);strAckMsg +=m_pReadBuf+7;elsedlg-m_messageList.InsertString(-1,m_pReadBuf);strAckMsg=m_pReadBuf;j=nBytesReceived;for(i=j-20;iGetDlgItem(IDC_MESSAGE)-SetWindowText(“接收数据成功!“);strAckMsg=“;strAckMsg +=“解密后:“;strAckMsg +=databuff;dlg-m_messageList.InsertString(-1,strAckMsg);else.return 0;