zoukankan      html  css  js  c++  java
  • 使用delphi+intraweb进行微信开发4—微信消息加解密

    示例代码已经放出!请移步使用delphi+intraweb进行微信开发1~4代码示例进行下载,虽为示例代码但是是从我项目中移出来的,封装很完备适于自行扩展和修改。

    上一讲当中我做了个简单的微信文本消息回显应用,当时是以微信明文方式实现的,其实微信推荐的是消息应该加密传输以增加安全性,所以这讲说说微信消息的加解密。

    在微信的帮助页面上可以下载微信消息加解密的例程,可惜的是没有Delphi语言的示例,网上搜索一番,没有人贡献出写好的Delphi版的微信加解密算法单元,好在有官方示例的C#版的,那就按照C#的改一个吧。

    微信消息是以AES算法进行的加密处理,而遗憾的是Delphi并没有内置的AES算法单元,必须找第三方实现的,不过一般第三方实现的算法都因为种种原因并不完善,需要使用者酌情修改,所以在基础算法支持上Delphi确实和.net以及java这类的开发语言比不了。

    呵呵,上网找Delphi版AES算法吧。在在这里要感谢cnpack开发组,他们不但推出一流的delphi开发环境增强组件还有开源组件包cnvcl,这个组件包中有SHA1、AES、MD5等多种算法单元,我打开AES算法单元查看,发现封装的很完美,ECB、CBC模式均支持,呵呵,幸福了02B69EAC

    参考C#示例代码一通修改测试,省略几昼夜苦干的吐槽终于开花结果:

    呵呵,这个或者是网上目前唯一的开源的Delphi版的微信加解密算法单元吧,激动!

    {*****************************************************************************}
    {                                                                             }
    {       泛思微平台                                                            }
    {                                                                             }
    {       版权所有 (C) 2016 石家庄泛思电子商务有限公司                          }
    {                                                                             }
    {       微信消息加密解密单元,使用DelphiXE2版本编写                           }
    {       作者:Delphi力量                                                      }
    {       QQ:404328970                                                         }
    {       EMail: heblxy@163.com                                                 }
    {       Blog:www.cnblogs.com/dpower                                          }
    {       参考链接:                                                            }
    {       http://mp.weixin.qq.com/wiki/14/70e73cedf9fd958d2e23264ba9333ad2.html }
    {                                                                             }
    {*****************************************************************************}

    unit uWxMsgCrypt;

    interface

    uses
      System.Classes, System.SysUtils;

    type
      WXBizMsgCryptErrorCode = (
        WXBizMsgCrypt_OK = 0,
        WXBizMsgCrypt_ValidateSignature_Error = -40001,
        WXBizMsgCrypt_ParseXml_Error = -40002,
        WXBizMsgCrypt_ComputeSignature_Error = -40003,
        WXBizMsgCrypt_IllegalAesKey = -40004,
        WXBizMsgCrypt_ValidateAppid_Error = -40005,
        WXBizMsgCrypt_EncryptAES_Error = -40006,
        WXBizMsgCrypt_DecryptAES_Error = -40007,
        WXBizMsgCrypt_IllegalBuffer = -40008,
        WXBizMsgCrypt_EncodeBase64_Error = -40009,
        WXBizMsgCrypt_DecodeBase64_Error = -40010
      );

      /// <summary>
      /// 提供微信加密消息解密和微信明文消息加密功能
      /// </summary>
      TWxMsgCrypt = class
      private
        function CreateRandCode(codeLen: Integer): string;
        function AES_decrypt(const sEncodingAESKey, sMsgEncrypt: string; var cpid: string): string;
        function AES_encrypt(const sEncodingAESKey, sMsg, cpid: string): string;
      public
        /// <summary>
        /// 解密微信消息
        /// </summary>
        /// <param name="sToken">Token,看公众号设置</param>
        /// <param name="sTimeStamp">时间戳,随微信消息一起传入,可以通过Url参数获取</param>
        /// <param name="sNonce">随机字符串,随微信消息一起传入,可以通过Url参数获取</param>
        /// <param name="sMsgEncrypt">微信消息xml的Encrypt字段内容</param>
        /// <param name="sSigture">签名,随微信消息一起传入,可以通过Url参数获取</param>
        /// <param name="sAppID">AppID,看公众号设置</param>
        /// <param name="sEncodingAESKey">EncodingAESKey,看公众号设置</param>
        /// <param name="sMsg">sMsg: 解密后的Encrypt字段内容原文,当return返回WXBizMsgCrypt_OK时有效</param>
        /// <returns>成功WXBizMsgCrypt_OK,失败返回对应的错误码</returns>
        function DecryptMsg(const sToken, sTimeStamp, sNonce, sMsgEncrypt, sSigture,
          sAppID, sEncodingAESKey: string; var sMsg: string): WXBizMsgCryptErrorCode;

        /// <summary>
        /// 加密微信消息
        /// </summary>
        /// <param name="sMsg">全部xml内容(明文)</param>
        /// <param name="sAppID">AppID,看公众号设置</param>
        /// <param name="sEncodingAESKey">EncodingAESKey,看公众号设置</param>
        /// <param name="sMsgEncrypt">sMsgEncrypt: 输出的是加密后的全部xml(可以直接发送无需再编组xml),当return返回WXBizMsgCrypt_OK时有效</param>
        /// <returns>成功WXBizMsgCrypt_OK,失败返回对应的错误码</returns>
        function EncryptMsg(const sMsg, sToken, sAppID, sEncodingAESKey: string;
          var sMsgEncrypt: string): WXBizMsgCryptErrorCode;
      end;

    implementation

    uses
      CnSHA1, EncdDecd, CnAES, System.Math, uWxGlobal;

    { TWxMsgCrypt }

    function TWxMsgCrypt.AES_decrypt(const sEncodingAESKey, sMsgEncrypt: string; var cpid: string): string;
    var
      aEncodingAESKeyStr, sInput: AnsiString;
      aEncodingAESKeyBts, IvBts, InputBts: TBytes;
      InputStream, DecodeStream: TMemoryStream;
      AesKey: TAESKey256;
      Iv: TAESBuffer;
      P: PByteArray;
      iLen, iDecodeDataLen: Integer;
      bMsg, bAppid: TBytes;

      function GetRealDataLenWithoutKCS7Bytes: Integer;
      var
        lstBt: Byte;
        block_size, AllKCS7ByteCount: Integer;
      begin
        block_size := 32;
        lstBt := P^[DecodeStream.Size - 1];
        AllKCS7ByteCount := block_size - (block_size - Ord(lstBt));
        if (AllKCS7ByteCount > 0) and (AllKCS7ByteCount < DecodeStream.Size) then
        begin
          if P^[DecodeStream.Size - AllKCS7ByteCount] = lstBt then
            Result := DecodeStream.Size - AllKCS7ByteCount
          else
            Result := DecodeStream.Size;
        end else
          Result := DecodeStream.Size;
      end;

    begin
      try
        aEncodingAESKeyStr := AnsiString(sEncodingAESKey + '=');
        aEncodingAESKeyBts := DecodeBase64(aEncodingAESKeyStr);
      except
        raise Exception.Create('1');
      end;
      try
        SetLength(IvBts, 16);
        Move(aEncodingAESKeyBts[0], IvBts[0], 16);

        // aes.KeySize = 256; aes.BlockSize = 128; aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.None;
        sInput := AnsiString(sMsgEncrypt);
        InputBts := DecodeBase64(sInput);

        InputStream := TMemoryStream.Create;
        DecodeStream := TMemoryStream.Create;
        try
          InputStream.Write(InputBts[0], Length(InputBts));
          Move(aEncodingAESKeyBts[0], AesKey, Length(aEncodingAESKeyBts));
          Move(IvBts[0], Iv, Length(IvBts));
          InputStream.Position := 0;
          DecryptAESStreamCBC(InputStream, 0,
            AesKey, Iv, DecodeStream);
          P := PByteArray(DecodeStream.Memory);
          iDecodeDataLen := GetRealDataLenWithoutKCS7Bytes;

          iLen := P^[16] shl 24 + P^[17] shl 16 + P^[18] shl 8 + P^[19];
          SetLength(bMsg, iLen);
          SetLength(bAppid, iDecodeDataLen - 20 - iLen);
          Move(P^[20], bMsg[0], iLen);
          Move(P^[20 + iLen], bAppid[0], iDecodeDataLen - 20 - iLen);
          Result := TEncoding.UTF8.GetString(bMsg);
          cpid := TEncoding.UTF8.GetString(bAppid);
        finally
          InputStream.Free;
          DecodeStream.Free;
        end;
      except
        raise Exception.Create('2');
      end;
    end;

    function TWxMsgCrypt.AES_encrypt(const sEncodingAESKey, sMsg,
      cpid: string): string;
    var
      aEncodingAESKeyStr: AnsiString;
      aEncodingAESKeyBts, IvBts, bRand, bAppid, btmpMsg, bMsg, bMsgLen, msg, pad: TBytes;
      Randcode: string;
      AesKey: TAESKey256;
      Iv: TAESBuffer;
      InputStream, OutputStream: TMemoryStream;

      function CreateRandCode(codeLen: Integer): string;
      var
        codeSerial, code: string;
        strLst: TStringList;
        randValue, i: Integer;
      begin
        codeSerial := '2,3,4,5,6,7,a,c,d,e,f,h,i,j,k,m,n,p,r,s,t,A,C,D,E,F,G,H,J,K,M,N,P,Q,R,S,U,V,W,X,Y,Z';
        if (codeLen = 0) then
          codeLen := 16;
        strLst := TStringList.Create;
        try
          ExtractStrings([','], [], PChar(codeSerial), strLst);
          code := '';
          Randomize;
          for i := 0 to codeLen - 1 do
          begin
            randValue := Random(strLst.Count);
            code := code + strLst[randValue];
          end;
          Result := code;
        finally
          strLst.Free;
        end;
      end;

      function KCS7Encoder(text_length: Integer): TBytes;
      var
        block_size, amount_to_pad: Integer;
        pad_chr: Char;
        tmp: string;
        i: Integer;
      begin
        block_size := 32;
        // 计算需要填充的位数
        amount_to_pad := block_size - (text_length mod block_size);
        if (amount_to_pad = 0) then
          amount_to_pad := block_size;
        // 获得补位所用的字符
        pad_chr := Chr(amount_to_pad);
        tmp := '';
        for i := 0 to amount_to_pad - 1 do
          tmp := tmp + pad_chr;
        Result := BytesOf(tmp);
      end;

    begin
      aEncodingAESKeyStr := AnsiString(sEncodingAESKey + '=');
      aEncodingAESKeyBts := DecodeBase64(aEncodingAESKeyStr);

      SetLength(IvBts, 16);
      Move(aEncodingAESKeyBts[0], IvBts[0], 16);

      Randcode := CreateRandCode(16);

      bRand := TEncoding.UTF8.GetBytes(Randcode);
      bAppid := TEncoding.UTF8.GetBytes(cpid);
      btmpMsg := TEncoding.UTF8.GetBytes(sMsg);
      SetLength(bMsgLen, 4);
      bMsgLen[0] := (Length(btmpMsg) shr 24) and $FF;
      bMsgLen[1] := (Length(btmpMsg) shr 16) and $FF;
      bMsgLen[2] := (Length(btmpMsg) shr 8) and $FF;
      bMsgLen[3] := Length(btmpMsg) and $FF;

      SetLength(bMsg, Length(bRand) + Length(bAppid) + Length(btmpMsg) + Length(bMsgLen));
      Move(bRand[0], bMsg[0], Length(bRand));
      Move(bMsgLen[0], bMsg[Length(bRand)], Length(bMsgLen));
      Move(btmpMsg[0], bMsg[Length(bRand) + Length(bMsgLen)], Length(btmpMsg));
      Move(bAppid[0], bMsg[Length(bRand) + Length(bMsgLen) + Length(btmpMsg)], Length(bAppid));

    {$REGION '自己进行PKCS7补位'}
      SetLength(msg, Length(bMsg) + 32 - Length(bMsg) mod 32);
      Move(bMsg[0], msg[0], Length(bMsg));
      pad := KCS7Encoder(Length(bMsg));
      Move(pad[0], msg[Length(bMsg)], Length(pad));
    {$ENDREGION}

      //aes.KeySize = 256; aes.BlockSize = 128; aes.Padding = PaddingMode.None; aes.Mode = CipherMode.CBC;
      Move(aEncodingAESKeyBts[0], AesKey, Length(aEncodingAESKeyBts));
      Move(IvBts[0], Iv, Length(IvBts));
      InputStream := TMemoryStream.Create;
      OutputStream := TMemoryStream.Create;
      try
        InputStream.Write(msg[0], Length(msg));
        InputStream.Position := 0;
        EncryptAESStreamCBC(InputStream, 0, AesKey, Iv, OutputStream);
        Result := string(EncodeBase64(OutputStream.Memory, OutputStream.Size));
      finally
        InputStream.Free;
        OutputStream.Free;
      end;
    end;

    function TWxMsgCrypt.CreateRandCode(codeLen: Integer): string;
    var
      codeSerial, code: string;
      strLst: TStringList;
      randValue, i: Integer;
    begin
      codeSerial := '2,3,4,5,6,7,a,c,d,e,f,h,i,j,k,m,n,p,r,s,t,A,C,D,E,F,G,H,J,K,M,N,P,Q,R,S,U,V,W,X,Y,Z';
      if (codeLen = 0) then
        codeLen := 16;
      strLst := TStringList.Create;
      try
        ExtractStrings([','], [], PChar(codeSerial), strLst);
        code := '';
        Randomize;
        for i := 0 to codeLen - 1 do
        begin
          randValue := Random(strLst.Count);
          code := code + strLst[randValue];
        end;
        Result := code;
      finally
        strLst.Free;
      end;
    end;

    function TWxMsgCrypt.DecryptMsg(const sToken, sTimeStamp, sNonce, sMsgEncrypt, sSigture,
      sAppID, sEncodingAESKey: string; var sMsg: string): WXBizMsgCryptErrorCode;
    var
      ret: WXBizMsgCryptErrorCode;
      cpid: string;

      function VerifySignature: WXBizMsgCryptErrorCode;
      var
        hash: string;
        aStr: AnsiString;
        AL: TStringList;
        i: Integer;
      begin
        AL := TStringList.Create;
        try
          AL.Add(sToken);
          AL.Add(sTimeStamp);
          AL.Add(sNonce);
          AL.Add(sMsgEncrypt);
          AL.Sort;
          hash := '';
          for i := 0 to AL.Count - 1 do
            hash := hash + AL[i];
          aStr := AnsiString(hash);
          hash := LowerCase(SHA1Print(SHA1StringA(aStr)));
        finally
          AL.Free;
        end;
        if (hash = sSigture) then
          Result := WXBizMsgCrypt_OK
        else
          Result := WXBizMsgCrypt_ValidateSignature_Error;
      end;

    begin
      sMsg := '';
      if (Length(sEncodingAESKey) <> 43) then
      begin
        Result :=  WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
        Exit;
      end;

      //verify signature
      ret := VerifySignature;
      if (ret <> WXBizMsgCrypt_OK) then
      begin
        Result := ret;
        Exit;
      end;

      //decrypt
      cpid := '';
      try
        sMsg := AES_decrypt(sEncodingAESKey, sMsgEncrypt, cpid);
      except
        on E: Exception do
        begin
          if E.Message = '1' then
            Result := WXBizMsgCrypt_DecodeBase64_Error
          else
            Result := WXBizMsgCrypt_DecryptAES_Error;
          Exit;
        end;
      end;

      if (cpid <> sAppID) then
      begin
        Result := WXBizMsgCrypt_ValidateAppid_Error;
        Exit;
      end;

      Result := WXBizMsgCrypt_OK;
    end;

    function TWxMsgCrypt.EncryptMsg(const sMsg, sToken, sAppID, sEncodingAESKey: string;
      var sMsgEncrypt: string): WXBizMsgCryptErrorCode;
    var
      hash, wxDt, wxNonce, EncryptField: string;

      function GenSignature: string;
      var
        hash: string;
        aStr: AnsiString;
        AL: TStringList;
        i: Integer;
      begin
        AL := TStringList.Create;
        try
          AL.Add(sToken);
          AL.Add(EncryptField);
          wxDt := GetWxNowStr;
          AL.Add(wxDt);
          wxNonce := CreateRandCode(10);
          AL.Add(wxNonce);
          AL.Sort;
          hash := '';
          for i := 0 to AL.Count - 1 do
            hash := hash + AL[i];
          aStr := AnsiString(hash);
          hash := LowerCase(SHA1Print(SHA1StringA(aStr)));
        finally
          AL.Free;
        end;
        Result := hash;
      end;

    begin
      sMsgEncrypt := '';
      if (Length(sEncodingAESKey) <> 43) then
      begin
        Result :=  WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
        Exit;
      end;

      //encrypt
      try
        EncryptField := AES_encrypt(sEncodingAESKey, sMsg, sAppID);
      except
        on E: Exception do
        begin
          if E.Message = '1' then
            Result := WXBizMsgCrypt_DecryptAES_Error
          else
            Result := WXBizMsgCrypt_EncryptAES_Error;
          Exit;
        end;
      end;

      //gen signature
      try
        hash := GenSignature;
      except
        Result := WXBizMsgCrypt_ComputeSignature_Error;
        Exit;
      end;

      //xml
      sMsgEncrypt := '<xml><Encrypt><![CDATA[' + EncryptField + ']]></Encrypt>' +
        '<MsgSignature><![CDATA[' + hash + ']]></MsgSignature>' +
        '<TimeStamp><![CDATA[' + wxDt + ']]></TimeStamp>' +
        '<Nonce><![CDATA[' + wxNonce + ']]></Nonce></xml>';

      Result := WXBizMsgCrypt_OK;
    end;

    end.

    呵呵,看看效果图吧:

  • 相关阅读:
    【APUE】一个fork的面试题及字符设备、块设备的区别
    【stl学习笔记】set、multiset
    【stl学习笔记】红黑树
    颜色
    Android之ActionBar与菜单
    Listview不显示的问题学习
    IM学习过程
    android filter
    sort()排序 collections.sort();
    notification使用
  • 原文地址:https://www.cnblogs.com/dpower/p/5167757.html
Copyright © 2011-2022 走看看