【JAVA】签名DEMO
<h2>签名DEMO</h2>
<h3>1. 签名规则</h3>
<ol>
<li>将 <strong>加密后</strong> 的请求参数按照 a-z 排序并用【&】拼接,如【a=1&b=2&c=3】</li>
<li>在参数拼接后,拼接 <strong>商户KEY</strong> ,得到【a=1&b=2&c=3&key="keyStr"】</li>
<li>将拼接完成的字符串通过 <code>MD5</code> 加密,结果转为大写,得到【sign】值</li>
<li>将【sign】和【timestamp】和【merchantNo】添加到请求的JSON数据里</li>
<li>验签通过前面的签名方式,获取到【sign】再对比接口参数里的【sign】对比是否一致</li>
</ol>
<h3>签名工具类 DEMO</h3>
<pre><code class="language-java">
import com.alibaba.fastjson2.JSONObject;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SignUtils {
//忽略 加密、签名 字段
public final static List&lt;String&gt; neglectParams = List.of(&quot;sign&quot;, &quot;timestamp&quot;, &quot;merchantNo&quot;);
/**
* 参数 验签、解密
*
* @param param 解密前参数JSON
* @param key 商户KEY
*/
public static void verifyParam(JSONObject param, String key) {
String timestamp = param.getString(&quot;timestamp&quot;);
//验签
boolean checkSign = SignUtils.verifySignature(param, key);
if (!checkSign) {
throw new RuntimeException(&quot;验签失败!&quot;);
}
//获取AES-key
String AesKey = AESUtils.sha256(timestamp);
//参数解密
AESUtils.decrypt(param, AesKey);
}
/**
* 参数 加密、签名
*
* @param param 加密前参数JSON
* @param key 商户KEY
*/
public static void encryptAndSign(JSONObject param, String key) {
//获取AesKey
Long timestamp = System.currentTimeMillis();
String AesKey = AESUtils.sha256(String.valueOf(timestamp)); // 生成随机16位,密钥
//数据加密
AESUtils.encrypt(param, AesKey);
//签名
String sign = SignUtils.generateSignature(param, key);
param.put(&quot;sign&quot;, sign);
param.put(&quot;timestamp&quot;, timestamp);
}
/**
* 生成签名
*
* @param params 请求参数,包含所有需要签名的字段
* @param secretKey 商户密钥
* @return 生成的签名
*/
public static String generateSignature(JSONObject params, String secretKey) {
// 步骤1:去除空值的参数,排序并拼接成字符串
String stringA = createQueryString(params);
// 步骤2:拼接密钥
String stringSignTemp = stringA + &quot;&amp;key=&quot; + secretKey;
// 步骤3:MD5运算,结果转换为大写
return md5(stringSignTemp).toUpperCase();
}
/**
* 验证签名
*
* @param params 请求参数,包含所有需要签名的字段
* @param secretKey 商户密钥
* @return 验签结果,true表示签名合法,false表示签名不合法
*/
public static boolean verifySignature(JSONObject params, String secretKey) {
String sign = params.getString(&quot;sign&quot;);
// 重新生成签名,并与传入的sign进行对比
String generatedSign = generateSignature(params, secretKey);
return generatedSign.equals(sign);
}
/**
* 根据参数构建URL编码的查询字符串,按照字典序排序
*
* @param params 请求参数
* @return 拼接后的查询字符串
*/
private static String createQueryString(JSONObject params) {
// 1. 排序,按字典序排列参数
List&lt;String&gt; keys = new ArrayList&lt;&gt;(params.keySet());
Collections.sort(keys);
StringBuilder queryString = new StringBuilder();
// 2. 遍历所有参数,拼接 key=value
for (String key : keys) {
if (neglectParams.contains(key)) {
continue;
}
String value = params.getString(key);
if (value != null &amp;&amp; !value.trim().isEmpty()) { // 排除值为空的参数
if (!queryString.isEmpty()) {
queryString.append(&quot;&amp;&quot;);
}
queryString.append(key).append(&quot;=&quot;).append(value);
}
}
return queryString.toString();
}
/**
* MD5加密
*
* @param input 输入字符串
* @return MD5加密后的结果
*/
private static String md5(String input) {
try {
MessageDigest md = MessageDigest.getInstance(&quot;MD5&quot;);
byte[] hash = md.digest(input.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xFF &amp; b);
if (hex.length() == 1) {
hexString.append(&#039;0&#039;);
}
hexString.append(hex);
}
return hexString.toString();
} catch (Exception e) {
throw new RuntimeException(&quot;MD5 encryption error&quot;, e);
}
}
public static void main(String[] args) {
String str = &quot;{\n&quot; +
&quot; \&quot;merchantNo\&quot;: 17178557819911016395,\n&quot; +
&quot; \&quot;merchantOrderNo\&quot;: \&quot;NO1234567890\&quot;,\n&quot; +
&quot; \&quot;payChannelId\&quot;: 2,\n&quot; +
&quot; \&quot;channelCode\&quot;: \&quot;LeShua\&quot;,\n&quot; +
&quot; \&quot;amount\&quot;: 1,\n&quot; +
&quot; \&quot;goodsDetail\&quot;: \&quot;{\\\&quot;costPrice\\\&quot;:1,\\\&quot;receiptId\\\&quot;:\\\&quot;NO123\\\&quot;,\\\&quot;details\\\&quot;:[{\\\&quot;goodsId\\\&quot;:\\\&quot;1\\\&quot;,\\\&quot;wxGoodsId\\\&quot;:\\\&quot;2\\\&quot;,\\\&quot;goodsName\\\&quot;:\\\&quot;测试商品\\\&quot;,\\\&quot;quantity\\\&quot;:1,\\\&quot;price\\\&quot;:1}]}\&quot;,\n&quot; +
&quot; \&quot;wechatInfo\&quot;: \&quot;{\\\&quot;openId\\\&quot;:\\\&quot;oxX4F7Intpo6xi9Zc64VXrKcJW34\\\&quot;,\\\&quot;appid\\\&quot;:\\\&quot;wx55df9726a583666a\\\&quot;}\&quot;,\n&quot; +
&quot; \&quot;notifyUrl\&quot;: \&quot;https://abc/abc\&quot;,\n&quot; +
&quot; \&quot;clientIp\&quot;: \&quot;127.0.0.1\&quot;,\n&quot; +
&quot; \&quot;attach\&quot;:\&quot;附加信息\&quot;,\n&quot; +
&quot; \&quot;description\&quot;:\&quot;商品A\&quot;,\n&quot; +
&quot; \&quot;supportFapiao\&quot;:\&quot;false\&quot;,\n&quot; +
&quot; \&quot;sceneInfo\&quot;:\&quot;{\\\&quot;id\\\&quot;:\\\&quot;123\\\&quot;,\\\&quot;name\\\&quot;:\\\&quot;门店\\\&quot;,\\\&quot;areaCode\\\&quot;:\\\&quot;101010\\\&quot;,\\\&quot;address\\\&quot;:\\\&quot;详细地址\\\&quot;}\&quot;&quot; +
&quot;}&quot;;
JSONObject param = JSONObject.parseObject(str);
encryptAndSign(param, &quot;6E198B98647DB12C5C0A1825E8638354&quot;);
System.out.println(param);
}
}
</code></pre>