- 微信API加密,排坑整理,PHP版本
微信推出了API加密解密的写法,主要应用于一些涉及交易的接口,如同城配送~,在对接同城配送的过程中,我踩了许多的坑,自身原因一部分、文档原因一部分,现在就来讲讲PHP如何一步步的实现解决API加密、解密的写法: 1.首先在 开发->开发管理->AP安全 ,设置几个密钥,并获取平台证书[图片] [图片] 2.直接上代码。 <?php /** * 封装微信api签名安全类 */ namespace common\components\wxapi; use common\helpers\MiniHelper; use phpseclib\Crypt\RSA; use Yii; use yii\base\ErrorException; class WxApi { private $appId; private $aes; private $rsa; private $cert; private $url; public function __construct() { $this->appId = Yii::$app->params['miniWechat']['appId']; $wechatSafeApi = Yii::$app->params['wechatSafeApi']; $this->aes['sn'] = $wechatSafeApi['aes-sn']; $this->aes['key'] = file_get_contents(dirname(Yii::$app->getBasePath()) . $wechatSafeApi['aes-key']); $this->rsa['sn'] = $wechatSafeApi['rsa-sn']; $this->rsa['rsa-public-key'] = file_get_contents(dirname(Yii::$app->getBasePath()) . $wechatSafeApi['rsa-public-key']); $this->rsa['rsa-private-key'] = file_get_contents(dirname(Yii::$app->getBasePath()) . $wechatSafeApi['rsa-private-key']); $this->cert['sn'] = $wechatSafeApi['cert-sn']; $this->cert['cert-key'] = file_get_contents(dirname(Yii::$app->getBasePath()) . $wechatSafeApi['cert-key']); } /** * Name:对外方法用于所有微信api的请求方法 * User: zcw * Date: 2023/7/14 * Time: 9:51 * @param $url * @param $req * @throws ErrorException * @throws \Exception */ public function request($url, $req) { $accessToken = $this->getAccessToken(); $this->url = $url; $urls = $url . "?access_token=" . $accessToken; //1.数据加密 $newRe = $this->getRequestParam($url, $req); //2.获取签名 $signature = $this->getSignature($newRe); //本地验签 非必需 $checkLocalSig = $this->checkLocalSignature($newRe, $signature); if (!$checkLocalSig) { throw new ErrorException('本地验签错误'); } $appId = $this->appId; $headerArray = ['Wechatmp-Appid:' . $appId, 'Wechatmp-TimeStamp:' . $newRe['ts'], 'Wechatmp-Signature:' . $signature]; $data = $this->curlPost($urls, $newRe['reqData'], $headerArray); $headers = $this->httpParseHeaders($data['header']); $body = \Qiniu\json_decode($data['body'], true); //请求平台报错 if (isset($body['errcode'])) { throw new ErrorException($body['errmsg']); } // 3.响应参数验签 目前存在问题 $vertify = $this->vertifyResponse($data); //4.参数解密 return $this->jM($headers['Wechatmp-TimeStamp'], $body); } /** * Name:获取accessToken * User: zcw * Date: 2023/7/14 * Time: 9:15 */ public function getAccessToken() { $qr = new MiniHelper(); return $qr->getAccessToken(); } /** * Name:post请求 * User: zcw * Date: 2023/7/14 * Time: 9:19 * @param $url * @param $field * @param $header * @return array */ public function curlPost($url, $field, $header) { $headerArray = array("Content-type:application/json;charset=utf-8", "Accept:application/json"); $headerArray = array_merge($headerArray, $header); $curl = curl_init(); curl_setopt($curl, CURLOPT_HTTPHEADER, $headerArray); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_POST, 1); curl_setopt($curl, CURLOPT_POSTFIELDS, $field); //输出响应头部 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_HEADER, true); $str = curl_exec($curl); $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE); $headers = substr($str, 0, $headerSize); $body = substr($str, $headerSize); curl_close($curl); return ['body' => $body, 'header' => $headers]; } /** * Name:对外方法用于所有微信api的请求通道 * User: zcw * Date: 2023/7/14 * Time: 9:21 * @param $url * @param $req * @return array * @throws \Exception */ public function getRequestParam($url, $req) { $key = base64_decode($this->aes['key']); $sn = $this->aes['sn']; $appId = $this->appId; $time = time(); //16位随机字符 $nonce = rtrim(base64_encode(random_bytes(16)), '='); $addReq = ["_n" => $nonce, "_appid" => $appId, "_timestamp" => $time]; $realReq = array_merge($addReq, $req); $realReq = json_encode($realReq); //额外参数 $aad = $url . "|" . $appId . "|" . $time . "|" . $sn; //12位随机字符 $iv = random_bytes(12); $cipher = openssl_encrypt($realReq, "aes-256-gcm", $key, OPENSSL_RAW_DATA, $iv, $tag, $aad); $iv = base64_encode($iv); $data = base64_encode($cipher); $authTag = base64_encode($tag); $reqData = ["iv" => $iv, "data" => $data, "authtag" => $authTag]; //校验本地加密是否正确 非必须 // $checkParam = $this->checkParam($key, $authTag, $iv, $data, $aad); return ['ts' => $time, 'reqData' => json_encode($reqData)]; } /** * Name:请求前本地验签 * User: zcw * Date: 2023/7/14 * Time: 9:57 * @param $key * @param $authTag * @param $iv * @param $data * @param $aad */ private function checkParam($key, $authTag, $iv, $data, $aad) { $iv = base64_decode($iv); $data = base64_decode($data); $authTag = base64_decode($authTag); return openssl_decrypt($data, "aes-256-gcm", $key, OPENSSL_RAW_DATA, $iv, $authTag, $aad); } /** * Name:获取签名 * User: zcw * Date: 2023/7/14 * Time: 10:03 * @param array $newRe */ private function getSignature(array $newRe) { $time = $newRe['ts']; $key = $this->rsa['rsa-private-key']; $url = $this->url; $appId = $this->appId; $reqData = $newRe['reqData']; $payload = "$url\n$appId\n$time\n$reqData"; $rsa = new RSA(); $rsa->loadKey($key); $rsa->setHash("sha256"); $rsa->setMGFHash("sha256"); $signature = $rsa->sign($payload); return base64_encode($signature); } /** * Name:请求前本地验签 * User: zcw * Date: 2023/7/14 * Time: 10:11 * @param array $newRe * @param string $signature */ private function checkLocalSignature(array $newRe, string $signature) { $signature = base64_decode($signature); $rsaPubKey = $this->rsa['rsa-public-key']; $appId = $this->appId; $url = $this->url; $time = $newRe['ts']; $reqData = $newRe['reqData']; $payload = "$url\n$appId\n$time\n$reqData"; $payload = utf8_encode($payload); $rsa = new RSA(); $rsa->loadKey($rsaPubKey); $rsa->setHash("sha256"); $rsa->setMGFHash("sha256"); return $rsa->verify($payload, $signature); } /** * Name:解析头部信息 * User: zcw * Date: 2023/7/14 * Time: 10:28 * @param $headerString * @return array */ private function httpParseHeaders($headerString) { $headers = []; $lines = explode("\r\n", $headerString); foreach ($lines as $line) { $line = trim($line); if (!empty($line)) { $parts = explode(':', $line, 2); $key = trim($parts[0]); $value = isset($parts[1]) ? trim($parts[1]) : ''; $headers[$key] = $value; } } return $headers; } /** * Name:解密参数 * User: zcw * Date: 2023/7/14 * Time: 10:31 * @param $ts * @param $body * @return mixed|null * @throws ErrorException */ private function jM($ts, $body) { $url = $this->url; $appId = $this->appId; $sn = $this->aes['sn']; $aad = $url . '|' . $appId . '|' . $ts . '|' . $sn; $key = $this->aes['key']; $key = base64_decode($key); $iv = base64_decode($body['iv']); $data = base64_decode($body['data']); $authTag = base64_decode($body['authtag']); $result = openssl_decrypt($data, "aes-256-gcm", $key, OPENSSL_RAW_DATA, $iv, $authTag, $aad); if (!$result) { throw new ErrorException(); } $result = \Qiniu\json_decode($result,true); return $result; } /** * Name:验证响应值 * User: zcw * Date: 2023/7/14 * Time: 11:16 * @param $data */ private function vertifyResponse($data) { $headers = $this->httpParseHeaders($data['header']); $nowTime = time(); $reTime = $headers['Wechatmp-TimeStamp']; $appId = $this->appId; $cert = $this->cert; $sn = $cert['sn']; $key = $cert['cert-key']; $url = $this->url; if ($appId != $headers['Wechatmp-Appid'] || $nowTime - $reTime > 300){ throw new \ErrorException('返回值安全字段校验失败'); } if ($sn == $headers['Wechatmp-Serial']) { $signature = $headers['Wechatmp-Signature']; } elseif ($sn == $headers['Wechatmp-Serial-Deprecated']) { $signature = $headers['Wechatmp-Signature-Deprecated']; } else { throw new \ErrorException('返回值sn不匹配'); } $reData = $data['body']; $payload = "$url\n$appId\n$reTime\n$reData"; $payload = utf8_encode($payload); $signature = base64_decode($signature); $rsa = new RSA(); $rsa->loadKey($key); $rsa->setHash("sha256"); $rsa->setMGFHash("sha256"); return $rsa->verify($payload, $signature); } } 以上是我的代码 ,每个方法都有明确注释,除了返回值使用平台证书未通过以外 ,其余几个环节均正常,大家也可以帮我找找平台证书对返回数据验签失败的原因,有需要也可以加我微信 abc1783475843
2023-07-24 - 同城配送排坑贴,打工人永不加班!
同城配送,微信最新出炉的,微信官方对接好多个运力方,价格会稍微低一点,我们只需要充值就可以使用了,但是因为是新出的,文档还是在线文档,而且比较绕,看讨论组里面比较多人也遇到坑,我对接完了就细细盘点一下吧,废话不多说,开搞! 同城配送产品介绍:https://docs.qq.com/doc/DVWRCSGllWWZBbG9t 同城配送API文档:https://doc.weixin.qq.com/doc/w3_ADkAtAZ1ACchtE1J1bXRzGqhpUnYX?scode=AJEAIQdfAAoySHzTsMADkAtAZ1ACc 官方加解密教程Demo:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/getting_started/api_signature.html 我用的开发语言是JAVA!!其他语言的兄弟姐妹们可以借鉴一下找找思路,本帖我是调用同城配送的查询店铺接口 [图片] 第1⃣️坑:错误代码:934011, 错误信息:request_body is required 这个是因为接口需要进行加密传输,如果请求头Header不带这几个参数的话就会报这个错,appid和timestamp参数的话还好说,signature这个需要后续通过不同参数拼接进行base64才可以计算出来(后面会贴代码) [图片] 第2⃣️坑:签名失败{"errcode": 40234,"errmsg": "invalid signature rid: 64b0c114-748bb871-181713e7"} 这个也是比较头疼的,也是比较多人遇到的错误,原因是加密出来的签名有问题。 加密需要拿到 对称密钥和 非对称密钥 登录小程序后台-开发管理-》开发设置-》API安全 贴图:非对称的密文只有在初始化的时候可以下载(已经开启了的可以按修改,保存前下载,确保下载回来的是----BEGIN PRIVATE KEY----开头的,因为官方前两天有bug,点击下载回来的还是非对称明文,不过腾讯已经说让相关人员去优化了) [图片] [图片] 我暂且给这几个参数取名ABCD,方便后续看代码! 直接贴数据加密的代码:代码是官方给出的加密demo,这里比较简单,直接看代码,按照上图的ABCD对好入座 import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.util.Arrays; import java.util.Base64; import com.google.gson.Gson; import com.google.gson.JsonObject; public class AES_Enc { private static JsonObject getReqForSign(JsonObject ctx, JsonObject req) { Gson gson = new Gson(); // 开发者本地信息 String local_appid = ctx.get("local_appid").getAsString(); String url_path = ctx.get("url_path").getAsString(); String local_sym_sn = ctx.get("local_sym_sn").getAsString(); String local_sym_key = ctx.get("local_sym_key").getAsString(); //加密签名使用的统一时间戳 long localTs = System.currentTimeMillis() / 1000; String nonce = generateNonce(); req.addProperty("_n", nonce); req.addProperty("_appid", local_appid); req.addProperty("_timestamp", localTs); String plaintext = gson.toJson(req); String aad = url_path + "|" + local_appid + "|" + localTs + "|" + local_sym_sn; byte[] realKey = Base64.getDecoder().decode(local_sym_key); byte[] realIv = generateRandomBytes(12); byte[] realAad = aad.getBytes(StandardCharsets.UTF_8); byte[] realPlaintext = plaintext.getBytes(StandardCharsets.UTF_8); try { Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); SecretKeySpec keySpec = new SecretKeySpec(realKey, "AES"); GCMParameterSpec parameterSpec = new GCMParameterSpec(128, realIv); cipher.init(Cipher.ENCRYPT_MODE, keySpec, parameterSpec); cipher.updateAAD(realAad); byte[] ciphertext = cipher.doFinal(realPlaintext); byte[] encryptedData = Arrays.copyOfRange(ciphertext, 0, ciphertext.length - 16); byte[] authTag = Arrays.copyOfRange(ciphertext, ciphertext.length - 16, ciphertext.length); String iv = base64Encode(realIv); String data = base64Encode(encryptedData); String authtag = base64Encode(authTag); JsonObject reqData = new JsonObject(); reqData.addProperty("iv", iv); reqData.addProperty("data", data); reqData.addProperty("authtag", authtag); JsonObject reqforsign = new JsonObject(); reqforsign.addProperty("req_ts", localTs); reqforsign.addProperty("req_data", reqData.toString()); return reqforsign; } catch (Exception e) { e.printStackTrace(); } return null; } private static String generateNonce() { byte[] nonce = generateRandomBytes(16); return base64Encode(nonce).replace("=", ""); } private static byte[] generateRandomBytes(int length) { byte[] bytes = new byte[length]; new SecureRandom().nextBytes(bytes); return bytes; } private static String base64Encode(byte[] data) { return Base64.getEncoder().encodeToString(data); } private static JsonObject getCtx() { JsonObject ctx = new JsonObject(); // 仅做演示,敏感信息请勿硬编码 ctx.addProperty("local_sym_key", "YcMLjYryM9L3I*************+W3dUZDm6Fj8="); ctx.addProperty("local_sym_sn", "6bda832d********ba2c5c072df"); ctx.addProperty("local_appid", "wx55e9*******d09"); ctx.addProperty("url_path", "https://api.weixin.qq.com/cgi-bin/express/intracity/querystore"); return ctx; } private static JsonObject getRawReq() { JsonObject req = new JsonObject(); req.addProperty("wx_store_id", "40000000************"); return req; } public static void main(String[] args) { JsonObject req = getRawReq(); JsonObject ctx = getCtx(); JsonObject reqforsign = getReqForSign(ctx, req); if (reqforsign != null) { System.out.println(reqforsign.get("req_ts").getAsLong()); System.out.println(reqforsign.get("req_data").getAsString()); } } public static JsonObject getData() { JsonObject req = getRawReq(); JsonObject ctx = getCtx(); JsonObject reqforsign = getReqForSign(ctx, req); return reqforsign; } } [图片] [图片] 代码参数说明如图,此时执行main方法打印如下: [图片] OK!到此请求参数的加密就有了,加下来就到请求头的签名了。 第3⃣️坑:非对称加密的私钥 我们直接下载回来的PRIVATE KEY是不能够直接使用的,直接使用签名的时候会报错:java.lang.IllegalArgumentException: Illegal base64 character 2d 官方也有说明:需要转换一下 [图片] 具体操作: 将下载回来的私钥改一下名:我们改成:private_key.pem [图片] 在私钥的当前路径打开终端 先执行: openssl rsa -in private_key.pem -outform der -out private_key.der 再执行: openssl pkcs8 -topk8 -inform der -in private_key.der -outform pem -out private_key_pkcs8.pem -nocrypt OK!现在我们得到了这三个 [图片] 其中private_key_pkcs8.pem里面的内容我们就可以直接替换官方的demo代码了!! 废话不多说,直接上代码 // RSAwithSHA256 import com.google.gson.JsonObject; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.interfaces.RSAPrivateKey; import java.security.spec.MGF1ParameterSpec; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.PSSParameterSpec; import java.util.Base64; public class RSA_Sign { public static String getSignature(JsonObject ctx, JsonObject req) { String signatureBase64 = null; // 开发者本地信息 String local_appid = ctx.get("local_appid").getAsString(); String url_path = ctx.get("url_path").getAsString(); String local_sym_sn = ctx.get("local_sym_sn").getAsString(); String local_private_key = ctx.get("local_private_key").getAsString(); // 待请求API数据 long reqTs = req.get("req_ts").getAsLong(); String reqData = req.get("req_data").getAsString(); String payload = url_path + "\n" + local_appid + "\n" + reqTs + "\n" + reqData; byte[] dataBuffer = payload.getBytes(StandardCharsets.UTF_8); try { local_private_key = local_private_key.replace("-----BEGIN PRIVATE KEY-----", ""); local_private_key = local_private_key.replace("-----END PRIVATE KEY-----", ""); local_private_key = local_private_key.replaceAll("\\s+", ""); byte[] decoded = Base64.getDecoder().decode(local_private_key); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoded); RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(keySpec); Signature signature = Signature.getInstance("RSASSA-PSS"); // salt长度,需与SHA256结果长度(32)一致 PSSParameterSpec pssParameterSpec = new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1); signature.setParameter(pssParameterSpec); signature.initSign(priKey); signature.update(dataBuffer); byte[] sigBuffer = signature.sign(); signatureBase64 = Base64.getEncoder().encodeToString(sigBuffer); } catch (Exception e) { e.printStackTrace(); } return signatureBase64; /* 最终请求头字段 { "Wechatmp-Appid": local_appid, "Wechatmp-TimeStamp": req_ts, "Wechatmp-Signature": sig, } */ } private static JsonObject getReq() { JsonObject req = new JsonObject(); req.addProperty("req_ts", 1689259852); req.addProperty("req_data", "{\"iv\":\"EehrptDmV/5gxjUT\",\"data\":\"zHlBtadb+dfEHwk4x1c4GZdz1MDSDV/3CAy4qYuMdkkxPOXDiQfJZCxnjBjivKNwPAZfKZZh3nv/xQyM8ZYUL9VUHP4M2kAcOMbvKLKnB7j4Lxwioiir4R+IHV7mwWNcL9SrztZ1FP9Nzg7kxc6xD2RrJkzPQ3W46w==\",\"authtag\":\"8Md6QGPY9HpPcLv86dkzyQ==\"}"); return req; } private static JsonObject getCtx() { JsonObject ctx = new JsonObject(); // 仅做演示,敏感信息请勿硬编码 String localPrivateKey = "-----BEGIN PRIVATE KEY-----\n" + "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC971V5m8Ou/MO3\n" + "CEP9MP3tUXJu2fE25YbIFPysRx6SD7hXxCZjNRjQ6q7DENCpt2541v5IdJe3kd8K\n" + "y5oLVDyzcTx6hWnyCInbS3lZJ27aTTS7LesOf8120uU3ma0CbICRhSH/oLWx9DNj\n" + "hpQbQQjracja42VGEqHm/3YlTy6CX0ygfOQ36iuNTvOFErdCffWild9YOZe757l+\n" + "ivTg0hoYq+/WzTCf3F0dsqn7og0eX6AXILBLnqs29cwtc/ov9BdDUxhCuCxghpTn\n" + "Pdd/l7zYOZYfZZM80q2wZWqkbh+75eQ2YAwjw7pBz905DdnGCdoNj7cORE78lQnF\n" + "YuT8VqV2YErQDR6gPZ2KEEtA0paHxBIeuluWgQqfdMGQqsNjRC2l2+Mx2Ly6CjzR\n" + "w70X3Db5x3QCtVRYz8J5303lOcrjFG2uB4w6dRWrCRbnwdlxFbIMdMTG4QKBgQCP\n" + "8RDygSRXrQYpEgWk2GqaxuQSoM0uAw5qhYmV75MeV+ZnBp0ITMvYjfrN9j43U/rC\n" + "l+gjz/KhMrMrxfAcx60TkOhD3W/6xsdNf2wXczP4Gvev5iBmEGLu5IGs4rkh8/AA\n" + "r5VNg659CCIkJMivYpr0GHU+1/ql06ZXpae5UZxgyQKBgQDcQq5dJVsEe0ky5G2O\n" + "GYZ/EPzOBlD+KRJyPVNzwKZws7UlhnNApwwp76/9UR+2PPhl/g5om6oBGygIAYhw\n" + "mx9wZkb1T3+qT2xJ7QMPRKZXfkthmay/7yigGTVRU1DhqmYfY35G+bsg3EsdeyHS\n" + "J5VUJ83ecM67+q+1FBekTZa+iA==\n" + "-----END PRIVATE KEY-----"; ctx.addProperty("local_private_key", localPrivateKey); ctx.addProperty("local_sym_sn", "e84cdda4*********0cee78f277"); ctx.addProperty("local_appid", "wx55******94d09"); ctx.addProperty("url_path", "https://api.weixin.qq.com/cgi-bin/express/intracity/querystore"); return ctx; } public static String getSign(JsonObject data) { JsonObject req = data; JsonObject ctx = getCtx(); String sign = getSignature(ctx, req); return sign; } public static void main(String[] args) { JsonObject req = getReq(); JsonObject ctx = getCtx(); String res = getSignature(ctx, req); System.out.println(res); } } 代码说明: [图片] [图片] 执行main方法打印出来的就是加密签名了,放到请求头里面,如果这个main方法执行错误了,就是你的私钥有问题了 [图片] 最后我们发起请求的参数都全了,去调用一下!! [图片] [图片] 上面是请求返回的响应,也是通过加密的内容,并且请求反应的请求头里面的内容我们也是需要用到的,解密获取返回内容的时候需要用到 请求返回结果解密,直接上代码 // AES256_GCM import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.util.Base64; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; public class AES_Dec { public static Object getRealResp(JsonObject ctx, JsonObject resp) { byte[] decryptedBytes = null; // 开发者本地信息 String local_appid = ctx.get("local_appid").getAsString(); String url_path = ctx.get("url_path").getAsString(); String local_sym_sn = ctx.get("local_sym_sn").getAsString(); String local_sym_key = ctx.get("local_sym_key").getAsString(); // API响应数据,解密只需要响应头时间戳与响应数据 long respTs = resp.get("resp_ts").getAsLong(); String respData = resp.get("resp_data").getAsString(); JsonParser parser = new JsonParser(); JsonElement resp_data = parser.parse(respData); String iv = resp_data.getAsJsonObject().get("iv").getAsString(); String data = resp_data.getAsJsonObject().get("data").getAsString(); String authtag = resp_data.getAsJsonObject().get("authtag").getAsString(); // 构建AAD String aad = url_path + "|" + local_appid + "|" + respTs + "|" + local_sym_sn; // 拼接cipher和authtag byte[] dataBytes = Base64.getDecoder().decode(data); byte[] authtagBytes = Base64.getDecoder().decode(authtag); byte[] new_dataBytes = new byte[dataBytes.length + authtagBytes.length]; System.arraycopy(dataBytes, 0, new_dataBytes, 0, dataBytes.length); System.arraycopy(authtagBytes, 0, new_dataBytes, dataBytes.length, authtagBytes.length); byte[] aadBytes = aad.getBytes(StandardCharsets.UTF_8); byte[] ivBytes = Base64.getDecoder().decode(iv); Object realResp = null; try { byte[] keyBytes = Base64.getDecoder().decode(local_sym_key); SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES"); Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, ivBytes); cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec); cipher.updateAAD(aadBytes); try { decryptedBytes = cipher.doFinal(new_dataBytes); } catch (Exception e) { System.out.println("auth tag验证失败"); return null; } // 解密结果 String decryptedData = new String(decryptedBytes, StandardCharsets.UTF_8); JsonElement element = parser.parse(decryptedData); Gson gson = new Gson(); realResp = gson.fromJson(element, Object.class); long localTs = System.currentTimeMillis() / 1000; // 安全检查,根据业务实际需求判断 if (element.getAsJsonObject().get("_appid").getAsString() == local_appid // appid不匹配 || element.getAsJsonObject().get("_timestamp").getAsLong() != respTs // timestamp与Wechatmp-TimeStamp不匹配 || localTs - element.getAsJsonObject().get("_timestamp").getAsLong() > 300 // 响应数据的时候与当前时间超过5分钟 ) { System.out.println("安全字段校验失败"); return null; } } catch (Exception e) { e.printStackTrace(); } return realResp; } private static JsonObject getCtx() { JsonObject ctx = new JsonObject(); // 仅做演示,敏感信息请勿硬编码 ctx.addProperty("local_sym_key", "YcMLjYryM9L3IFg*********3dUZDm6Fj8="); ctx.addProperty("local_sym_sn", "6bda832d57b*******2c5c072df"); ctx.addProperty("local_appid", "wx55*********d09"); ctx.addProperty("url_path", "https://api.weixin.qq.com/cgi-bin/express/intracity/querystore"); return ctx; } private static JsonObject getResp() { JsonObject resp = new JsonObject(); String respData = "{\"iv\":\"QrZZqNdqxToexKwy\",\"data\":\"CfoqyhQ1oYg2zJ7tvqO4t5\\/KA2zUD33y2D3FIbFShkclYaONiLqROmG8CfHm0EXMADNcvp4EQuxKxrGZbVE97qrymbnpreTT\\/XQtly62A84KrGCLIqi8SF\\/jM75QFH6qEuwy32gSsl7sv2GxDSCdb4JkpFPmskjsZgP3z9vBFUPklTiJtLRDfy\\/MPg9sYT3eVFuD636NO01\\/NkYPAa9WUIIlbDlqk\\/O0xMgBdi84JwqtxW+VuRqSkge30EluRBoiH4j3ngCuK5JGqfPw1MFXL0e55V72iBsSa5fqHDnskl62pKUcR+\\/h4Znw7H5f3U5WnUtsKAzH3hg22+VIgUp5veLOw1PI94c9Ks5A26+4OWIh9wmgUuInZo20g1j50G8ENfNPVcDApSG0P\\/yKJZFAsQrvx2c+pU6sVllo+XTp4eoz2wCNrEedEZ\\/ism02GWmwTtgWALFIedib44fOyNuIfNWwXMg\\/ywzBN37Ercip7vI+Iyd+wPTCjB046wmZYSzVelLY5qc97X7jo6m8+np3VS+NHr09tNGmjG5L\\/FMHO99WuURJqSNdXB3swiOlVbtRCYwA4epzhhnv2b6TzYN9UOLw\",\"authtag\":\"n5OB+4ZdaQU\\/ZE6wE3GO2w==\"}"; resp.addProperty("resp_ts", 1689266097); resp.addProperty("resp_data", respData); return resp; } public static void main(String[] args) { JsonObject resp = getResp(); JsonObject ctx = getCtx(); Object res = getRealResp(ctx, resp); System.out.println(res); } } [图片] 替换一下这里的加密内容和响应时间戳执行即可! 我们执行一下main方法打印出来内容: [图片] OK!大功告成,从请求的加密到结果的解密都完成了!! 具体封装的话大家可以自己去弄,最后贴一下发起请求的方法吧, final String accessToken = this.getWxMaService(request.getWxAppId()).getAccessToken(); log.info("accessToken is =>{}", this.getWxMaService(request.getWxAppId()).getAccessToken()); final JsonObject data = AES_Enc.getData(); final Long reqTs = data.get("req_ts").getAsLong(); final String reqData = data.get("req_data").getAsString(); final String sign = RSA_Sign.getSign(data); log.info("data is ={}", data); final HttpResponse response = HttpRequest.post("https://api.weixin.qq.com/cgi-bin/express/intracity/querystore?access_token=" + accessToken) .header("Wechatmp-Appid", "wx5********4d09") .header("Wechatmp-TimeStamp", reqTs.toString()) .header("Wechatmp-Signature", sign) .header("Content-Type", "application/json;charset=utf-8") .body(reqData) .execute(); final String respSign = response.header("Wechatmp-Signature"); final String respAppId = response.header("Wechatmp-Appid"); final String respTs = response.header("Wechatmp-TimeStamp"); final String respSerial = response.header("Wechatmp-Serial"); log.info("respSign is {}", respSign); log.info("respAppId is {}", respAppId); log.info("respTs is {}", respTs); log.info("respSerial is {}", respSerial); log.info("请求返回结果是:{}", response.body());
2023-07-14