示例代碼已經放出!請移步使用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模式均支持,呵呵,幸福了
。
參考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.
呵呵,看看效果圖吧:


