数据签名
<h3>申请公私钥</h3>
<p>说明:一共两对公私钥,平台公私钥(由平台生成)、企业公私钥(由企业生成)。
用途:企业私钥用于企业发送请求时参数加签,企业公钥用于平台接受企业的请求后的参数验签;平台私钥用于平台回调企业时的参数加密,平台公钥用于企业接受到平台信息后的参数验签。<br />
公私钥生成步骤:<br />
1、通过客服人员给到的账号登录企业端,<a href="https://b.kylin-task.com">https://b.kylin-task.com</a><br />
2、点击企业管理-开放平台,然后点击右上角的新增按钮
<img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=1249a605d22f4f6a008978fc2fbf6b8b&amp;file=file.png" alt="" />
3、可以使用平台提供的密钥生成工具来生成一对公私钥(平台不会保存企业私钥,请企业妥善保管),也可以企业自己生成后上传企业公钥,然后点击保存即可
<img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=472120ba69478778fb1b464023adccc0&amp;file=file.png" alt="" /></p>
<h3>签名</h3>
<p>企业发送请求时使用企业私钥进行加签,平台接收到请求后使用企业上传的企业公钥进行验签。</p>
<ol>
<li>不包括字节类型参数,如文件、字节流,剔除 sign 字段,剔除值为空的参数;</li>
<li>按照第一个字符的键值 ASCII 码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值 ASCII 码递增排序,以此类推;</li>
<li>将排序后的参数与其对应值,组合成 参数=参数值 的格式,并且把这些参数用 & 字符连接起来,此时生成的字符串为待签名字符串。</li>
</ol>
<p>例:bizContent={"outBizNo":"2023100080808","accountType":"ALI_ACCOUNT","name":"张三","accountNumber":"testaccount@alipay.com","phone":"13100000000","idCardNumber":"33010120231001111","transAmount":10.01}&charset=utf-8&companyId=1&signType=RSA2&timestamp=2023-10-01 08:08:08</p>
<h3>验签</h3>
<p>平台回调时使用平台私钥进行加签,企业接收到回调请求后使用平台公钥进行验签。</p>
<p>加签、验签代码示例</p>
<pre><code>import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
public class RSAUtil {
//签名算法名称
private static final String RSA_KEY_ALGORITHM = &quot;RSA&quot;;
//标准签名算法名称
private static final String RSA_SIGNATURE_ALGORITHM = &quot;SHA1withRSA&quot;;
private static final String RSA2_SIGNATURE_ALGORITHM = &quot;SHA256withRSA&quot;;
/**
* RSA签名(私钥)
* @param data 待签名数据
* @param privateKeyStr 私钥
* @param signType RSA或RSA2
* @param charset 编码格式
* @return 签名
* @throws Exception
*/
public static String sign(String data, String privateKeyStr, String signType, String charset) throws Exception {
//创建PKCS8编码密钥规范
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(decode(privateKeyStr));
//返回转换指定算法的KeyFactory对象
KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
//根据PKCS8编码密钥规范产生私钥对象
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
//标准签名算法名称(RSA还是RSA2)
String algorithm = RSA_KEY_ALGORITHM.equals(signType) ? RSA_SIGNATURE_ALGORITHM : RSA2_SIGNATURE_ALGORITHM;
//用指定算法产生签名对象Signature
Signature signature = Signature.getInstance(algorithm);
//用私钥初始化签名对象Signature
signature.initSign(privateKey);
//将待签名的数据传送给签名对象(须在初始化之后)
signature.update(data.getBytes(charset));
//返回签名结果字节数组
byte[] sign = signature.sign();
//返回Base64编码后的字符串
return encode(sign);
}
/**
* RSA校验数字签名(公钥)
* @param data 待校验数据
* @param sign 数字签名
* @param publicKeyStr 公钥
* @param signType RSA或RSA2
* @param charset 编码格式
* @return boolean 校验成功返回true,失败返回false
*/
public static boolean verify(String data, String sign, String publicKeyStr, String signType, String charset) throws Exception {
//返回转换指定算法的KeyFactory对象
KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
//创建X509编码密钥规范
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(decode(publicKeyStr));
//根据X509编码密钥规范产生公钥对象
PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
//标准签名算法名称(RSA还是RSA2)
String algorithm = RSA_KEY_ALGORITHM.equals(signType) ? RSA_SIGNATURE_ALGORITHM : RSA2_SIGNATURE_ALGORITHM;
//用指定算法产生签名对象Signature
Signature signature = Signature.getInstance(algorithm);
//用公钥初始化签名对象,用于验证签名
signature.initVerify(publicKey);
//更新签名内容
signature.update(data.getBytes(charset));
//得到验证结果
return signature.verify(decode(sign));
}
/**
* 编码
* @param bytes byte字节数组
* @return encode Base64编码
*/
private static String encode(byte[] bytes){
return Base64.getEncoder().encodeToString(bytes);
}
/**
* 解码
* @param str 编码后的byte
* @return decode Base64解码
*/
private static byte[] decode(String str){
return Base64.getDecoder().decode(str);
}
/***
* 获取排序后的待签名字符串
* @param sortedParams
* @return
*/
public static String getSignContent(Map&lt;String, String&gt; sortedParams) {
StringBuilder content = new StringBuilder();
List&lt;String&gt; keys = new ArrayList&lt;String&gt;(sortedParams.keySet());
Collections.sort(keys);
int index = 0;
for (int i = 0; i &lt; keys.size(); i++) {
String key = keys.get(i);
String value = sortedParams.get(key);
if (areNotEmpty(key, value)) {
content.append(index == 0 ? &quot;&quot; : &quot;&amp;&quot;).append(key).append(&quot;=&quot;).append(value);
index++;
}
}
return content.toString();
}
/**
* 检查指定的字符串列表是否不为空。
*/
public static boolean areNotEmpty(String... values) {
boolean result = true;
if (values == null || values.length == 0) {
result = false;
} else {
for (String value : values) {
result &amp;= !isEmpty(value);
}
}
return result;
}
/**
* 检查指定的字符串是否为空。
* &lt;ul&gt;
* &lt;li&gt;SysUtils.isEmpty(null) = true&lt;/li&gt;
* &lt;li&gt;SysUtils.isEmpty(&quot;&quot;) = true&lt;/li&gt;
* &lt;li&gt;SysUtils.isEmpty(&quot; &quot;) = true&lt;/li&gt;
* &lt;li&gt;SysUtils.isEmpty(&quot;abc&quot;) = false&lt;/li&gt;
* &lt;/ul&gt;
*
* @param value 待检查的字符串
* @return true/false
*/
public static boolean isEmpty(String value) {
int strLen;
if (value == null || (strLen = value.length()) == 0) {
return true;
}
for (int i = 0; i &lt; strLen; i++) {
if ((Character.isWhitespace(value.charAt(i)) == false)) {
return false;
}
}
return true;
}
}</code></pre>