Delphi 微信支付接口AEAD_AES_256_GCM解密(待测试)

38次阅读
没有评论

做微信的支付接口,现在必须要使用到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数据。

重要说明

  • 1.以上代码可用,需要按需调整。
  • 2.代码引用会有部分代码不能执行,引用不是引用 IdSSLOpenSSL,IdSSLOpenSSLHeaders, 可能是我的版本问题需要引用OpenSSL.Api_11.pas编译目录下需要libcrypto-1_1.dll
  • 以上所需文件下载地址

转自:https://blog.csdn.net/tanqth/article/details/120043252

参考:http://bbs.2ccc.com/topic.asp?topicid=614470

正文完