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