签名
为了保证接口调用的安全性及防止数据被篡改,使用接口参数签名的方式生成签名信息,随请求携带,发送给接收方。
验签
接收方为了验证数据的完整性和正确性,利用与发送方同样的签名方式生成签名信息,然后与请求中携带的签名信息比对,如果一致则请求合法,否则请求不合法。
1.签名步骤
应用过程中, 接口调用流程如下:
2.签名计算过程
为了验证用户的请求合法性,使用 RSA 验签的方式对用户的请求参数 sign 进行验证
签名 sign 的生成方法如下:
第 1 步:将请求 query 参数中的所有不包含 sign 参数的数据,按照字典顺序对参数进行排序,生成 url 格式的字符串作为待签名数据。
示例 1 (无 username 参数):
apiCode=0001&entCode=1&nonce=999×tamp=1565244098737
示例 2 (有 username 参数):
apiCode=0001&entCode=1&nonce=999×tamp=1565244098737&userName=xiao@qq.com
第 2 步:对上述字符串做 md5WithRsa 算法进行签名,得到 sign。
第 3 步:最后把 sign 放入 url 请求参数中即可
1.在发送HTTP请求之前,由于签名sign中有特殊字符,需要对其做URLencode编码处理,否则验签失败
2.RestTemplate发送http请求会自动解码,建议使用okhttp、httpclient组件
3.Postman调用(员工任职数据接口)示例
curl --location --request POST 'https://api.mokahr.com/api-platform/hcm/oapi/v1/batch/data?apiCode=接口编码&entCode=租户编码&nonce=k2ajsqaa&timestamp=1660187065416&userName=moxiaoka@moka.com&sign=URLencode()的签名串' \
--header 'Authorization: Basic base64(apiKey)' \
--header 'Content-Type: application/json' \
--data-raw '{
"uuidList": [],
"employeeNoList": [],
"telephoneList": [],
"officeEmailList": [],
"startDate": "",
"endDate": "",
"pageSize": 200,
"pageNum": 1
}'
2
3
4
5
6
7
8
9
10
11
12
13
4.签名算法 Java 版
第一步:创建 Base64 编码工具类
/**
* Base64 编码工具类
* @author Moka
* @date 2022/5/10
* @apiNote
*/
public class Base64 {
private static char[] Base64Code = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', '+', '/' };
private static byte[] Base64Decode = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, 62, -1, 63, -1, 63, 52, 53,
54, 55, 56, 57, 58, 59, 60, 61, -1, -1,
-1, 0, -1, -1, -1, 0, 1, 2, 3, 4,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, -1, -1, -1, -1, -1, -1, 26, 27, 28,
29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, -1, -1, -1, -1, -1 };
public static String encode(byte[] b)
{
int code = 0;
if (b == null)
return null;
StringBuffer sb = new StringBuffer((b.length - 1) / 3 << 6);
for (int i = 0; i < b.length; i++)
{
code |= b[i] << 16 - i % 3 * 8 & 255 << 16 - i % 3 * 8;
if ((i % 3 != 2) && (i != b.length - 1))
continue;
sb.append(Base64Code[((code & 0xFC0000) >>> 18)]);
sb.append(Base64Code[((code & 0x3F000) >>> 12)]);
sb.append(Base64Code[((code & 0xFC0) >>> 6)]);
sb.append(Base64Code[(code & 0x3F)]);
code = 0;
}
if (b.length % 3 > 0)
sb.setCharAt(sb.length() - 1, '=');
if (b.length % 3 == 1)
sb.setCharAt(sb.length() - 2, '=');
return sb.toString();
}
public static byte[] decode(String code)
{
if (code == null)
return null;
int len = code.length();
if (len % 4 != 0)
throw new IllegalArgumentException("Base64 string length must be 4*n");
if (code.length() == 0)
return new byte[0];
int pad = 0;
if (code.charAt(len - 1) == '=')
pad++;
if (code.charAt(len - 2) == '=')
pad++;
int retLen = len / 4 * 3 - pad;
byte[] ret = new byte[retLen];
for (int i = 0; i < len; i += 4)
{
int j = i / 4 * 3;
char ch1 = code.charAt(i);
char ch2 = code.charAt(i + 1);
char ch3 = code.charAt(i + 2);
char ch4 = code.charAt(i + 3);
int tmp = Base64Decode[ch1] << 18 | Base64Decode[ch2] << 12 | Base64Decode[ch3] << 6 | Base64Decode[ch4];
ret[j] = (byte)((tmp & 0xFF0000) >> 16);
if (i < len - 4)
{
ret[(j + 1)] = (byte)((tmp & 0xFF00) >> 8);
ret[(j + 2)] = (byte)(tmp & 0xFF);
}
else {
if (j + 1 < retLen)
ret[(j + 1)] = (byte)((tmp & 0xFF00) >> 8);
if (j + 2 < retLen)
ret[(j + 2)] = (byte)(tmp & 0xFF);
}
}
return ret;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
第二步:创建 RAS 加解密工具类
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;
/**
* RSA 加解密工具类
* @author Moka
* @date 2022/5/10
* @apiNote
*/
public class RSAUtils {
/**
* 加密算法 RSA
*/
public static final String KEY_ALGORITHM = "RSA";
/**
* 签名算法
*/
public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
/**
* 获取公钥的 key
*/
private static final String PUBLIC_KEY = "RSAPublicKey";
/**
* 获取私钥的 key
*/
private static final String PRIVATE_KEY = "RSAPrivateKey";
/**
* RSA 最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/**
* RSA 最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 128;
/**
* <p>
* 用私钥对信息生成数字签名
* </p>
*
* @param data 已加密数据
* @param privateKey 私钥(BASE64 编码)
*
* @return
* @throws Exception
*/
public static String sign(byte[] data, String privateKey) throws Exception {
byte[] keyBytes = Base64.decode(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(privateK);
signature.update(data);
return Base64.encode(signature.sign());
}
/**
* <p>
* 校验数字签名
* </p>
*
* @param data 已加密数据
* @param publicKey 公钥(BASE64 编码)
* @param sign 数字签名
*
* @return
* @throws Exception
*
*/
public static boolean verify(byte[] data, String publicKey, String sign)
throws Exception {
byte[] keyBytes = Base64.decode(publicKey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey publicK = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(publicK);
signature.update(data);
return signature.verify(Base64.decode(sign));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
第三步:创建测试类
import cn.hutool.core.util.RandomUtil;
import java.net.URLEncoder;
/**
* 测试类
* @author Moka
* @date 2022/5/10
* @apiNote
*/
public class RSATester {
static String publicKey = "替换 Moka 提供得 publicKey";
static String privateKey = "替换 Moka 提供得 privateKey";
public static void main(String[] args) throws Exception {
long timestamp = System.currentTimeMillis();
String randomString = RandomUtil.randomString(8);
String userName = "hanshg@mokahr.com";
//String s = "apiCode=25af48e84326d81f95d9021652bd4036&entCode=9d94eb859a6742d98e32fce040bbb8dc&nonce=" + randomString + "&timestamp=" + timestamp + "&userName=" + userName;
String s = "apiCode=接口编码&entCode=租户编码&nonce=" + randomString + "&timestamp=" + timestamp;
System.err.println(s);
String sign = RSAUtils.sign(s.getBytes(), privateKey);
System.out.println("签名:" + URLEncoder.encode(sign, "UTF-8"));
boolean status = RSAUtils.verify(s.getBytes(), publicKey, sign);
System.out.println("验证结果:" + status);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
4.签名算法 Python 版
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import MD5
import base64
def my_hash(data):
return MD5.new(data.encode('utf-8'))
def rsa_sign(data):
private_key_file = open('private.pem', 'r')
pri_key = RSA.importKey(private_key_file.read())
signer = PKCS1_v1_5.new(pri_key)
hash_obj = my_hash(data)
signature = base64.b64encode(signer.sign(hash_obj))
private_key_file.close()
return signature
def rsa_verify(signature, data):
public_key_file = open('public.pem', 'r')
pub_key = RSA.importKey(public_key_file.read())
hash_obj = my_hash(data)
verifier = PKCS1_v1_5.new(pub_key)
public_key_file.close()
return verifier.verify(hash_obj, base64.b64decode(signature))
if __name__ == '__main__':
test_data = "apiCode=0fe827b0ce0c2f1249642119bc3efcdb&entCode=0fe827b0ce0c2f1249642119bc3efcdb&nonce=99999915&timestamp=1632742060453&userName=qyzhg113@core.qyzhg.com"
print(rsa_sign(test_data).decode('utf-8'))
print(rsa_verify(rsa_sign(test_data), test_data))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29