天天看點

Delphi 微信支付接口AEAD_AES_256_GCM解密

做微信的支付接口,現在必須要使用到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資料。

繼續閱讀