• 老陈---谈Delphi中SSL协议的应用[转]


    摘要:本文主要介绍如何在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

  • 相关阅读:
    圆环自带动画进度条ColorfulRingProgressView
    FragmentTabHost+FrameLayout实现底部菜单栏
    PopupWindowFromBottom 从底部弹出popupwindow
    Android滚动选择控件
    github入门基础之上传本地文件以及安装github客户端
    Android 快速开发系列 ORMLite 框架最佳实践之实现历史记录搜索
    Android 快速开发系列 ORMLite 框架最佳实践
    《Entity Framework 6 Recipes》中文翻译——第十章EntityFramework存储过程处理(一)
    《Entity Framework 6 Recipes》中文翻译——第九章EntityFramework在N层架构程序中的应用(七)
    《Entity Framework 6 Recipes》中文翻译——第九章EntityFramework在N层架构程序中的应用(六)
  • 原文地址:https://www.cnblogs.com/findumars/p/5929783.html
Copyright © 2020-2023  润新知