微信支付生成签名和验签SDK源码分析
目录
一、签名分析
1.1 流程分析
1.构造签名串
2.计算签名值
3.设置请求头
二、源码级别分析
二、获取平台证书分析
三、验签分析
3.1 验签使用场景:
3.2 验证流程:
1.获取微信平台证书列表
2.检查平台证书序列号
3.2 验签源码分析
1.分析
2.总结:
在商户系统(自开发)向微信支付平台系统发送请求,和接收结果的过程中,都是有生成签名和校验签名的过程,只是这些操作都被 SDK 的 生成CloseableHttpClient 之前已经处理过了。
证书密钥使用说明-接口规则 | 微信支付商户平台文档中心
一、签名分析
1.1 流程分析
签名的生成分为3步骤,在SDK中也有代码的体现。
签名生成-接口规则 | 微信支付商户平台文档中心
1.构造签名串
签名串一共有五行,每一行为一个参数。行尾以 \n(换行符,ASCII编码值为0x0A)结束,包括最后一行。如果参数本身以\n结束,也需要附加一个\n。
HTTP请求方法\n GET\n
URL\n /v3/certificates\n
请求时间戳\n 1554208460\n
请求随机串\n 593BEC0C930BF1AFEB40B4A08C8FB242\n
请求报文主体\n {ifn:xxxx} \n
2.计算签名值
绝大多数编程语言提供的签名函数支持对签名数据进行签名。强烈建议商户调用该类函数,使用商户私钥对待签名串进行SHA256 with RSA签名,并对签名结果进行Base64编码得到签名值。
通过商户私钥使用 SHA256 withRSA 算法加密 ,在进行 BASE64 处理。
3.设置请求头
微信支付商户API v3要求请求通过HTTP Authorization头来传递签名。 Authorization由认证类型和签名信息两个部分组成。
认证类型 :这是使用什么类型进行加密处理
Authorization: 认证类型 签名信息具体组成为:1.认证类型,目前为WECHATPAY2-SHA256-RSA20482.签名信息发起请求的商户(包括直连商户、服务商或渠道商)的商户号 mchid商户API证书序列号serial_no,用于声明所使用的证书请求随机串nonce_str时间戳timestamp签名值signature注:以上五项签名信息,无顺序要求。Authorization: WECHATPAY2-SHA256-RSA2048 mchid="1900009191",nonce_str="593BEC0C930BF1AFEB40B4A08C8FB242",signature="uOVRnA4qG/MNnYzdQxJanN+zU+lTgIcnU9BxGw5dKjK+VdEUz2FeIoC+D5sB/LN+nGzX3hfZg6r5wT1pl2ZobmIc6p0ldN7J6yDgUzbX8Uk3sD4a4eZVPTBvqNDoUqcYMlZ9uuDdCvNv4TM3c1WzsXUrExwVkI1XO5jCNbgDJ25nkT/c1gIFvqoogl7MdSFGc4W4xZsqCItnqbypR3RuGIlR9h9vlRsy7zJR9PBI83X8alLDIfR1ukt1P7tMnmogZ0cuDY8cZsd8ZlCgLadmvej58SLsIkVxFJ8XyUgx9FmutKSYTmYtWBZ0+tNvfGmbXU7cob8H/4nLBiCwIUFluw==",timestamp="1554208460",serial_no="1DDE55AD98ED71D6EDD4A4A16996DE7B47773A8C"
二、源码级别分析
1.通过log日志的打印得出 在生成 ScheduledUpdateCertificatesVerifier 校验器的时候已经 做好了签名的生成工作。
- 生成签名串
- 计算签名值
- 设置HTTP请求信息 TOKEN参数的封装
2.通过日志分析得出处理类是 WechatPay2Credentials,进行了签名的处理。
public class WechatPay2Credentials implements Credentials {protected static final Logger log = LoggerFactory.getLogger(WechatPay2Credentials.class);// 私用符号protected static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";// 安全随机数生成器protected static final SecureRandom RANDOM = new SecureRandom();// 商户编号protected final String merchantId;// 签名生成器 protected final Signer signer;public WechatPay2Credentials(String merchantId, Signer signer) {this.merchantId = merchantId;this.signer = signer;}public String getMerchantId() {return merchantId;}// 获取当前时间戳 protected long generateTimestamp() {return System.currentTimeMillis() / 1000;}// 生成字符串protected String generateNonceStr() {char[] nonceChars = new char[32];for (int index = 0; index < nonceChars.length; ++index) {nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));}return new String(nonceChars);}// 获取认证类型@Overridepublic final String getSchema() {return "WECHATPAY2-SHA256-RSA2048";}/*** 生成签名串+计算签名值+设置HTTP请求头***/@Overridepublic final String getToken(HttpRequestWrapper request) throws IOException {// 生成随机字符串String nonceStr = generateNonceStr();// 生成当前时间戳long timestamp = generateTimestamp();// 构造签名串String message = buildMessage(nonceStr, timestamp, request);log.debug("authorization message=[{}]", message);// 使用 PrivateKeySigner 进行签名算法处理 ,通过 商户私钥 + SHA256withRSA 的摘要计算 对签名数据进行加密处理 Signer.SignatureResult signature = signer.sign(message.getBytes(StandardCharsets.UTF_8));String token = "mchid=\"" + getMerchantId() + "\","+ "nonce_str=\"" + nonceStr + "\","+ "timestamp=\"" + timestamp + "\","+ "serial_no=\"" + signature.certificateSerialNumber + "\","+ "signature=\"" + signature.sign + "\"";log.debug("authorization token=[{}]", token);return token;}/*** 构造签名串方法 * @Param nonce 随机字符串* @Param timestamp 当前时间戳* @Param request 请求对象***/protected String buildMessage(String nonce, long timestamp, HttpRequestWrapper request) throws IOException {// 1.获取当前的请求路径对象,URI uri = request.getURI();// 2.获取到当前请求uri 路径 String canonicalUrl = uri.getRawPath(); // " /v3/certificates "// 3.判断是否有请求参数如果有则进行拼接上if (uri.getQuery() != null) {canonicalUrl += "?" + uri.getRawQuery();}// 4.签名主体String body = "";// 判断是否微信上传文件请求 请求方法为GET时,报文主体为空。// 当请求方法为POST或PUT时,请使用真实发送的JSON报文。// 图片上传API,请使用meta对应的JSON报文。// PATCH,POST,PUTif (request.getOriginal() instanceof WechatPayUploadHttpPost) {body = ((WechatPayUploadHttpPost) request.getOriginal()).getMeta();// } else if (request instanceof HttpEntityEnclosingRequest) {body = EntityUtils.toString(((HttpEntityEnclosingRequest) request).getEntity(), StandardCharsets.UTF_8);}// 5.构建签名串 GET请求 body 是空的return request.getRequestLine().getMethod() + "\n"+ canonicalUrl + "\n"+ timestamp + "\n"+ nonce + "\n"+ body + "\n";}}
/*** @author xy-peng*/
public class PrivateKeySigner implements Signer {// 商户证书序列号 protected final String certificateSerialNumber;// 商户私钥 protected final PrivateKey privateKey;public PrivateKeySigner(String serialNumber, PrivateKey privateKey) {this.certificateSerialNumber = serialNumber;this.privateKey = privateKey;}// 对签名串进行摘要处理加密处理 @Overridepublic SignatureResult sign(byte[] message) {try {// 创建SHA256withRSA加密方式的签名生成器实例对焦 Signature sign = Signature.getInstance("SHA256withRSA");// 初始化签名生成器,将商户私钥配置sign.initSign(privateKey);// 设置要加密的数据 sign.update(message);// 签名生成方法,并将结果进行 Base64的编码处理,并他封装签名结果对象 return new SignatureResult(Base64.getEncoder().encodeToString(sign.sign()), certificateSerialNumber);} catch (NoSuchAlgorithmException e) {throw new RuntimeException("当前Java环境不支持SHA256withRSA", e);} catch (SignatureException e) {throw new RuntimeException("签名计算失败", e);} catch (InvalidKeyException e) {throw new RuntimeException("无效的私钥", e);}}}
4.在 SignatureExec 的 executeWithSignature 方法中设置HTTP请求信息,并执行请求方法,获取请求结果,并进行结果的验签操作。
签名生成流程总结
- 1.在获取 ScheduledUpdateCertificatesVerifier(签名校验器) 的过程中 调用 了 WechatPay2Credentials的 getToken 方法生成签名,该方法执行了生成: 1.签名串 2.将签名串进行加密处理
- 2.getToken中调用了buildMessage 来生成签名串数据。
- 3.buildMessage 方法中生成
- 4.在getToken方法中调用了PrivateKeySigner 的 sign(), 这个方法底层调用 java. security 包下 的 SignatureSpi 的相关签名处理方法。
- 5.执行完签名的后,SignatureExec 的 executeWithSignature 方法中执行,设置HTTP请求头和执行请求方法来,并解析请求参数验证结果信息。
二、获取平台证书分析
核心的类是 ScheduledUpdateCertificatesVerifier 是在原有CertificatesVerifier基础上,增加定时更新证书功能(默认1小时)
/*** 初始化平台证书管理器实例,在使用前需先调用该方法** @param credentials 认证器* @param apiV3Key APIv3密钥* @param minutesInterval 定时更新间隔时间*/public synchronized void init(Credentials credentials, byte[] apiV3Key, long minutesInterval) {if (credentials == null || apiV3Key.length == 0 || minutesInterval == 0) {throw new IllegalArgumentException("credentials或apiV3Key或minutesInterval为空");}if (this.credentials == null || this.apiV3Key.length == 0 || this.executor == null|| this.certificates == null) {this.credentials = credentials;this.apiV3Key = apiV3Key;this.executor = new SafeSingleScheduleExecutor();// 创建线程池this.certificates = new ConcurrentHashMap<>(); // 证书存放的Map// 初始化证书initCertificates();// 启动定时更新证书任务Runnable runnable = () -> {try {Thread.currentThread().setName(SCHEDULE_UPDATE_CERT_THREAD_NAME);log.info("Begin update Certificate.Date:{}", Instant.now());updateCertificates(); // 更新证书的方法log.info("Finish update Certificate.Date:{}", Instant.now());} catch (Throwable t) {log.error("Update Certificate failed", t);}};// 执行线程任务 executor.scheduleAtFixedRate(runnable, 0, minutesInterval, TimeUnit.MINUTES);}}// 更新证书的方法private void updateCertificates() {Verifier verifier = null;if (!certificates.isEmpty()) {verifier = new CertificatesVerifier(certificates);}// 调用下载证书的方法downloadAndUpdateCert(verifier);}
// 初始化证书方法
private void initCertificates() {downloadAndUpdateCert(null);}
// 线程安全的证书下载方法
private synchronized void downloadAndUpdateCert(Verifier verifier) {// 创建出httpClient对象try (CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create().withCredentials(credentials).withValidator(verifier == null ? (response) -> true: new WechatPay2Validator(verifier)).build()) {// 发送GET请求设置请求通信息HttpGet httpGet = new HttpGet(CERT_DOWNLOAD_PATH);httpGet.addHeader(ACCEPT, APPLICATION_JSON.toString());// 发送请求try (CloseableHttpResponse response = httpClient.execute(httpGet)) {// 获取结果code int statusCode = response.getStatusLine().getStatusCode();// 获取结果数据 String body = EntityUtils.toString(response.getEntity());// 如果结果的 200 if (statusCode == SC_OK) {// 将证书文件解码后存放到map中Map<BigInteger, X509Certificate> newCertList = CertSerializeUtil.deserializeToCerts(apiV3Key, body);if (newCertList.isEmpty()) {log.warn("Cert list is empty");return;}// 清除所有的证书certificates.clear();// 重新添加证书 certificates.putAll(newCertList);} else {log.error("Auto update cert failed, statusCode = {}, body = {}", statusCode, body);}}} catch (IOException | GeneralSecurityException e) {log.error("Download Certificate failed", e);}}
总结:
证书每隔60分钟刷新证书,两个核心东西一个是定时器一个是存储身份信息对象(Credentials),证书更新是调用证书下载的方法,证书信息被放到响应体中被加密处理过,用的是对称加密的密钥,获取到证书数据后进行解密,然后存放到缓存Map中,每次重新获取证书都会清空当前map。
三、验签分析
3.1 验签使用场景:
需要验证签名的时间点分为两处:
- 1.商户(我们系统)主要向微信支付系统发送,微信支付系统向我们响应结果时。
- 2.微信系统回调商户系统,需要对数据验证操作,防止伪造数据
注意:应答签名验证是可做可不做的,回调必须要验证签名。
如果验证商户的请求签名正确,微信支付会在应答的HTTP头部中包括应答签名。我们建议商户验证应答签名。同样的,微信支付会在回调的HTTP头部中包括回调报文的签名。商户必须 验证回调的签名,以确保回调是由微信支付发送。
3.2 验证流程:
签名验证-接口规则 | 微信支付商户平台文档中心
1.获取微信平台证书列表
- 接口:https://api.mch.weixin.qq.com/v3/certificates
- 在上方验签请求就是在获取微信平台证书。
- 微信支付的公钥只能从从微信平台证书中获取。
- 微信支付的公钥只能从从微信平台证书中获取。
- 平台证书是一个列表,因为证书是有一个有效期的,如果证书过期,新的证书还没有生成,那么就会有一个证书的空档期,所有微信会把提前24小时的新证书加入到平台证书列表中,所以平台证书会存在两个一个是将要过期的,一个即将启用的。
- 返回结果会提供微信支付平台证书的序列号,serial_no 进行证书区分。
200: OK {"data": [{"serial_no": "5157F09EFDC096DE15EBE81A47057A7232F1B8E1","effective_time ": "2018-06-08T10:34:56+08:00","expire_time ": "2018-12-08T10:34:56+08:00","encrypt_certificate": {"algorithm": "AEAD_AES_256_GCM","nonce": "61f9c719728a","associated_data": "certificate","ciphertext": "sRvt… "}},{"serial_no": "50062CE505775F070CAB06E697F1BBD1AD4F4D87","effective_time ": "2018-12-07T10:34:56+08:00","expire_time ": "2020-12-07T10:34:56+08:00","encrypt_certificate": {"algorithm": "AEAD_AES_256_GCM","nonce": "35f9c719727b","associated_data": "certificate","ciphertext": "aBvt… "}}] }
2.检查平台证书序列号
微信支付的平台证书序列号位于HTTP头Wechatpay-Serial。验证签名前,请商户先检查序列号是否跟商户当前所持有的 微信支付平台证书的序列号一致。如果不一致,请重新获取证书。否则,签名的私钥和证书不匹配,将无法成功验证签名。
3.构造签名串
- 首先,商户先从应答中获取以下信息:
- HTTP头Wechatpay-Timestamp 中的应答时间戳
- HTTP头Wechatpay-Nonce 中的应答随机串。
- 应答主体(response Body),需要按照接口返回的顺序进行验签,错误的顺序将导致验签失败。
- 然后,请按照以下规则构造应答的验签名串。签名串共有三行,行尾以\n 结束,包括最后一行。\n为换行符(ASCII编码值为0x0A)。若应答报文主体为空(如HTTP状态码为204 No Content),最后一行仅为一个\n换行符。
应答时间戳\n
应答随机串\n
应答报文主体\n
- 如某个应答的HTTP报文为(省略了ciphertext的具体内容):
HTTP/1.1 200 OK Server: nginx Date: Tue, 02 Apr 2019 12:59:40 GMT Content-Type: application/json; charset=utf-8 Content-Length: 2204 Connection: keep-alive Keep-Alive: timeout=8 Content-Language: zh-CN Request-ID: e2762b10-b6b9-5108-a42c-16fe2422fc8a Wechatpay-Nonce: c5ac7061fccab6bf3e254dcf98995b8c // 应答随机串 // 应答签名 Wechatpay-Signature: CtcbzwtQjN8rnOXItEBJ5aQFSnIXESeV28Pr2YEmf9wsDQ8Nx25ytW6FXBCAFdrr0mgqngX3AD9gNzjnNHzSGTPBSsaEkIfhPF4b8YRRTpny88tNLyprXA0GU5ID3DkZHpjFkX1hAp/D0fva2GKjGRLtvYbtUk/OLYqFuzbjt3yOBzJSKQqJsvbXILffgAmX4pKql+Ln+6UPvSCeKwznvtPaEx+9nMBmKu7Wpbqm/+2ksc0XwjD+xlvlECkCxfD/OJ4gN3IurE0fpjxIkvHDiinQmk51BI7zQD8k1znU7r/spPqB+vZjc5ep6DC5wZUpFu5vJ8MoNKjCu8wnzyCFdA== Wechatpay-Timestamp: 1554209980 // 应答时间戳 Wechatpay-Serial: 5157F09EFDC096DE15EBE81A47057A7232F1B8E1 Cache-Control: no-cache, must-revalidate // 应答报文主体 {"data":[{"serial_no":"5157F09EFDC096DE15EBE81A47057A7232F1B8E1","effective_time":"2018-03-26T11:39:50+08:00","expire_time":"2023-03-25T11:39:50+08:00","encrypt_certificate":{"algorithm":"AEAD_AES_256_GCM","nonce":"4de73afd28b6","associated_data":"certificate","ciphertext":"..."}}]}
- 则验签名串为
1554209980 c5ac7061fccab6bf3e254dcf98995b8c {"data":[{"serial_no":"5157F09EFDC096DE15EBE81A47057A7232F1B8E1","effective_time":"2018-03-26T11:39:50+08:00","expire_time":"2023-03-25T11:39:50+08:00","encrypt_certificate":{"algorithm":"AEAD_AES_256_GCM","nonce":"4de73afd28b6","associated_data":"certificate","ciphertext":"..."}}]}
4.获取应答签名
- 微信支付的应答签名通过HTTP头Wechatpay-Signature传递。(注意,示例因为排版可能存在换行,实际数据应在一行)
- 对 Wechatpay-Signature的字段值使用Base64进行解码,得到应答签名。
Wechatpay-Signature: CtcbzwtQjN8rnOXItEBJ5aQFSnIXESeV28Pr2YEmf9wsDQ8Nx25ytW6FXBCAFdrr0mgqngX3AD9gNzjnNHzSGTPBSsaEkIfhPF4b8YRRTpny88tNLyprXA0GU5ID3DkZHpjFkX1hAp/D0fva2GKjGRLtvYbtUk/OLYqFuzbjt3yOBzJSKQqJsvbXILffgAmX4pKql+Ln+6UPvSCeKwznvtPaEx+9nMBmKu7Wpbqm/+2ksc0XwjD+xlvlECkCxfD/OJ4gN3IurE0fpjxIkvHDiinQmk51BI7zQD8k1znU7r/spPqB+vZjc5ep6DC5wZUpFu5vJ8MoNKjCu8wnzyCFdA==
5.验证签名
- 很多编程语言的签名验证函数支持对验签名串和签名 进行签名验证。强烈建议商户调用该类函数,使用微信支付平台公钥对验签名串和签名进行SHA256 with RSA签名验证。
- (1) 首先,从微信支付平台证书导出微信支付平台公钥
- (2) 然后,把签名base64解码。
- (3) 最后验证签名,验证签名的流程是:
- 1.拿到刚刚生成签名,使用 SHA256 算法+ 微信公钥 进行摘要计算。
- 2.将得到的结果和 应到签名进行比较判断是否一致,如果一致则为正常,否则失败。
3.2 验签源码分析
1.分析
通过源码分析得出真正的验签的类是 WechatPay2Validator ,这个类中有一个方法 validate 进行执行验证操作。
package com.wechat.pay.contrib.apache.httpclient.auth;import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.REQUEST_ID;
import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_NONCE;
import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_SERIAL;
import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_SIGNATURE;
import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_TIMESTAMP;import com.wechat.pay.contrib.apache.httpclient.Validator;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** @author xy-peng*/
public class WechatPay2Validator implements Validator {protected static final Logger log = LoggerFactory.getLogger(WechatPay2Validator.class);/*** 应答超时时间,单位为分钟*/protected static final long RESPONSE_EXPIRED_MINUTES = 5;// 验证器protected final Verifier verifier;public WechatPay2Validator(Verifier verifier) {this.verifier = verifier;}// 参数异常处理方法protected static IllegalArgumentException parameterError(String message, Object... args) {message = String.format(message, args);return new IllegalArgumentException("parameter error: " + message);}// 验证失败处理异常protected static IllegalArgumentException verifyFail(String message, Object... args) {message = String.format(message, args);return new IllegalArgumentException("signature verify fail: " + message);}// 核心方法验证方法@Overridepublic final boolean validate(CloseableHttpResponse response) throws IOException {try {// 验证响应的结果参数是validateParameters(response);// 生成签名String message = buildMessage(response);// 获取微信平台证书序列号String serial = response.getFirstHeader(WECHAT_PAY_SERIAL).getValue();// 获取当前应答签名String signature = response.getFirstHeader(WECHAT_PAY_SIGNATURE).getValue();// 验签比较方法,if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",serial, message, signature, response.getFirstHeader(REQUEST_ID).getValue());}} catch (IllegalArgumentException e) {log.warn(e.getMessage());return false;}return true;}// 校验参数方法protected final void validateParameters(CloseableHttpResponse response) {// 获取第一个请求为 Request-ID Header firstHeader = response.getFirstHeader(REQUEST_ID);// 如果请求头为null则验证失败 if (firstHeader == null) {throw parameterError("empty " + REQUEST_ID);}// 获取请求id String requestId = firstHeader.getValue();// NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last// 组装一个 响应的参数名称集合 有 证书序列号、应答签名、时间戳、随机字符串String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};Header header = null;// 遍历所需要的请求的参数名取从应答对象中拿如果没有则报错for (String headerName : headers) {// 拿去所需要的应答对象数据header = response.getFirstHeader(headerName);if (header == null) {throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);}}// 因为最后的值是时间戳,所以这里直接获取到时间戳了String timestampStr = header.getValue();try {// 将时间戳进行转换为时间Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));// 比较应答时间和当前时间超过默认5分钟, 拒绝过期应答if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);}} catch (DateTimeException | NumberFormatException e) {throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);}}// 验签名串protected final String buildMessage(CloseableHttpResponse response) throws IOException {String timestamp = response.getFirstHeader(WECHAT_PAY_TIMESTAMP).getValue();String nonce = response.getFirstHeader(WECHAT_PAY_NONCE).getValue();String body = getResponseBody(response);return timestamp + "\n"+ nonce + "\n"+ body + "\n";}// 获取报文主体结果protected final String getResponseBody(CloseableHttpResponse response) throws IOException {HttpEntity entity = response.getEntity();return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";}}
- 真正验签会走到 CertificatesVerifier 的 verify 方法
- certificates.get(val) 是根据 微信平台证书序列号从map中获取证书信息
- 获取到验证器会调用 verify 进行组装 参数 最终调用 Signature 的 verify 进行验证
protected boolean verify(X509Certificate certificate, byte[] message, String signature) {try {// 创建签名器,并指定签名算法Signature sign = Signature.getInstance("SHA256withRSA");sign.initVerify(certificate); // 设置签名器使用的证书公钥sign.update(message);// 要验证的数据// 验证传入的签名数据 也就是 update 和 verify 数据进行比较return sign.verify(Base64.getDecoder().decode(signature));} catch (NoSuchAlgorithmException e) {throw new RuntimeException("当前Java环境不支持SHA256withRSA", e);} catch (SignatureException e) {throw new RuntimeException("签名验证过程发生了错误", e);} catch (InvalidKeyException e) {throw new RuntimeException("无效的证书", e);}}
- 从图中可以看出 传来参数 var1 就是应答的签名,首先对应答对象进行了 rsa 公钥的解密得到 var3
- var2 是对生成的签名串进行摘要计算
- 最后将应答签名和生成的签名进行equal比较。
// sigBytes 应答的签名结果
@Overrideprotected boolean engineVerify(byte[] sigBytes) throws SignatureException {if (publicKey == null) {throw new SignatureException("Missing public key");}try {if (sigBytes.length != RSACore.getByteLength(publicKey)) {throw new SignatureException("Signature length not correct: got " +sigBytes.length + " but was expecting " +RSACore.getByteLength(publicKey));}// 获取生成的签名串的摘要结果byte[] digest = getDigestValue();// 将应答数据进行rsa使用微信公钥解密byte[] decrypted = RSACore.rsa(sigBytes, publicKey);// 将处理的值进行拆包处理byte[] unpadded = padding.unpad(decrypted);// 使用digestOID 进行解密处理 得出 摘要的数据byte[] decodedDigest = decodeSignature(digestOID, unpadded);// 将摘要的值和应答的值进行equal比较return MessageDigest.isEqual(digest, decodedDigest);} catch (javax.crypto.BadPaddingException e) {// occurs if the app has used the wrong RSA public key// or if sigBytes is invalid// return false rather than propagating the exception for// compatibility/ease of usereturn false;} catch (IOException e) {throw new SignatureException("Signature encoding error", e);} finally {resetDigest();}}
2.总结:
正常的签名流程是 是对数据先进行摘要,在进行加密处理,然后在进行Base64的处理,验签也是反过来,最后一部就是验证签名
1.获取应答对象的请求头数据
2.将数据生成签名串
3.获取应答签名串
4.将生成的签名串进行摘要计算,生成摘要串。
5.使用rsa算法对应答签名进行解密得出摘要结果。
6.将生成的摘要串和解密的摘要串进行比较 。
检验的话他会先将用公钥对解码后(base64)的数据进行解密获取到加密的数据,在后用 使用rsa算法+微信公钥进行解码得出 sha256 对之前获取到微信的签名串数据进行摘要运算,在将在将两个摘要数据进行比较是否相同从而验证是否合法性。因为摘要运算,相同的数据得出的结果都是相同的。
微信支付生成签名和验签SDK源码分析相关推荐
- 微信支付v3 签名与验签
微信支付v3 新需求为了在网页上进行Native支付,在开发过程中走了很多弯路,网上的代码很多运行无法正常加解密,经过几天的读文档,百度,终于调通. 微信文档详见微信支付开发文档 签名 HTTP请求方 ...
- 使用RSA、MD5对参数生成签名与验签
在日常的工作中,我们对外提供的接口或调用三方的接口往往有一步生成签名或验签的步骤,这个步骤主要是验证调用方是 不是合法的以及内容是否被修改.比如:对于某些网上公开下载的软件,视频,尤其是镜像文件.如果 ...
- PHP SHA1withRSA加密生成签名及验签
最近公司对接XX第三方支付平台的代付业务,由于对方公司只有JAVA的demo,所以只能根据文档自己整合PHP的签名加密,网上找过几个方法,踩到各种各样的坑,还好最后算是搞定了,话不多说,代码分享出来. ...
- 【源码分析】极验验证官方SDK源码分析和实现思路
前言 2016年就这么来了,新的一年,继续努力~ 最近,除了12306的验证码火起来以后,还有一个在界面上拖拽的验证码,也火了起来,就是这次要说的极验验证,在这个万众创新的时代,工具类产品能做到这样, ...
- Weex Android SDK源码分析
前言 最近开始试水Weex开发,使用这么长一段时间,感觉写Weex还是非常方便的.作为一个Android开发,免不了要追查一下weex的sdk源码.今天,就以Weex SDK for Android为 ...
- java 微信支付吊起 验签等源码
微信支付 自己以前没做过微信支付的时候很是迷茫,现把2020最新附上,直接上代码 吊起支付 public JsonResult wxPay(String ids) throws Exception { ...
- 微信支付V3版本回调+验签流程
本文主要是接前面2篇微信V3支付参数准备和微信V3支付整合进项目中的后续之微信支付后的回调. 一.回调验签流程介绍 二.核心流程操作 本文主要是接前面2篇微信V3支付参数准备和微信V3支付整合进项目中 ...
- MD5数字签名算法:生成签名和验签(附代码)
一.背景 为了增加接口的安全性(防止中间人攻击),现增加签名算法.此算法参考微信支付中的签名算法,由于该签名针对前后端,采用了对称算法,如后续接口供给多家第三方接口使用可采用非对称算法.大致整理文档供 ...
- 【java】微信支付生成签名的过程
关于签名的算法,api提供的原文是: 1.签名算法 签名生成的通用步骤如下: 第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用U ...
最新文章
- 扑克牌排序_JAVA 扑克牌排序打印,并进行洗牌
- 在vue中使用babel-polyfill
- oracle SYS and SYSTEM Schemas
- NFV和VNF的现状如何
- P5704 【深基2.例6】字母转换(python实现)
- mysql 与oracle 批量插入的 sql 总结
- oracle merge
- 【SDOI2008】【P1377】仪仗队
- linxuwindows下JBOSS服务端口号及默认根应用修改
- linux下shell程序(一)
- 最火的开源项目及编程语言
- html辅助方法实现原理,前端每日实战:苦练 CSS 基本功——图解辅助线的原理和画法...
- CSDN出品,必是精品:CSDN浏览器助手!
- YDOOK:STM32: 芯片在线需求选型工具
- Juniper 210 密码清不掉_iPhone 11 每次下载应用都需要输入密码怎么办?
- steam网络相关问题-社区错误代码118/无法自动登陆/短期内来自您网络的失败登录过多/无法连接至steam网络(2021/2/18更新)
- s7edge固件android7.0,欧版S7 edge刷上Android 7.0之后:超级流畅
- 揭穿内存厂家“谎言”,实测内存带宽真实表现
- SRM 405(1-250pt, 1-500pt)
- Terraform 学习总结(6)—— 基于阿里云平台上的 Terraform 实战
热门文章
- 2020-09-22关于dialog 问题
- python打印九九乘法表到文件_99乘法表打印_python怎么打印九九乘法表
- 计算机专业英语形成型考核册,电大资源网《人文英语3》形成性考核册作业题目和答案2018年...
- 中国网:防火墙之父发声
- 华为防火墙通用配置详解
- Windows 上安装 Bugzilla 详解
- ppt中如何合并流程图_PPT中流程图如何分支?
- HMACSHA1 加密算法
- qprocess qt 打开word_Qt QProcess启动和关闭外部程序
- [Camera]摄像头模组硬件