分享

【delphi】实现微信公众号,小程序消息加密解密函数

 hncdman 2022-10-16 发布于湖南

sensor_WU

于 2021-02-13 21:37:33 发布

292

 收藏 4

分类专栏: Delphi 文章标签: 微信公众号 消息加密解密 Delphi 微信公众号 Delphi 消息加密 加密解密

版权

Delphi

专栏收录该内容

74 篇文章17 订阅

订阅专栏

在开发微信公众号,小程序的时候,可以配置成明文模式,兼容模式,安全模式。当配置成安全模式的时候,就需要对消息进行加密,解密。说明下,只有被动回复的时候需要进行消息加密,其它主动请求的API不需要对消息进行加密。

微信官方提供的没有delphi的例子,这里根据官方文档以及C#的例子,更改成delphi的加密解密函数,已经在公众号中使用。核心源代码如下(没有按照对象实现,就是简单的函数实现):

{2021-02-10 大年29 创建

 主要实现微信 消息加密、解密功能

 参考微信的 C# Demo 实现

 错误代码

  //-40001 : 签名验证错误

  //-40002 :  xml解析失败

  //-40003 :  sha加密生成签名失败

  //-40004 :  AESKey 非法

  //-40005 :  appid 校验错误

  //-40006 :  AES 加密失败

  //-40007 : AES 解密失败

  //-40008 : 解密后得到的buffer非法

  //-40009 :  base64加密异常

  //-40010 :  base64解密异常

}

unit uWX_PUB_Cryptography;

interface

uses

  System.Classes,

  System.SysUtils,

  System.Math,

  System.NetEncoding,

  Xml.XMLIntf,

  Xml.XMLDoc,

  WinApi.ActiveX,

  uWX_PUB_ElAES,

  system.Hash;

const

  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;

//1. 生成签名函数

function GenarateSinature(sToken, sTimeStamp, sNonce, sMsgEncrypt: string) : string;

//2. 验证签名是否正确

function VerifySignature(sToken, sTimeStamp, sNonce, sMsgEncrypt, sSigture: string) : Boolean;

//3. 解密消息

function DecryptMsg(sMsgSignature, sTimeStamp, sNonce, sPostData, sToken, sAppID, sEncodingAESKey : string; var sMsg: string) : integer;

//4. 加密消息

function EncryptMsg(sReplyMsg, sTimeStamp, sNonce, sToken, sAppid, sEncodingAESKey : string; var sEncryptMsg : string) : integer;

//5. 实际的加密算法

function AES_encrypt(Input, EncodingAESKey, appid : string) : string;

//6. 实际的解密算法

function AES_decrypt(sEncryptMsg, EncodingAESKey : string; var appid : string) : string;

//6. 创建n个长度的随机字符串

function CreateRandCode(Keylen : Word = 16) : string;

//进行

implementation

//1. 生成签名函数

function GenarateSinature(sToken, sTimeStamp, sNonce, sMsgEncrypt: string) : string;

var

   tmpStr: string;

   SB  : TArray<string>;

   i,j : integer;

 begin

   SetLength(SB,4);

   SB[0] := sToken;

   SB[1] := sTimeStamp;

   SB[2] := sNonce;

   SB[3] := sMsgEncrypt;

   for i := low(SB) to High(SB) - 1 do

     for j := low(SB) to High(SB) - i - 1 do

       if SB[j] > SB[j + 1] then

          begin

            tmpStr := SB[j];

            SB[j] := SB[j + 1];

            SB[j + 1] := tmpStr;

          end;

   tmpStr := SB[0] + SB[1] + SB[2] + SB[3];   //这里似乎需要按照 ANSI 字节加密

   //计算签名

   Result :=  THashSHA1.GetHashString(tmpStr);

   Result := Result.Replace('-','').ToLower;

 end;

//2. 验证签名是否正确

function VerifySignature(sToken, sTimeStamp, sNonce, sMsgEncrypt, sSigture: string) : Boolean;

begin

  Result := GenarateSinature(sToken, sTimeStamp, sNonce, sMsgEncrypt) = sSigture;

end;

//3. 解密消息

function DecryptMsg(sMsgSignature, sTimeStamp, sNonce, sPostData, sToken, sAppID, sEncodingAESKey : string; var sMsg: string) : integer;

var

  FXML : IXMLDocument;

  sEncryptMsg : string;

  appid : string;

begin

  Result := -1;  //初始值

  //如果密钥不正确,则直接返回

  if length(sEncodingAESKey) <> 43 then

     Exit(WXBizMsgCrypt_IllegalAesKey);

  //解析除实际需要进行解密的字符串

  CoInitialize(nil);  //CoUninitialize;

  FXML := NewXMLDocument; // TXMLDocument.Create(Self);

  //接收到的数据位于 RequestData 中

  FXML.XML.Text := sPostData;  //收到的具体内容;

  try

    FXML.Active := True;

    sEncryptMsg := FXML.DocumentElement.ChildNodes['Encrypt'].Text;          //需要解密的数据

  except on E: Exception do

    begin

      exit(WXBizMsgCrypt_ParseXml_Error);

    end;

  end;

  CoInitialize(nil);

  //如果签名失败,直接退出

  if not VerifySignature(sToken, sTimeStamp, sNonce, sEncryptMsg, sMsgSignature) then Exit(WXBizMsgCrypt_ValidateSignature_Error);

  //进行数据解密

  try

     sMsg := AES_decrypt(sEncryptMsg, sEncodingAESKey, appid);

  except on E: Exception do

     Exit(WXBizMsgCrypt_DecryptAES_Error);

  end;

  if appid <> sAppID then Exit(WXBizMsgCrypt_ValidateAppid_Error);

  Result := 0;

end;

//4. 加密消息

function EncryptMsg(sReplyMsg, sTimeStamp, sNonce, sToken, sAppid, sEncodingAESKey : string; var sEncryptMsg : string) : integer;

var

  RAW, MsgSigature : string;

   EncryptLabelHead,

   EncryptLabelTail,

   MsgSigLabelHead,

   MsgSigLabelTail,

   TimeStampLabelHead,

   TimeStampLabelTail,

   NonceLabelHead,

   NonceLabelTail : string;

begin

  //如果密钥不正确,则直接返回

  if length(sEncodingAESKey) <> 43 then

     Exit(WXBizMsgCrypt_IllegalAesKey);

  //进行数据加密

  try

    RAW := AES_encrypt(sReplyMsg, sEncodingAESKey, sAppid);

  except on E: Exception do

    Exit(WXBizMsgCrypt_EncryptAES_Error);

  end;

  //计算签名

  MsgSigature := GenarateSinature(sToken, sTimeStamp, sNonce, RAW);

   EncryptLabelHead := '<Encrypt><![CDATA[';

   EncryptLabelTail := ']]></Encrypt>';

   MsgSigLabelHead := '<MsgSignature><![CDATA[';

   MsgSigLabelTail := ']]></MsgSignature>';

   TimeStampLabelHead := '<TimeStamp><![CDATA[';

   TimeStampLabelTail := ']]></TimeStamp>';

   NonceLabelHead := '<Nonce><![CDATA[';

   NonceLabelTail := ']]></Nonce>';

   sEncryptMsg := sEncryptMsg + '<xml>' + EncryptLabelHead + raw + EncryptLabelTail;

   sEncryptMsg := sEncryptMsg + MsgSigLabelHead + MsgSigature + MsgSigLabelTail;

   sEncryptMsg := sEncryptMsg + TimeStampLabelHead + sTimeStamp + TimeStampLabelTail;

   sEncryptMsg := sEncryptMsg + NonceLabelHead + sNonce + NonceLabelTail;

   sEncryptMsg := sEncryptMsg + '</xml>';

end;

//5. 实际的加密算法

function AES_encrypt(Input, EncodingAESKey, appid : string) : string;

var

  Key : TBytes;

  iV  : TBytes;

  Randcode : string;

  bRand, bAppid, btmpMsg, bMsgLen, bMsg : TBytes;

  len : Integer;

  FAESKey256 : TAESKey256;

  FInitVectorBytes : TAESBuffer;

  SM : TMemoryStream;

  TM : TMemoryStream;

  B : TBytes;

  BB : Byte;

  i : integer;

begin

  Key := TNetEncoding.Base64.DecodeStringToBytes(EncodingAESKey);

  SetLength(iv,16);

  move(Key[0],iv[0],16);

  Randcode := {'046db2d37a77bad4'; //}  CreateRandCode(16);

  //组织加密的数据

  bRand   := TEncoding.UTF8.GetBytes(Randcode);

  bAppid  := TEncoding.UTF8.GetBytes(appid);

  btmpMsg := TEncoding.UTF8.GetBytes(Input);

  //消息的实际长度

  len := length(btmpMsg);

  SetLength(bMsgLen,Sizeof(len){4});

  //这一句实现的是低位在前,高位在后,需要调整为高位在前,低位在后

  move(len,bMsgLen[0], Sizeof(len){4});

  //需要调整为高位在前,低位在后

  BB := bMsgLen[0];

  bMsgLen[0] :=  bMsgLen[3];

  bMsgLen[3] := BB;

  BB := bMsgLen[1];

  bMsgLen[1] := bMsgLen[2];

  bMsgLen[2] := BB;

  //设置消息的实际长度

  SetLength(bMsg,Length(bRand) + Length(bMsgLen) + Length(bAppid) + Length(btmpMsg));

  //加密前数据进行组合

  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));

  //根据微信约定,需要按照256bit位密钥进行补全加密前字符,也就是被加密字节长度必须是32 个字符的整数倍

  len := Length(bMsg);

  BB := len mod 32;   //取32的余数

  BB := 32 - BB;      //完全按照 C# 的官方Demo ,官方Demo 有点瑕疵

  SetLength(bMsg,len + BB);

  for i := len to len + BB do

      bMsg[i] := BB;

  //现在进行 AES 加密

  SM := TMemoryStream.Create;

  TM := TMemoryStream.Create;

  try

    Move(Key[0],FAESKey256[0],Length(Key));

    move(iv[0],FInitVectorBytes[0],16);

    SM.Write(bMsg[0],Length(bMsg));

    EncryptAESStreamCBC(SM, 0, FAESKey256, FInitVectorBytes, TM);

    TM.Position := 0;

    SetLength(B,TM.Size);

    TM.Read(B[0],TM.Size);

    //结果进行Base64编码

    Result := TNetEncoding.Base64.EncodeBytesToString(B);

    Result := Result.Replace(#13#10,'');

  finally

    SM.Free;

    TM.free;

  end;

end;

//6. 实际的解密算法

function AES_decrypt(sEncryptMsg, EncodingAESKey : string; var appid : string) : string;

var

  Key : TBytes;

  iV  : TBytes;

  Randcode : string;

  bRand, bAppid, btmpMsg, bMsgLen, bMsg : TBytes;

  len : Integer;

  FAESKey256 : TAESKey256;

  FInitVectorBytes : TAESBuffer;

  SM : TMemoryStream;

  TM : TMemoryStream;

  B : TBytes;

begin

  Key := TNetEncoding.Base64.DecodeStringToBytes(EncodingAESKey);

  SetLength(iv,16);

  move(Key[0],iv[0],16);

  //1. 进行Base64 解码

  bMsg := TNetEncoding.Base64.DecodeStringToBytes(sEncryptMsg);

  //现在进行 AES 解密密

  SM := TMemoryStream.Create;

  TM := TMemoryStream.Create;

  try

    Move(Key[0],FAESKey256[0],Length(Key));

    move(iv[0],FInitVectorBytes[0],16);

    SM.Write(bMsg[0],Length(bMsg));

    DecryptAESStreamCBC(SM, 0, FAESKey256, FInitVectorBytes, TM);

    TM.Position := 0;

    SetLength(B,TM.Size);

    TM.Read(B[0],TM.Size);  //此时 B中包含全部解密后的数据

  finally

    SM.Free;

    TM.free;

  end;

  //2.1 按顺序取出各个字段数据

  //2.1.1 随机16个字符长度的字符串

  SetLength(bRand, 16);

  move(B[0], bRand[0],16);

  Randcode := TEncoding.UTF8.GetString(bRand);

  //2.2.2 消息的长度

  SetLength(bMsgLen,4);

  move(B[16], bMsgLen[0],4);

  //2.2.3 实际的消息

  len := bMsgLen[0] * 256 * 256 * 256 + bMsgLen[1] * 256 * 256 + bMsgLen[2] * 256 + bMsgLen[3];

  SetLength(btmpMsg,len);

  move(B[20],btmpMsg[0],len);

  //2.2.4 appid 的长度

  SetLength(bAppid,Length(B) - 20 - len);

  move(B[20 + len], bAppid[0], Length(B) - 20 - len);

  //对于最后一个appid字段,需要按照CBC的补齐方式去除最后多余的字符

  if bAppid[length(bAppid) - 1] <= $20 then    //32

     SetLength(bAppid,Length(bAppid) - bAppid[length(bAppid) - 1]);

  appid := TEncoding.UTF8.GetString(bAppid);

  Result := TEncoding.UTF8.GetString(btmpMsg);

end;

//6. 创建那个长度的随机字符串

function CreateRandCode(Keylen : Word = 16) : string;

const

  KeyChar = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012345679';

var

  i : integer;

begin

  Result := '';

  for I := 0 to KeyLen - 1 do

    begin

     Randomize;

     Result := Result + KeyChar.Substring(RandomRange(0,62),1);

    end;

end;

end.

————————————————

版权声明:本文为CSDN博主「sensor_WU」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/sensor_WU/article/details/113803842

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多