分享微信支付v3版 php解密解密代码

1. 微信支付 v3版介绍

微信支付是微信公众账号、微信开放平台和微信商户号三个主体之间的支付服务。微信支付 v3版相比于 v2版,最大的变化是在加强了支付接口安全性的同时,将业务逻辑参数以JSON格式传递,而不是像之前的XML格式。

为了在数据传输时提高安全性,微信支付 v3版增加了RSA密钥加密功能。虽然这种方式对于客户端用户是无感知的,但对于开发者来说,需要进行密钥加密和解密。

本文将介绍如何解密微信支付 v3版接口返回的加密数据,为开发者提供了解逐步实现解密逻辑的方法。

2. 解密原理

2.1 加密方法

微信支付 v3版采用了签名、加密和随机串的方式保证数据传输的安全性。这里我们需要了解以下几个参数:

nonce_str:自定义随机值,例如:"5K8264ILTKCH16CQ2502SI8ZNMTM67VS"

associated_data:对于AEAD_AES_256_GCM算法,传输中会加入额外的数据进行验证,此处为:"{\"nonce\":\"5K8264ILTKCH16CQ2502SI8ZNMTM67VS\",\"associated_data\":\"test\"}"

ciphertext:加密的报文体内容

ad_info:微信支付v3接口新增的广告信息,指用户留在微信上的时间,单位为秒。例如:"20201323:34:56"

mch_id:商户号,注意不是AppID

apiversion:接口版本号,例如:"v3"

加密方法采用的是AEAD_AES_256_GCM算法,通俗地讲就是在AES加密的基础上进行GCM模式加密。对于AES加密,是常规的互联网加密算法,核心代码如下:

/**

* 用于进行AES加密的函数

*/

function aesEncrypt(msg, key) {

const cipher = crypto.createCipheriv('aes-256-cdc', key, iv);

return Buffer.concat([cipher.update(msg), cipher.final()]);

}

而在此基础上,加入了GCM模式的加密,核心代码如下:

/**

* 用于进行AES-GCM加密的函数

*/

function aesGcmEncrypt(msg, key, associated_data = '') {

const cipher = crypto.createCipheriv(ALGORITHM_AES_256_GCM, key, iv, {

authTagLength: TAG_LENGTH_16,

});

cipher.setAAD(Buffer.from(associated_data, 'utf8'), {

plaintextLength: Buffer.byteLength(msg),

});

return Buffer.concat([cipher.update(msg), cipher.final(), cipher.getAuthTag()]);

}

2.2 解密方法

微信支付 v3版数据的解密,主要涉及两种密钥:APIv3密钥 和 敏感信息加密密钥。

APIv3密钥是微信支付v3唯一的凭据,类似于之前我们使用的AppID和AppSecret。此处我们需要用到对应的商户号mch_id、APIv3密钥商户私钥和APIv3密钥平台私钥进行解密。解密之前,我们还需要获取到微信支付v3接口的响应信息和独立随机字符串nonce_str。

为了方便解密,我们可以使用官方提供的解密类库:aesgcm.js。该类库提供SanboxEncryptor和APIv3Encryptor两个类,分别用于解密测试环境和正式环境下的数据。

解密时,我们需要进行以下步骤:

发送加密数据

获取平台证书信息

验证签名

使用APIv3密钥和敏感信息加密密钥解密数据

结果处理

步骤四是重点,这里为了方便,我们分别列出APIv3密钥和敏感信息加密密钥的解密代码:

/**

* 解密APIv3密钥

* @param {string} cipherText - 微信支付返回的加密数据

* @param {string} nonce - 随机字符串,与微信支付接口传输的一致

* @param {string} associatedData - 额外数据,与微信支付接口传输的一致

* @param {string} apiversion - 接口版本,例如:"v3"

* @param {string} mchid - 商户号

* @param {string} privateKey - APIv3密钥商户私钥

* @param {string} wechatpayPublicKey - APIv3密钥平台公钥

* @return {string} 解密后的APIv3密钥

*/

async function decryptAPIv3Key(

cipherText,

nonce,

associatedData,

apiversion,

mchid,

privateKey,

wechatpayPublicKey

) {

const localPrivateKey = await parsePrivateKey(privateKey);

const localWechatPublicKey = await parsePublicKey(wechatpayPublicKey);

const APIv3EncryptorInstance = new APIv3Encryptor(

mchid,

localPrivateKey,

localWechatPublicKey,

'/v3/decrypt',

apiversion

);

const ciphertextBuffer = Buffer.from(cipherText, 'base64');

const decryptResult = await APIv3EncryptorInstance.decryptData(

ciphertextBuffer,

Buffer.from(nonce, 'utf8'),

Buffer.from(associatedData, 'utf8')

);

return decryptResult.toString();

}

/**

* 解密敏感信息加密密钥

* @param {string} cipherText - 微信支付返回的加密数据

* @param {string} nonce - 随机字符串,与微信支付接口传输的一致

* @param {string} associatedData - 额外数据,与微信支付接口传输的一致

* @param {string} apiversion - 接口版本,例如:"v3"

* @param {string} mchid - 商户号

* @param {string} key - APIv3密钥

* @return {string} 解密后的敏感信息加密密钥

*/

async function decryptSymmetricKey(cipherText, nonce, associatedData, apiversion, mchid, key) {

const plainKey = await decryptAPIv3Key(key, nonce, associatedData, apiversion, mchid);

const decryptedBuffer = await aesgcm.decrypt(

Buffer.from(cipherText, 'base64'),

Buffer.from(plainKey, 'base64'),

Buffer.from(associatedData, 'utf8'),

Buffer.from(nonce, 'utf8')

);

return decryptedBuffer.toString('utf8');

}

3. 总结

微信支付 v3版的加密和解密方法有了重大升级,其实现方式涉及到多个参数和密钥,对于开发者要求较高。因此,我们需要借助官方提供的类库,以及对Node.js加密库crypto的深入理解,逐步构建起理解微信支付v3版的方法论,进而进行程序开发和功能优化。