微信支付v3 AEAD_AES_256_GCM解密JS版本代码及验证 javascript

news/2025/1/13 6:09:29/

最近因为在写微信支付相关的代码,所以不可避免的涉及到加密解密的问题。而很多js的许多加密解密算法需要自行寻找,我也没有在网上找到一篇针对微信支付这个问题的综合类博客,所以在这里叙述一下我自己关于AEAD_AES_256_GCM解密的一个JS解决方案,并列举一下收集到的资料,防止大家走弯路。
本篇文章针对的具体问题如下:
微信支付通知API:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_11.shtml
回调报文解密文档:https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/zheng-shu-he-hui-tiao-bao-wen-jie-mi

AEAD_AES_256_GCM 加密算法解析

这个名称应该是分三部分看的

  • AES-256 加密算法:我们首先需要知道的是加密的一个过程,一个简单的加密满足如下的公式:密文 = 加密算法(密钥,明文)。也就是说密文是由加密算法、密钥、明文共同产生的,而AES指的是一种加密算法,256指的是密钥的长度为256位(32B)。所以,AES-256指的是使用AES算法和32字节长度密钥的一种加密方式,与之相似的还有AES-128和AES-192。
    参考链接:https://blog.csdn.net/qq_28205153/article/details/55798628
  • AES-256-GCM: 在加密算法中会存在一个问题,就是如何判定密文和密钥有没有被篡改。也就是说,中间人可以获取并修改密文,导致接收方解密后的明文出错,而所谓的GCM就是来解决这个问题的。
    • GCM解决问题的方式是给加密完成的密文添加一个MAC值(Message Authentication Code),而消息验证码的产生是通过加密后的密文进行运算得来的,这样接收方可以通过验证MAC值是否相等来判断消息是否有篡改(如果看不懂请自行搜索Mac相关的知识查漏补缺)。
    • GCM需要一个计数器和一个附加消息来完成算法,算法的具体细节可以参考下方连接。并且,这个计数器需要一个初始值向量来进行初始化,这就使得我们在加密的过程中,不仅仅需要有一个密钥,还需要一个计数器的初始化向量(Initialization vector , iv)和一个附加数据。
      参考链接:https://blog.csdn.net/andylau00j/article/details/79269303
  • AEAD-AES-256-GCM:AEAD(Authenticated Encryption with Associated Data)指的是一种加密形式,如果一个加密算法可以同时完成加密验证加密解密过程的结果是否正确这两个操作,那么他就是一种AEAD的算法。上面说的AES-256-GCM就是一种AEAD加密算法,所以在我的浅薄理解中,AEAD-AES-256-GCM和AES-256-GCM应该是一种东西吧。(不确定,欢迎讨论)
    参考链接:https://zhuanlan.zhihu.com/p/28566058

node-aes-gcm

下述是微信支付提供的参数,当然还有商户平台上提供的32位密钥(表示为key)

"resource" : {"algorithm":"AEAD_AES_256_GCM","ciphertext": "...","nonce": "...","original_type":"transaction","associated_data": ""
}

JS的话我是在npm上找到的一个库——node-aes-gcm,链接:https://www.npmjs.com/package/node-aes-gcm。这个库只有两个函数,加密与解密。函数签名如下:

function encrypt(key, iv, plaintext, add): { ciphertext: Buffer, auth_tag: Buffer}
function decrypt(key, iv, ciphertext, aad, auth_tag): { plaintext: Buffer, auth_ok: Boolean } 

参数解释:

  • key:密钥,支持16, 24, 32位,对应我们上述的AES-128, AES-192, AES-256 算法。在微信支付中就是商户平台上的32位密钥。
  • iv:初始化GCM算法计数器的向量(initialization vector),在参数中即随机串nonce
  • plaintext:需要加密的明文,即我们需要获取的数据
  • add:GCM算法中使用的附加数据,即微信支付参数中的associated_data
    而关于这个库中的密文和微信支付中的密文表达的含义是不一样的。我们上面提到AES-256-GCM算法其实是在加密后产生了一个MAC值用于验证密钥和密文的可靠性,然后将其与密文拼接起来。我们可以观察到的是,node-ase-gcm库的加密算法产生了两个返回值,一个是ciphertext,一个是auth_tag,这两个就对应着加密后的密文和MAC值;而微信支付中的resource.ciphertext只有一个密文参数,这个参数是将密文和MAC拼接起来的结果。所以:resource.ciphertext = ciphertext + auth_tag

实验验证

因为微信官方并没有给出JS的实现方法,所以我这里使用python的实现示例进行比较验证。实验思路为:

  • 使用JS库进行加密,得到密文和MAC
  • 拼接起来之后使用python库进行解密,可以还原出明文即可验证成功
// cnpm install node-aes-gcm
> gcm = require('node-aes-gcm')
{ encrypt: [Function], decrypt: [Function] }
> key = new Buffer([0xfe,0xff,0xe9,0x92,0x86,0x65,0x73,0x1c,0x6d,0x6a,0x8f,0x94,0x67,0x30,0x83,0x08])
<Buffer fe ff e9 92 86 65 73 1c 6d 6a 8f 94 67 30 83 08>
> iv = new Buffer([0xca,0xfe,0xba,0xbe,0xfa,0xce,0xdb,0xad,0xde,0xca,0xf8,0x88])
<Buffer ca fe ba be fa ce db ad de ca f8 88>
> plaintext = new Buffer([0xd9,0x31,0x32,0x25,0xf8,0x84,0x06,0xe5,0xa5,0x59,0x09,0xc5,0xaf,0xf5,0x26,0x9a,0x86,0xa7,0xa9,0x53,0x15,0x34,0xf7,0xda,0x2e,0x4c,0x30,0x3d,0x8a,0x31,0x8a,0x72,0x1c,0x3c,0x0c,0x95,0x95,0x68,0x09,0x53,0x2f,0xcf,0x0e,0x24,0x49,0xa6,0xb5,0x25,0xb1,0x6a,0xed,0xf5,0xaa,0x0d,0xe6,0x57,0xba,0x63,0x7b,0x39,0x1a,0xaf,0xd2,0x55])
<Buffer d9 31 32 25 f8 84 06 e5 a5 59 09 c5 af f5 26 9a 86 a7 a9 53 15 34 f7 da 2e 4c 30 3d 8a 31 8a 72 1c 3c 0c 95 95 68 09 53 2f cf 0e 24 49 a6 b5 25 b1 6a ed ...>
// 在这里指定附加数据为空,生成密文和MAC
> e = gcm.encrypt(key, iv, plaintext, new Buffer([]))
{ ciphertext: <Buffer 42 83 1e c2 21 77 74 24 4b 72 21 b7 84 d0 d4 9c e3 aa 21 2f 2c 02 a4 e0 35 c1 7e 23 29 ac a1 2e 21 d5 14 b2 54 66 93 1c 7d 8f 6a 5a ac 84 aa 05 1b a3 0b ...>,auth_tag: <Buffer 4d 5c 2a f3 27 cd 64 a6 2c f3 5a bd 2b a6 fa b4> }
> d = gcm.decrypt(key, iv, e.ciphertext, new Buffer([]), e.auth_tag)
{ plaintext: <Buffer d9 31 32 25 f8 84 06 e5 a5 59 09 c5 af f5 26 9a 86 a7 a9 53 15 34 f7 da 2e 4c 30 3d 8a 31 8a 72 1c 3c 0c 95 95 68 09 53 2f cf 0e 24 49 a6 b5 25 b1 6a ed ...>,auth_ok: true }
# pip install cryptography
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
# 将16进制字符串转为buffer作为key
>>> key = bytes.fromhex('feffe9928665731c6d6a8f9467308308')
>>> key
b'\xfe\xff\xe9\x92\x86es\x1cmj\x8f\x94g0\x83\x08'
# 将16进制字符串转为buffer作为initVector
>>> iv = bytes.fromhex('cafebabefacedbaddecaf888')
>>> iv
b'\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88'
# 附加数据暂时为空
>>> ad = bytes.fromhex('')
>>> ad
b''
# 密文为e.ciphertext与e.auth_tag的拼接
>>> ciphertext = bytes.fromhex('42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985' + '4d5c2af327cd64a62cf35abd2ba6fab4');
# python解密结果与js的解密结果相同,验证成功
>>> aesgcm = AESGCM(key)
>>> aesgcm.decrypt(iv, ciphertext, ad)
b'\xd912%\xf8\x84\x06\xe5\xa5Y\t\xc5\xaf\xf5&\x9a\x86\xa7\xa9S\x154\xf7\xda.L0=\x8a1\x8ar\x1c<\x0c\x95\x95h\tS/\xcf\x0e$I\xa6\xb5%\xb1j\xed\xf5\xaa\r\xe6W\xbac{9\x1a\xaf\xd2U'

实现代码(未验证)

因为商户号还没注册完,所以本段代码的可用性还没有得到验证

const AUTH_KEY_LENGTH = 16; // Bconst { id ,create_time, event_type, resource_type, resource, summary } = ctx.request.body;
const { algorithm, ciphertext, associated_data , nonce, original_type } = resource;
const key_bytes = Buffer.from(miniprogramConfig.api_v3_key_32, 'utf8');
const nonce_bytes = Buffer.from(nonce, 'utf8');
const associated_data_bytes = Buffer.from(associated_data, 'utf8');
const ciphertext_bytes = Buffer.from(ciphertext, 'base64');
const cipherdata_length = ciphertext_bytes.length - AUTH_KEY_LENGTH;
const cipherdata_bytes = ciphertext_bytes.slice(0, cipherdata_length);
const auth_tag_bytes = ciphertext_bytes.slice(cipherdata_length, ciphertext_bytes.length);
const result = gcm.decrypt(key_bytes, nonce_bytes, cipherdata_bytes, associated_data_bytes, auth_tag_bytes);
const {plain_text, auth_ok } = result;
const notify_msg = JSON.parse(plain_text.toString());

http://www.ppmy.cn/news/169483.html

相关文章

Pentest Wiki Part4 后渗透(二)

Hacking Windows Active Directory 目录 1. Description 2. Workthrough 2.1 10.1.222.2032.1.1 Wordpress - Code Injection 2.2 10.1.222.2002.2.1 Port Scanning2.2.2 XP_CMDSHELL 2.3 10.1.222.2012.3.1 MS14-068 2.4 10.1.222.202 描述 Descriptionread Flag from C:\…

GPS经纬度坐标WGS84到东北天坐标系ENU的转换

文章目录 一、简介1.1 ECEF坐标系1.2 WGS-84坐标1.3 东北天坐标系&#xff08;ENU&#xff09; 二、坐标系间的转换2.1 LLA坐标系转ECEF坐标系2.2 ECEF坐标系转LLA坐标系2.3 ECEF坐标系转ENU坐标系2.4 ENU坐标系转ECEF坐标系2.5 LLA坐标系直接转ENU坐标系 参考资料打赏支付宝微…

【嵌入式】MCU外接Flash图片数据存取实例

一 问题背景 工程中需要使用大量的图片资源用于GUI显示&#xff0c;但是图片资源比较大&#xff0c;不能直接保存在MCU上&#xff0c;所以考虑外接Flash芯片用于图片数据的存储。实际使用中&#xff0c;将Flash芯片内的地址直接映射到芯片内部&#xff0c;读取映射的地址即可加…

Activiti工作流引擎

一、 Activiti概述&#xff1a; 前言&#xff1a; 系统的核心根本上是业务流程&#xff0c;工作流只是协助进行业务流程管理。 在没有使用工作流引擎时&#xff0c;可以采用状态字段来跟踪流程的变化情况&#xff0c;这样不同角色的用户&#xff0c;通过状态字段的取值来决定记…

下了个蓝屏代码查看工具,就中病毒了。。。什么鬼病毒,竟然还是用的VBS

扫描所有盘下面的html文件&#xff0c;加入VBS脚本。。。真是奇葩&#xff0c;多少年前的病毒了。。。 http://files.cnblogs.com/files/guangshan/lpdmcxq.rar 这个是病毒链接&#xff0c;下载地址也在里面。 原理是为所有的html文件增加一段脚本&#xff1a; </div><…

数据隐私为先:EMQX Cloud BYOC 架构解析

随着物联网的飞速发展&#xff0c;保护数据隐私和安全变得愈发重要。构建一个安全、可靠、可扩展的物联网基础设施成为企业的首要任务。 EMQ 近期推出了 EMQX Cloud BYOC&#xff0c;采用了以数据隐私为先的架构&#xff0c;为解决这些问题提供了一个理想的方案。用户可以在自…

什么叫匿名子类?

在面向对象编程中&#xff0c;匿名子类是指在创建对象时&#xff0c;直接定义一个没有显式命名的子类。这种子类通常是在父类的基础上添加或重写一些方法&#xff0c;或者实现一些特定的接口。 匿名子类的主要特点是它没有名称&#xff0c;因此无法在其他地方直接引用。它通常…

5. 垃圾收集器G1ZGC详解

JVM性能调优 1. G1收集器(-XX:UseG1GC) 本文是按照自己的理解进行笔记总结&#xff0c;如有不正确的地方&#xff0c;还望大佬多多指点纠正&#xff0c;勿喷。 课程内容&#xff1a; 1、G1垃圾收集器详解 2、每秒几十万并发的系统JVM级别如何优化 3、ZGC垃圾收集器详解 4、…