package admin.sunmi;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.json.JSONObject;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.UUID;
public class SunMiHttpClient {
private final Log logger = LogFactory.getLog(SunMiHttpClient.class);
private final String appId;
private final String appKey;
private final String appPrivateKey;
private final String sunmiPublicKey;
public SunMiHttpClient(String appId, String appkey, String appPrivateKey, String sunmiPublicKey) {
this.appId = appId;
this.appKey = appkey;
this.appPrivateKey = appPrivateKey;
this.sunmiPublicKey = sunmiPublicKey;
}
/**
* Http request
* @param url: api url
* @param params: json string parameters
* @param signType: RSA|hmac
* @return response body
* @throws Exception
*/
public String request(String url, String params, String signType) throws Exception {
String respBody = "";
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url);
StringEntity s = new StringEntity(params, "utf-8");
httpPost.setEntity(s);
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonce = UUID.randomUUID().toString();
String sign;
if ("RSA".equals(signType)) {
sign = generateRsa2048Sign(appId, appPrivateKey, timestamp, nonce, params);
} else {
sign = generateHmac256Sign(appId, appKey, timestamp, nonce, params);
}
httpPost.setHeader("Sunmi-Sign", sign);
httpPost.setHeader("Sunmi-Timestamp", timestamp);
httpPost.setHeader("Sunmi-Nonce", nonce);
httpPost.setHeader("Sunmi-Appid", appId);
httpPost.setHeader(HTTP.CONTENT_TYPE, "application/json;charset=utf-8");
CloseableHttpResponse response = httpClient.execute(httpPost);
if (response.getStatusLine().getStatusCode() != 200) {
throw new Exception("Server error");
}
HttpEntity entity = response.getEntity();
if (entity != null) {
respBody = EntityUtils.toString(entity, "UTF-8");
}
EntityUtils.consume(entity);
response.close();
logger.info("Response body: " + respBody);
return respBody;
}
/**
* 生成Hmac-sha256签名
* @param appId: appId
* @param appKey: appKey
* @param timestamp: timestamp
* @param nonce: random string
* @param params: json string parameters
* @return signature
* @throws Exception
*/
public String generateHmac256Sign(String appId, String appKey, String timestamp, String nonce, String params) throws Exception {
String content = params + appId + timestamp + nonce;
Mac hmacSHA256 = Mac.getInstance("HmacSHA256");
SecretKeySpec key = new SecretKeySpec(appKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
hmacSHA256.init(key);
byte[] array = hmacSHA256.doFinal(content.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString();
}
/**
* 生成Rsa 2048签名
* @param appId
* @param privateKey: rsa private key
* @param timestamp
* @param nonce
* @param params
* @return signature
* @throws Exception
*/
public static String generateRsa2048Sign(String appId, String privateKey, String timestamp, String nonce, String params) throws Exception {
String content = params + appId + timestamp + nonce;
byte[] keyBytes = Base64.decodeBase64(privateKey.replaceAll("(\\s)|(--.*--)", ""));
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(priKey);
signature.update(content.getBytes());
return Base64.encodeBase64String(signature.sign());
}
/**
* 用公钥验证签名
* @param appId
* @param publicKey: rsa public key
* @param sign
* @param timestamp
* @param nonce: random string
* @param params
* @return
*/
public static boolean verifyRsa2048Sign(String appId, String publicKey, String sign, String timestamp, String nonce, String params) {
try {
String content = params + appId + timestamp + nonce;
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey.replaceAll("(\\s)|(--.*--)", "")));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(pubKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
return signature.verify(Base64.decodeBase64(sign));
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
近期 对接了商米云端支付 这是我从github上找到的类(但报错) 于是我对其进行了修改 现开源出来。
其中 verifyRsa2048Sign 方法 在支付回调时验签不好使,但经测试 我使用上述代码生成密钥再使用该方法进行验签是没问题的。也不知道是那里的问题 没去研究。采用时自行辨认吧。
侵权请联系我删除。