Sign执行流程
您的系统端
当您请求EPAY服务端前,封装请求参数+API_KEY,根据约定的算法,生成一个Sign签名,生成签名之后,和参数一起放进请求体里,传给EPAY服务端。
EPAY服务端
EPAY服务端将接收到的请求数据,生成相应的Sign签名,与您传来的Sign签名进行一个比对,如果两个Sign签名一致。通过验证允许访问EPAY服务端数据,否则禁止访问。
[warning] 注意:
您需要保存好API_KEY,如API_KEY丢失或不安全,请重新申请生成新的API_KEY
Sign生成算法
第一步,将请求参数封装成json对象类型,去掉空值和null值。
第二步,将json对象根据参数名(区分大小写)按ASCII码从小到大排序转换成json格式的字符串。
第三步,将json格式的字符串转换成queryString格式的字符串,即key1=value1&key2=value2&key3={subKey1=sValue1,subKey2=sValue2},遍历json对象,将key-value数据转换成queryString格式的字符串。
第四步,将queryString格式的字符串拼接key={API_KEY},例如:API_KEY=3A4BC4A4000CF1B5FFA9E351E6C15rfs3,则字符串变为 key1=value1&key2=value2&key3={subKey1=sValue1,subKey2=sValue2}&key=3A4BC4A4000CF1B5FFA9E351E6C15rfs3
第五步,将拼接的字符串进行sha256运算,然后转换为大写,即得到sign值,用于api请求。
Sign Demo
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
public class SignUtil {
private static final Logger logger = LoggerFactory.getLogger(SignUtil.class);
/**
* 获取签名
* @param parameters
* @param key
* @return
*/
public static String createSign(JSONObject parameters, String key){
replaceEmptyWithNull(parameters);
//排序输出字符串,过滤null
String param = JSONObject.toJSONString(parameters, SerializerFeature.MapSortField);
SortedMap<String, Object> sortedMap = JSON.parseObject(JSONObject.toJSONString(parameters), new TypeReference<TreeMap>() {});
//转成queryString类型
String qString = queryString(sortedMap);
String sbkey = qString + "&key=" + key;
//MD5加密,字符集UTF-8,结果转换为大写字符
String sign = SHAUtils.sha256(sbkey).toUpperCase();
logger.info("加密结束(sha256)sign:"+sign);
return sign;
}
/**
* 空字符串转成null值
* @param parameters
*/
private static void replaceEmptyWithNull(JSONObject parameters) {
parameters.replaceAll((k,v) -> {
if (v.equals("")) {
return null;
}
if (v instanceof JSONObject) {
replaceEmptyWithNull((JSONObject)v);
}
return v;
});
}
/**
* 转成queryString类型
* @param jObj
* @return
*/
private static String queryString(SortedMap<String, Object> jObj) {
String qString = "";
if (null != jObj) {
for (String key : jObj.keySet()) {
if (jObj.get(key) instanceof JSONObject) {
String jsonStr = queryString(JSON.parseObject(JSONObject.toJSONString(jObj.get(key)), new TypeReference<TreeMap>() {}));
qString = qString + key + "=" + "{"+ jsonStr + "}" + "&";
}else{
qString = qString + key + "=" + jObj.get(key) + "&";
}
}
qString = qString.substring(0, qString.length() - 1);
}
return qString;
}
public static void main(String[] args) {
String s="{\"epayAccount\":\"api@epay.com\",\"category\":\"BANK\",\"notifyUrl\":\"\",\"merchantOrderNo\":\"202103220010\",\"amount\":\"\",\"receiveAmount\":\"3000\",\"settlementCurrency\":\"USD\",\"receiveCurrency\":\"RUB\",\"version\":\"V1.0.0\",\"senderInfo\":{\"surName\":\"tom\",\"givName\":\"cat\",\"gender\":\"M\",\"email\":\"tomcat@epay.com\",\"country\":\"CN\",\"address\":\"address\",\"city\":\"city\",\"area\":\"86\",\"phone\":\"11111111111\",\"nationality\":\"AU\",\"idType\":\"1\",\"idNumber\":\"111111\",\"issueDate\":\"1980-01-01\",\"expireDate\":\"2050-01-01\",\"birthday\":\"1970-01-01\",\"occupation\":\"1\",\"sourceOfFund\":\"1\",\"beneficiaryRelationShip\":\"1\",\"purposeOfRemittance\":\"1\"},\"receiverInfo\":{\"surName\":\"ll\",\"givName\":\"test\",\"otherName\":\"其他语言\",\"address\":\"address\",\"area\":\"86\",\"phone\":\"11111111111\",\"country\":\"RU\",\"nationality\":\"RU\",\"idType\":\"1\",\"idNumber\":\"222222\",\"locationId\":\"RURLR00001-1\",\"bankId\":\"RURLR01299-2\",\"bankName\":\"AMP Bank Limited\",\"bankBranchName\":\"AMP Bank Limited\",\"bankBranchCode\":\"\",\"accountNo\":\"42222222225222222222\"}}";
System.out.println(isJSONValid(createSign(JSONObject.parseObject(s),"aa")));
}
}
/*
加密算法库
*/
public class SHAUtils {
/**
* SHA256加密
* @param source 待加密字符
* @return String 密文
*/
public static String sha256(String source) {
MessageDigest md;
String target;
byte[] bt = source.getBytes();
try {
md = MessageDigest.getInstance("SHA-256");
md.update(bt);
target = bytes2Hex(md.digest());
} catch (NoSuchAlgorithmException e) {
logger.error("SHA256加密异常");
throw new EpayException(ErrorCode.ERROR);
}
return target;
}
/**
* 把字节数据转16进制
* @param bts 字节数组
* @return String
*/
public static String bytes2Hex(byte[] bts) {
StringBuilder target = new StringBuilder();
String tmp;
for (byte bt : bts) {
tmp = (Integer.toHexString(bt & 0xFF));
if (tmp.length() == 1) {
target.append("0");
}
target.append(tmp);
}
return target.toString();
}
}
java
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
public class SignUtil {
private static final Logger logger = LoggerFactory.getLogger(SignUtil.class);
/**
* 获取签名
* @param parameters
* @param key
* @return
*/
public static String createSign(JSONObject parameters, String key){
replaceEmptyWithNull(parameters);
//排序输出字符串,过滤null
String param = JSONObject.toJSONString(parameters, SerializerFeature.MapSortField);
SortedMap<String, Object> sortedMap = JSON.parseObject(JSONObject.toJSONString(parameters), new TypeReference<TreeMap>() {});
//转成queryString类型
String qString = queryString(sortedMap);
String sbkey = qString + "&key=" + key;
//MD5加密,字符集UTF-8,结果转换为大写字符
String sign = SHAUtils.sha256(sbkey).toUpperCase();
logger.info("加密结束(sha256)sign:"+sign);
return sign;
}
/**
* 空字符串转成null值
* @param parameters
*/
private static void replaceEmptyWithNull(JSONObject parameters) {
parameters.replaceAll((k,v) -> {
if (v.equals("")) {
return null;
}
if (v instanceof JSONObject) {
replaceEmptyWithNull((JSONObject)v);
}
return v;
});
}
/**
* 转成queryString类型
* @param jObj
* @return
*/
private static String queryString(SortedMap<String, Object> jObj) {
String qString = "";
if (null != jObj) {
for (String key : jObj.keySet()) {
if (jObj.get(key) instanceof JSONObject) {
String jsonStr = queryString(JSON.parseObject(JSONObject.toJSONString(jObj.get(key)), new TypeReference<TreeMap>() {}));
qString = qString + key + "=" + "{"+ jsonStr + "}" + "&";
}else{
qString = qString + key + "=" + jObj.get(key) + "&";
}
}
qString = qString.substring(0, qString.length() - 1);
}
return qString;
}
public static void main(String[] args) {
String s="{\"epayAccount\":\"api@epay.com\",\"category\":\"BANK\",\"notifyUrl\":\"\",\"merchantOrderNo\":\"202103220010\",\"amount\":\"\",\"receiveAmount\":\"3000\",\"settlementCurrency\":\"USD\",\"receiveCurrency\":\"RUB\",\"version\":\"V1.0.0\",\"senderInfo\":{\"surName\":\"tom\",\"givName\":\"cat\",\"gender\":\"M\",\"email\":\"tomcat@epay.com\",\"country\":\"CN\",\"address\":\"address\",\"city\":\"city\",\"area\":\"86\",\"phone\":\"11111111111\",\"nationality\":\"AU\",\"idType\":\"1\",\"idNumber\":\"111111\",\"issueDate\":\"1980-01-01\",\"expireDate\":\"2050-01-01\",\"birthday\":\"1970-01-01\",\"occupation\":\"1\",\"sourceOfFund\":\"1\",\"beneficiaryRelationShip\":\"1\",\"purposeOfRemittance\":\"1\"},\"receiverInfo\":{\"surName\":\"ll\",\"givName\":\"test\",\"otherName\":\"其他语言\",\"address\":\"address\",\"area\":\"86\",\"phone\":\"11111111111\",\"country\":\"RU\",\"nationality\":\"RU\",\"idType\":\"1\",\"idNumber\":\"222222\",\"locationId\":\"RURLR00001-1\",\"bankId\":\"RURLR01299-2\",\"bankName\":\"AMP Bank Limited\",\"bankBranchName\":\"AMP Bank Limited\",\"bankBranchCode\":\"\",\"accountNo\":\"42222222225222222222\"}}";
System.out.println(isJSONValid(createSign(JSONObject.parseObject(s),"aa")));
}
}
/*
加密算法库
*/
public class SHAUtils {
/**
* SHA256加密
* @param source 待加密字符
* @return String 密文
*/
public static String sha256(String source) {
MessageDigest md;
String target;
byte[] bt = source.getBytes();
try {
md = MessageDigest.getInstance("SHA-256");
md.update(bt);
target = bytes2Hex(md.digest());
} catch (NoSuchAlgorithmException e) {
logger.error("SHA256加密异常");
throw new EpayException(ErrorCode.ERROR);
}
return target;
}
/**
* 把字节数据转16进制
* @param bts 字节数组
* @return String
*/
public static String bytes2Hex(byte[] bts) {
StringBuilder target = new StringBuilder();
String tmp;
for (byte bt : bts) {
tmp = (Integer.toHexString(bt & 0xFF));
if (tmp.length() == 1) {
target.append("0");
}
target.append(tmp);
}
return target.toString();
}
}
php
<?php
function SignatureAndBuild($params,$appKey){
// 排序&组装
$sign_str = SortParams($params);
echo $sign_str;
// 拼接key
$sign_str = $sign_str."&key=".$appKey;
// 加密&转大写
$sign = strtoupper(hash("sha256",$sign_str));
$res = [
"param"=>$params,
"sign"=>$sign
];
return $res;
}
function SortParams($params){
// 排序
ksort($params);
$sign_arr = [];
// 参数值为空不参与签名,值为数组需要递归
foreach($params as $key=>$value){
if(empty($value)){
continue;
}
if (is_array($value)){
$value = SortParams($value);
$value = "{".$value."}";
}
$sign_arr[] = "$key=$value";
}
return join('&', $sign_arr);
}
$strJson = '{"epayAccount":"jing32@qq.com","transactionType":"C2C","category":"BANK","notifyUrl":"http://192.168.2.6:8380/openapi/apiForward/callBack","merchantOrderNo":"202201019002","amount":"100","receiveAmount":"","settlementCurrency":"USD","receiveCurrency":"CNY","version":"V2.0.0","senderInfo":{"surName":"Joe","givName":"Chang","idNumber":"A199267867","idType":"1","birthday":"1986-09-11","country":"US","address":"Santa Clara, CA 3050 Bowers Avenue","purposeOfRemittance":"20","city":"Santa Clara","zipCode":"58039","accountNo":"13721473389"},"receiverInfo":{"surName":"lu","givName":"hui","nationality":"CN","country":"CN","area":"","accountNo":"13721473389","idNumber":"1234567890"}}';
$api_key='37a96739aece637fb567fd40e6526116'
$jObj = json_decode($strJson,true);
$obj = SignatureAndBuild($jObj,$api_key);
echo json_encode($obj)
?>
示例
- 例1:排序后JSON对象
{currency=USD, epayAccount=api@epay.com, version=v1.0.0}
加密前字符串
currency=USD&epayAccount=api@epay.com&version=v1.0.0&key=3A4BC4A4000CF1B5FFA9E351E6C1539E
结果(sha256)
4B0FF54AEF7F9395784F8BA2A35A30C4A74414EDA026E47D862D82ED7306797F
- 例2:排序后JSON对象
{category=BANK, epayAccount=api@epay.com, merchantOrderNo=202103220010, receiveAmount=3000, receiveCurrency=RUB, receiverInfo={accountNo=42222222225222222222, address=address, area=86, bankBranchName=AMP Bank Limited, bankId=RURLR01299-2, bankName=AMP Bank Limited, country=RU, givName=test, idNumber=222222, idType=1, locationId=RURLR00001-1, nationality=RU, otherName=其他语言, phone=11111111111, surName=ll}, senderInfo={address=address, area=86, beneficiaryRelationShip=1, birthday=1970-01-01, city=city, country=CN, email=tomcat@epay.com, expireDate=2050-01-01, gender=M, givName=cat, idNumber=111111, idType=1, issueDate=1980-01-01, nationality=AU, occupation=1, phone=11111111111, purposeOfRemittance=1, sourceOfFund=1, surName=tom}, settlementCurrency=USD, version=V1.0.0}
加密前字符串
category=BANK&epayAccount=api@epay.com&merchantOrderNo=202103220010&receiveAmount=3000&receiveCurrency=RUB&receiverInfo={accountNo=42222222225222222222&address=address&area=86&bankBranchName=AMP Bank Limited&bankId=RURLR01299-2&bankName=AMP Bank Limited&country=RU&givName=test&idNumber=222222&idType=1&locationId=RURLR00001-1&nationality=RU&otherName=其他语言&phone=11111111111&surName=ll}&senderInfo={address=address&area=86&beneficiaryRelationShip=1&birthday=1970-01-01&city=city&country=CN&email=tomcat@epay.com&expireDate=2050-01-01&gender=M&givName=cat&idNumber=111111&idType=1&issueDate=1980-01-01&nationality=AU&occupation=1&phone=11111111111&purposeOfRemittance=1&sourceOfFund=1&surName=tom}&settlementCurrency=USD&version=V1.0.0&key=aa
结果(sha256)
7FD906B556363B145169A2EE511CCB0E897A28F85323F8BF18B517C5E96D6A26
Sign Run
//pls input the params to sign
let jStr = '{"epayAccount":"jing32@qq.com","transactionType":"C2C","category":"BANK","notifyUrl":"http://192.168.2.6:8380/openapi/apiForward/callBack","merchantOrderNo":"202201019002","amount":"100","receiveAmount":"","settlementCurrency":"USD","receiveCurrency":"CNY","version":"V2.0.0","senderInfo":{"surName":"Joe","givName":"Chang","idNumber":"A199267867","idType":"1","birthday":"1986-09-11","country":"US","address":"Santa Clara, CA 3050 Bowers Avenue","purposeOfRemittance":"20","city":"Santa Clara","zipCode":"58039","accountNo":"13721473389"},"receiverInfo":{"surName":"lu","givName":"hui","nationality":"CN","country":"CN","area":"","accountNo":"13721473389","idNumber":"1234567890"}}'
//test2020@epay.com
//let apiKey = '2d00b386231806ec7e18e2d96dc043aa';
//test2020@epay.com
//let apiKey = '757ec68185c1bc8eff36522398d980d7';
//jing32@qq.com
let apiKey = '37a96739aece637fb567fd40e6526116';
sign(jStr,apiKey);
//去掉空值和null值
function deleteEmptyProperty(object){
for (var i in object) {
var value = object[i];
if (typeof value === 'object') {
if (Array.isArray(value)) {
if (value.length == 0) {
delete object[i];
continue;
}
}
this.deleteEmptyProperty(value);
if (this.isEmpty(value)) {
delete object[i];
}
} else {
if (value === '' || value === null || value === undefined) {
delete object[i];
} else {
}
}
}
}
function isEmpty(object) {
for (var name in object) {
return false;
}
return true;
}
//排序
function json_sort(data){
data = data || '{}';
let arr = [];
for (var key in data) {
arr.push(key)
}
arr.sort();
let str = '{';
for (var i in arr) {
if(isJson(data[arr[i]])){
let eJson = json_sort(data[arr[i]]);
let eJsonStr = JSON.stringify(eJson);
// console.log('isJsonStr: ' + eJsonStr);
data[arr[i]] = eJsonStr;
str += '"' + arr[i] + '":' + data[arr[i]] + ','
}else{
str += '"' + arr[i] + '":"' + data[arr[i]] + '",'
}
}
str = str.substr(0, str.length - 1)
str += '}'
str = JSON.parse(str);
return str;
}
//转成queryString
function queryString(data){
data = data || '{}';
let str = '';
for (var key in data) {
if(isJson(data[key])) {
let jsonStr = queryString(data[key]);
str += key + '=' + '{'+ jsonStr + '}' + '&';
}else{
str += key + '=' + data[key] + '&';
}
}
str = str.substr(0, str.length - 1)
return str;
}
//是否json字符串
// function isJsonString(str) {
// if (typeof str == 'string') {
// try {
// var obj=JSON.parse(str);
// if(typeof obj == 'object' && obj ){
// return true;
// }else{
// return false;
// }
// } catch(e) {
// console.log('error:'+str+'!!!'+e);
// return false;
// }
// }
// console.log('It is not a string!')
// }
//是否json对象
function isJson(obj){
var isjson = typeof(obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == "[object object]" && !obj.length;
return isjson;
}
//sha256
function SHA256(s){
var chrsz = 8;
var hexcase = 0;
function safe_add (x, y) {
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
function S (X, n) { return ( X >>> n ) | (X << (32 - n)); }
function R (X, n) { return ( X >>> n ); }
function Ch(x, y, z) { return ((x & y) ^ ((~x) & z)); }
function Maj(x, y, z) { return ((x & y) ^ (x & z) ^ (y & z)); }
function Sigma0256(x) { return (S(x, 2) ^ S(x, 13) ^ S(x, 22)); }
function Sigma1256(x) { return (S(x, 6) ^ S(x, 11) ^ S(x, 25)); }
function Gamma0256(x) { return (S(x, 7) ^ S(x, 18) ^ R(x, 3)); }
function Gamma1256(x) { return (S(x, 17) ^ S(x, 19) ^ R(x, 10)); }
function core_sha256 (m, l) {
var K = new Array(0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786, 0xFC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x6CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2);
var HASH = new Array(0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19);
var W = new Array(64);
var a, b, c, d, e, f, g, h, i, j;
var T1, T2;
m[l >> 5] |= 0x80 << (24 - l % 32);
m[((l + 64 >> 9) << 4) + 15] = l;
for ( var i = 0; i<m.length; i+=16)
{
a = HASH[0];
b = HASH[1];
c = HASH[2];
d = HASH[3];
e = HASH[4];
f = HASH[5];
g = HASH[6];
h = HASH[7];
for ( var j = 0; j<64; j++) {
if (j < 16) W[j] = m[j + i];
else W[j] = safe_add(safe_add(safe_add(Gamma1256(W[j - 2]), W[j - 7]), Gamma0256(W[j - 15])), W[j - 16]);
T1 = safe_add(safe_add(safe_add(safe_add(h, Sigma1256(e)), Ch(e, f, g)), K[j]), W[j]);
T2 = safe_add(Sigma0256(a), Maj(a, b, c));
h = g;
g = f;
f = e;
e = safe_add(d, T1);
d = c;
c = b;
b = a;
a = safe_add(T1, T2);
}
HASH[0] = safe_add(a, HASH[0]);
HASH[1] = safe_add(b, HASH[1]);
HASH[2] = safe_add(c, HASH[2]);
HASH[3] = safe_add(d, HASH[3]);
HASH[4] = safe_add(e, HASH[4]);
HASH[5] = safe_add(f, HASH[5]);
HASH[6] = safe_add(g, HASH[6]);
HASH[7] = safe_add(h, HASH[7]);
}
return HASH;
}
function str2binb (str) {
var bin = Array();
var mask = (1 << chrsz) - 1;
for(var i = 0; i < str.length * chrsz; i += chrsz) {
bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i%32);
}
return bin;
}
function Utf8Encode(string) {
string = string.replace(/\r\n/g,"\n");
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
}
else if((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
}
function binb2hex (binarray) {
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var str = "";
for(var i = 0; i < binarray.length * 4; i++) {
str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
}
return str;
}
s = Utf8Encode(s);
return binb2hex(core_sha256(str2binb(s), s.length * chrsz));
}
function sign(jStr,apiKey){
//let obj = eval('(' + jStr + ')');
console.log('排序前JSON字符串: ' + jStr);
let obj = JSON.parse(jStr);
deleteEmptyProperty(obj);
let sobj = json_sort(obj);
let jsonStr = JSON.stringify(sobj);
console.log('排序后JSON字符串: ' + jsonStr);
//jsonStr = jsonStr.substr(1);
//jsonStr = jsonStr.substring(0,jsonStr.length-1);
//jsonStr = jsonStr.replaceAll("\":", "\"=").replaceAll("\"", "").replaceAll(",", "&");
jsonStr = queryString(sobj) + '&key=' + apiKey;
console.log('加密前字符串: ' + jsonStr);
let sha256Str = SHA256(jsonStr).toUpperCase();
console.log('签名结果: ' + sha256Str);
}