摘要:本文主要介绍如何在Delphi中使用SSL协议.一共分为七个部分:(1)SSL协议是什么?(2)Delphi中如何使用SSL协议?(3)SSL客户端编程实例.(4)SSL服务端编程实例.(5)SSL证书编程实例.(6)中间人欺骗实例.(7)其它.本文作者同时有一个用SSL协议编写的作品叫SSLPROXY,感兴趣的读者可以从作者主页http://www.138soft.org下载.
一:SSL协议是什么? SSL是一种加密传输协议.引用网上一段话:SSL 是Secure socket Layer英文缩写,它的中文意思是安全套接层协议,指使用公钥和私钥技术组合的安全网络通讯协议。SSL协议是网景公司(Netscape)推出的基于 WEB应用的安全协议,SSL协议指定了一种在应用程序协议(如Http、Telenet、NMTP和FTP等)和TCP/IP协议之间提供数据安全性分层的机制,它为TCP/IP连接提供数据加密、服务器认证、消息完整性以及可选的客户机认证,主要用于提高应用程序之间数据的安全性,对传送的数据进行加密和隐藏,确保数据在传送中不被改变,即确保数据的完整性。
二:Delphi中如何使用SSL协议? 要使用SSL协议,有几种方法,一种是根据SSL协议文档自己实现,另外一种是调用第三方的开发库.现在一般的开发者(无论是Delphi还是VC)都是使用开源的OpenSSL库.本文的介绍都是基于OpenSSL的. Delphi中的Indy组件本身就是支持OpenSSL的.例如,对于IdTCPClient,只需要拖一个IdSSLIOHandlerSocket控件到窗口,然后将IdTCPClient的IOHandler指向它即可.不过IdSSLOpenSSLHeaders.pas的声明比较老,是基于OpenSSL 0.9.4的,而0.9.4的DLL文件现在比较少见了.现在一般使用OpenSSL 0.9.7i.(可以打开IdSSLOpenSSLHeaders.pas,搜索版本,老版本是这样的:
OPENSSL_OPENSSL_VERSION_NUMBER = $00904100; OPENSSL_OPENSSL_VERSION_TEXT = 'OpenSSL 0.9.4 09 Aug 1999'; {Do not localize}
0.9.4和0.9.7i的区别是0.9.4的部分函数位于ssleay32.dll中,而0.9.7i则抛弃了此DLL.网上有很多修改过的基于0.9.7i版本的IdSSLOpenSSLHeaders.pas,本文的演示代码主要是基于0.9.7i.
三:SSL客户端编程实例
使用OpenSSL之前,得先装载SSL库.直接调用Load函数即可完成:
if not uIdSSLOpenSSLHeaders.Load then //装载ssl库 begin Application.MessageBox('装载ssl动态库失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end;
然后装载错误字符库和加密算法:
IdSslLoadErrorStrings; IdSslAddSslAlgorithms; //load所有的SSL算法 然后,初始化SSL使用的协议版本和SSL上下文:
methClient := IdSslMethodClientV23;//SSL协议有多个版本,包括SSLv2,SSLv23,SSLv3和TLSv1我们这里使用v23. if methClient = nil then begin Application.MessageBox('建立SSL Client所用的method失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end;
ctxClient := IdSslCtxNew(methClient); //初始化上下文情景. if ctxClient = nil then begin Application.MessageBox('创建客户端SSL_CTX失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end;
上下文情景初始化完毕后,就可以进行socket通信了.首先创建一个正常的socket,连接到目的地址,然后申请一个SSL会话的环境: sslClient := IdSslNew(ctxClient); if sslClient = nil then begin closesocket(ClientSocket); Application.MessageBox('申请SSL会话的环境失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end;
然后绑定套接字: IdSslSetFd(sslClient, ClientSocket); IdSslConnect(sslClient); 成功后就可以调用IdSslRead和IdSslWrite来读写数据了. 下面我们给出一个完整的例子,用于连接安全焦点的论坛首页.此程序用到一个Memo控件和一个按钮控件.注意:此代码没有做过多的错误处理,读者可以自行添加:
procedure TForm1.Button1Click(Sender: TObject); const //Url: string = 'https://www.xfocus.net/bbs/index.php?lang=cn'; Host: string = 'www.xfocus.net'; Port: Word = 443; var Wsa: TWSAData; ctxClient: PSSL_CTX; //SSL上下文 methClient: PSSL_METHOD; sRemoteIP: string;
ClientSocket: TSocket; sAddr: TSockAddr; sslClient: PSSL; pServer_Cert: PX509; pStr: Pchar; buf: array[0..4095] of Char; nRet: integer; strSend: string; begin Button1.Enabled := False;
if WSAStartup($101, Wsa) <> 0 then //初始化Wsock32.dll,MakeWord(2,2), begin Application.MessageBox('初始化Winsock动态库失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end;
if not uIdSSLOpenSSLHeaders.Load then //装载ssl库 begin Application.MessageBox('装载ssl动态库失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end; IdSslLoadErrorStrings; IdSslAddSslAlgorithms; //load所有的SSL算法
methClient := IdSslMethodClientV23; if methClient = nil then begin Application.MessageBox('建立SSL Client所用的method失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end;
ctxClient := IdSslCtxNew(methClient); //初始化上下文情景 if ctxClient = nil then begin Application.MessageBox('创建客户端SSL_CTX失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end;
sRemoteIP := GetIPAddressByHost(Host); if sRemoteIP = '' then begin Application.MessageBox('获取远程IP地址失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end;
ClientSocket := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if ClientSocket = INVALID_SOCKET then begin Application.MessageBox('创建socket失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end;
FillChar(sAddr, Sizeof(TSockAddr), #0); sAddr.sin_family := AF_INET; sAddr.sin_addr.S_addr := inet_addr(Pchar(sRemoteIP)); sAddr.sin_port := htons(Port);
if connect(ClientSocket, sAddr, sizeof(TSockAddr)) = SOCKET_ERROR then begin Application.MessageBox('连接远程服务器失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end;
sslClient := IdSslNew(ctxClient); //申请SSL会话的环境,参数就是前面我们申请的 SSL通讯方式。返回当前的SSL 连接环境的指针。 if sslClient = nil then begin closesocket(ClientSocket); Application.MessageBox('申请SSL会话的环境失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end;
IdSslSetFd(sslClient, ClientSocket); //绑定读写套接字 if IdSslConnect(sslClient) = -1 then begin IdSslFree(sslClient); closesocket(ClientSocket); Application.MessageBox('绑定读写套接字失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end;
Memo1.Lines.Add(Format('SSL连接使用的算法为:%s', [IdSSLCipherGetName(IdSSLGetCurrentCipher(sslClient))]));
pServer_Cert := IdSslGetPeerCertificate(sslClient); if (pServer_Cert <> nil) then begin Memo1.Lines.Add('服务端证书:'); pStr := IdSslX509NameOneline(IdSslX509GetSubjectName(pServer_Cert), nil, 0); if pStr <> nil then Memo1.Lines.Add(Format('主题: %s', [pStr])); pStr := IdSslX509NameOneline(IdSslX509GetIssuerName(pServer_Cert), nil, 0); if pStr <> nil then Memo1.Lines.Add(Format('发行者: %s', [pStr])); end else begin Memo1.Lines.Add('客户端没有证书.'); end; Memo1.Lines.Add('');
strSend := 'GET /bbs/index.php?lang=cn HTTP/1.1'#$D#$A + 'Accept: */*'#$D#$A + 'Accept-Language: zh-cn'#$D#$A + 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)'#$D#$A + 'Host: ' + Host + #$D#$A + 'Connection: Keep-Alive'#$D#$A#$D#$A;
if IdSslWrite(sslClient, @strSend[1], Length(strSend)) <= 0 then begin IdSslFree(sslClient); closesocket(ClientSocket); Application.MessageBox('发送失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end;
FillChar(buf, sizeof(buf), #0); nRet := IdSslRead(sslClient, buf, sizeof(buf)); while nRet > 0 do begin Memo1.Lines.Add(StrPas(Buf)); FillChar(buf, sizeof(buf), #0); Application.ProcessMessages; nRet := IdSslRead(sslClient, buf, sizeof(buf)); end;
IdSslFree(sslClient); closesocket(ClientSocket); if ctxClient <> nil then IdSslCtxFree(ctxClient); WSACleanup; //结束对WSocket32.dll调用 Button1.Enabled := True; end;
四:SSL服务端编程实例
服务端没有什么好讲的,跟客户端不同的地方,在于初始化时需要加载证书,然后Accept后需要再用IdSslAccept关联.关于如何用Delphi生成证书文件,详见下一讲.下面直接贴代码:
var Form1: TForm1; g_Wsa: TWSAData; g_ServerSocketMain: TSocket = INVALID_SOCKET; g_ctxServer: PSSL_CTX = nil; //SSL上下文 g_methServer: PSSL_METHOD = nil; g_DebugCritSec: TRTLCriticalSection; g_hAcceptThread: THandle = 0; g_Start: BOOL = False;
implementation
{$R *.dfm}
function AcceptThread(lp: Pointer): DWORD; stdcall; var nAddrLen: integer; sdClient: TSocket; sAddrs: TSockAddr; nErrorCode: integer; sslServer: PSSL; pClient_Cert: PX509; pStr: Pchar; nRet: integer; buf: array[0..4095] of Char; strBody, strSend: string; begin Result := 0;
while g_Start do begin nAddrLen := sizeof(TSockAddr); sdClient := accept(g_ServerSocketMain, @sAddrs, @nAddrLen); if (sdClient = INVALID_SOCKET) then begin nErrorCode := WSAGetLastError; EnterCriticalSection(g_DebugCritSec); if g_Start then Form1.Memo1.Lines.Add(Format('发生错误.错误代码:%d', [nErrorCode])); LeaveCriticalSection(g_DebugCritSec); end else begin EnterCriticalSection(g_DebugCritSec); Form1.Memo1.Lines.Add(Format('新连接.客户端IP:%s', [inet_ntoa(sAddrs.sin_addr)])); LeaveCriticalSection(g_DebugCritSec);
sslServer := IdSslNew(g_ctxServer); //申请SSL会话的环境,参数就是前面我们申请的 SSL通讯方式,返回当前的SSL 连接环境的指针. if sslServer = nil then begin closesocket(sdClient); Continue; end;
IdSslSetFd(sslServer, sdClient); //绑定读写套接字
nRet := IdSslAccept(sslServer); &nb"7p0�B if (nRet = -1) then<br "2="" ="" begin<br=""> closesocket(sdClient); IdSslShutdown(sslServer); IdSslFree(sslServer); Continue; end;
EnterCriticalSection(g_DebugCritSec); Form1.Memo1.Lines.Add(Format('SSL连接使用的算法为:%s', [IdSSLCipherGetName(IdSSLGetCurrentCipher(sslServer))])); LeaveCriticalSection(g_DebugCritSec);
pClient_Cert := IdSslGetPeerCertificate(sslServer); if (pClient_Cert <> nil) then begin EnterCriticalSection(g_DebugCritSec); Form1.Memo1.Lines.Add('客户端证书:'); LeaveCriticalSection(g_DebugCritSec); pStr := IdSslX509NameOneline(IdSslX509GetSubjectName(pClient_Cert), nil, 0); if pStr = nil then Exit; EnterCriticalSection(g_DebugCritSec); Form1.Memo1.Lines.Add(Format('主题: %s', [pStr])); LeaveCriticalSection(g_DebugCritSec); IdSslFree(pStr); pStr := IdSslX509NameOneline(IdSslX509GetIssuerName(pClient_Cert), nil, 0); if pStr = nil then Exit; IdSslFree(pStr); EnterCriticalSection(g_DebugCritSec); Form1.Memo1.Lines.Add(Format('发行者: %s', [pStr])); LeaveCriticalSection(g_DebugCritSec); IdSslFree(pStr); IdSslFree(pClient_Cert); end else begin EnterCriticalSection(g_DebugCritSec); Form1.Memo1.Lines.Add('客户端没有证书.'); LeaveCriticalSection(g_DebugCritSec); end;
FillChar(buf, sizeof(buf), #0); nRet := IdSslRead(sslServer, buf, sizeof(buf)); if nRet <= 0 then begin closesocket(sdClient); sdClient := INVALID_SOCKET; IdSslShutdown(sslServer); IdSslFree(sslServer); sslServer := nil; end else begin EnterCriticalSection(g_DebugCritSec); Form1.Memo1.Lines.Add('客户端发送请求:'#$D#$A + StrPas(buf)); LeaveCriticalSection(g_DebugCritSec); end;
strBody := 'Your IP is:' + inet_ntoa(sAddrs.sin_addr);
strSend := 'HTTP/1.1 200 OK'#$D#$A + 'Content-Length: ' + IntToStr(Length(strBody)) + #$D#$A + 'Content-Type: text/html'#$D#$A#$D#$A + strBody;
IdSslWrite(sslServer, @strSend[1], Length(strSend));
closesocket(sdClient); IdSslShutdown(sslServer); IdSslFree(sslServer); end; end; g_hAcceptThread := 0; end;
procedure TForm1.Button1Click(Sender: TObject); var sAddr: TSockAddr; dwThreadID: DWORD; begin Button1.Enabled := False;
//Create a socket g_ServerSocketMain := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if g_ServerSocketMain = INVALID_SOCKET then begin MessageBox(0, '创建Socket失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Application.Terminate; Exit; end;
//Bind Port FillChar(sAddr, Sizeof(TSockAddr), #0); sAddr.sin_family := AF_INET; sAddr.sin_port := htons(StrToIntDef(Trim(Edit1.Text), 443)); sAddr.sin_addr.S_addr := INADDR_ANY;
if Bind(g_ServerSocketMain, sAddr, Sizeof(TSockAddr)) = SOCKET_ERROR then begin MessageBox(0, '绑定端口失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Application.Terminate; Exit; end;
//listen if listen(g_ServerSocketMain, SOMAXCONN) = SOCKET_ERROR then begin MessageBox(0, '监听端口失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Application.Terminate; Exit; end; g_Start := TRUE; g_hAcceptThread := CreateThread(nil, 0, @AcceptThread, nil, 0, dwThreadId); end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); begin g_Start := FALSE; if g_ServerSocketMain <> INVALID_SOCKET then begin closesocket(g_ServerSocketMain); g_ServerSocketMain := INVALID_SOCKET; end;
if (g_hAcceptThread <> 0) then begin WaitForSingleObject(g_hAcceptThread, INFINITE); CloseHandle(g_hAcceptThread); g_hAcceptThread := 0; end; end;
initialization
if WSAStartup($101, g_Wsa) <> 0 then //初始化Wsock32.dll,2.2版本可以使用MakeWord(2,2), begin MessageBox(0, '初始化Winsock动态库失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end;
if not Load then //装载ssl库失败 begin MessageBox(0, '装载ssl动态库失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end; IdSslLoadErrorStrings; IdSslAddSslAlgorithms; //load所有的SSL算法.
//========================== 初始化SSL Server ================================ g_methServer := IdSslMethodV23; //建立SSL所用的method. if g_methServer = nil then begin MessageBox(0, '建立SSL Server所用的method失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end; g_ctxServer := IdSslCtxNew(g_methServer); //初始化上下文情景. if g_ctxServer = nil then //创建SSL_CTX失败 begin MessageBox(0, '创建服务端SSL_CTX失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end;
if IdSslCtxUseCertificateFile(g_ctxServer, Pchar(ExtractFilePath(GetModuleName(HInstance)) + 'UserCert.pem'), OPENSSL_SSL_FILETYPE_PEM) <= 0 then //加载证书失败 begin MessageBox(0, '加载证书失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end; if IdSslCtxUsePrivateKeyFile(g_ctxServer, Pchar(ExtractFilePath(GetModuleName(HInstance)) + 'UserKey.pem'), OPENSSL_SSL_FILETYPE_PEM) <= 0 then //加载私钥失败 begin MessageBox(0, '加载私钥失败!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end; if not IdSslCtxCheckPrivateKeyFile(g_ctxServer) = 0 then //密钥证书不匹配 begin MessageBox(0, '密钥和证书不匹配!', '错误', MB_ICONEXCLAMATION + MB_TOPMOST); Halt; end; InitializeCriticalSection(g_DebugCritSec); finalization if g_ctxServer <> nil then IdSslCtxFree(g_ctxServer); DeleteCriticalSection(g_DebugCritSec); WSACleanup; //结束对WSocket32.dll调用 end.
完整代码下载地址:http://www.138soft.com/download/ssldemo_server.rar
实际上,稍微用心的人结合第一讲的客户端例子,已经可以写出中间人欺骗程序了.
完整代码下载地址:http://www.138soft.com/download/ssldemo_client.rar
http://www.cnblogs.com/qiubole/archive/2007/12/24/1012077.html
|
请发表评论