Bootstrap

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

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