做微信的支付接口,現在必須要使用到AEAD_AES_256_GCM加解密。在微信的文檔中提供了其他開發語言的示例代碼,但因為某些大家都知道的原因,沒有delphi的示例。
而在網上去找一圈,你可能會更加的蒙圈,一是幾乎隻有其他語言的文章,二是delphi有提到過的要麼太複雜要麼沒法使用。
今天這裡就來詳細講一講Delphi(我使用的版本的D10.3)處理微信AEAD_AES_256_GCM解密,我們實際需要的代碼并不多,很多主要的代碼delphi已給我們準備好了。下面就是最終代碼:
function Decrypt_AEAD_AES_256_GCM(const AData, AKey, AIV, AAD: TBytes; out ADeCryptData: TBytes): Boolean;
var
Ctx: PEVP_CIPHER_CTX;
vLen: Integer;
vDataLen: Integer;
vTAG: TBytes;
begin
Result := False;
vLen := 0;
vDataLen := 0;
SetLength(ADeCryptData, Length(AData));
//取TAG ,這個是一個解密後的驗證資料
Move(AData[Length(AData)-16],vTag[0],16);
SetLength(vTAG,16);
Load;//初始化SSL
Ctx := EVP_CIPHER_CTX_new(); //初始化Ctx 後面必須使用到的
try
EVP_DecryptInit_ex(Ctx, EVP_aes_256_gcm, nil, nil, nil);//初始化解密資料
EVP_CIPHER_CTX_ctrl(Ctx, EVP_CTRL_GCM_SET_IVLEN, Length(AIV), nil);//設定IV長度
EVP_DecryptInit_ex(Ctx, nil, nil, @AKey[0], @AIV[0]);//設定解決用的KEY及IV
if EVP_CIPHER_CTX_set_padding(Ctx,0) <> 1 then//這個其實可以不用的,這是配置非填充模式
Exit(False);
if EVP_DecryptUpdate(Ctx, nil, @vLen, @AAD[0], Length(AAD)) <> 1 then
Exit(False);
//下面這條,其實就是在進行解密了
if EVP_DecryptUpdate(Ctx, @ADeCryptData[0], @vDataLen, @AData[0], Length(AData)-16) <> 1 then
Exit(False);
//設定 tag
if EVP_CIPHER_CTX_ctrl(Ctx, EVP_CTRL_GCM_SET_TAG, 16, @vTag[0]) <> 1 then
Exit(False);
//最後進行解密驗證
if EVP_DecryptFinal_ex(Ctx, @ADeCryptData[vDataLen], @vLen) <> 1 then
Exit(False);
SetLength(ADeCryptData, vDataLen);//傳回解決的資料
Result := True;
finally
EVP_CIPHER_CTX_free(Ctx);
end;
end;
這段代碼需要引用系統提供的 IdSSLOpenSSL,IdSSLOpenSSLHeaders,
由這檔案的引用,大家可能已知道,這其實就是使用了Id的SSL庫,其根本也就是OpenSSL,是以運作的時候需要有對應的兩個動态庫檔案(libeay32.dll、ssleay32.dll),在Delphi安裝目錄裡可以找到的哈,沒必要網上去搜。
現在來說一下這個函數的使用:
函數的調用很簡單:Decrypt_AEAD_AES_256_GCM(vData,vKey,vIV,vAD,vDeCryptData),執行完成後傳回的是一個Boolean,為True時說明解密正确,為False時為解密錯誤(失敗),另外還有一個傳回的變量vDeCryptData,這裡是解決後的明文,這個變量通常都會有一個傳回資料,但需要根據函數傳回來确定是否是正确的明文。請一定注意,即便你是随意設定的解密參數,vDeCryptData都可能會傳回資料的。
具體調用代碼:
procedure TForm8.Button4Click(Sender: TObject);
var
vDeCryptData: TBytes;
vData, vKey, vIV, vAD, vTag: TBytes;
vv:string;
reb:Boolean;
begin
vv:= 'eELiRmT/laQs='; //這裡是微信傳回的密碼 對應JSON中的ciphertext
vData:= TNetEncoding.Base64.DecodeStringToBytes(vv);//需要先解Base64
vKey:=TEncoding.Default.GetBytes('wwwl.............86v3');//這個是微信中設定的APIV3密鑰 Key
vIV:=TEncoding.Default.GetBytes('8Ty0LWf6Opfm');//這裡是微信傳回的加密使用的随機串 對應JSON中nonce
vAD:=TEncoding.Default.GetBytes('transaction');//這裡是微信傳回的附加資料 對應JSON中的associated_data
reb:=vSHA256WithRSA.Decrypt_AEAD_AES_256_GCM(vData,vKey,vIV,vAD,vDeCryptData);
if reb then
Memo4.Lines.Text:= UTF8ToString(TEncoding.Default.GetString(vDeCryptData))
else
Memo4.Lines.Text:='解密失敗!';
end;
下面這段是微信支付通知傳回的JSON資料格式(摘自微信文檔:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_2.shtml):
{
"original_type": "transaction", // 加密前的對象類型
"algorithm": "AEAD_AES_256_GCM", // 加密算法
// Base64編碼後的密文
"ciphertext": "...",
// 加密使用的随機串初始化向量)
"nonce": "...",
// 附加資料包(可能為空)
"associated_data": ""
}
簡單說:
vData對應微信傳回的待解密資料(密文) ,JSON中的ciphertext;
vKey對應微信你在微信中設定的APIv3密鑰;
vIV對應微信傳回的加密使用的随機串 ,JSON中的nonce;
vAD對應微信傳回的附加資料 ,JSON中的associated_data;
在騰訊的文檔中,沒有明确的告訴你加密不使用填充模式,僅僅是在JAVA的示範代碼中有這樣一句:Cipher.getInstance("AES/GCM/NoPadding");,其中NoPadding就是不使用填充模式。
還有一點沒有說明的是,JSON中傳回的密文,不隻是密文,而是密文加上TAG資料。