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