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
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
|
/**
* @author wwz
* @version 1 (2018/7/26)
* @since Java7
*/
public class DingTalkEncryptor {
private static final Charset CHARSET = Charset.forName("UTF-8");
private byte[] aesKey;
private String token;
private String corpId;
/**
* ask getPaddingBytes key固定长度
**/
private static final Integer AES_ENCODE_KEY_LENGTH = 43;
/**
* 加密随机字符串字节长度
**/
private static final Integer RANDOM_LENGTH = 16;
/**
* 构造函数
*
* @param token 钉钉开放平台上,开发者设置的token
* @param encodingAesKey 钉钉开放台上,开发者设置的EncodingAESKey
* @param corpId ISV进行配置的时候应该传对应套件的SUITE_KEY(第一次创建时传的是默认的CREATE_SUITE_KEY),普通企业是Corpid
*/
public DingTalkEncryptor(String token, String encodingAesKey, String corpId) {
if (null == encodingAesKey || encodingAesKey.length() != AES_ENCODE_KEY_LENGTH) {
throw new IllegalArgumentException("encodingAesKey is null");
}
this.token = token;
this.corpId = corpId;
this.aesKey = Base64.decode(encodingAesKey + "=");
}
/**
* 将和钉钉开放平台同步的消息体加密,返回加密Map
*
* @param message 传递的消息体明文
* @param timeStamp 时间戳
* @param nonce 随机字符串
* @return
*/
public Map<String, ? extends Serializable> getEncryptedMsg(String message, Long timeStamp, String nonce) {
Assert.notNull(message, "plaintext is null");
Assert.notNull(timeStamp, "timeStamp is null");
Assert.notNull(nonce, "nonce is null");
String encrypt = encrypt(getRandomStr(RANDOM_LENGTH), message);
String signature = getSignature(token, String.valueOf(timeStamp), nonce, encrypt);
return ImmutableMap.of(
"msg_signature", signature, //
"encrypt", encrypt, //
"timeStamp", timeStamp,//
"nonce", nonce);
}
/**
* 密文解密
*
* @param msgSignature 签名串
* @param timeStamp 时间戳
* @param nonce 随机串
* @param encryptMsg 密文
* @return 解密后的原文
*/
public String getDecryptMsg(String msgSignature, String timeStamp, String nonce, String encryptMsg) {
// 校验签名
String signature = getSignature(token, timeStamp, nonce, encryptMsg);
if (!signature.equals(msgSignature)) {
throw new RuntimeException("校验签名失败。");
}
// 解密
return decrypt(encryptMsg);
}
private String encrypt(String random, String plaintext) {
try {
byte[] randomBytes = random.getBytes(CHARSET);
byte[] plainTextBytes = plaintext.getBytes(CHARSET);
byte[] lengthByte = int2Bytes(plainTextBytes.length);
byte[] corpidBytes = corpId.getBytes(CHARSET);
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
byteStream.write(randomBytes);
byteStream.write(lengthByte);
byteStream.write(plainTextBytes);
byteStream.write(corpidBytes);
byte[] padBytes = PKCS7Padding.getPaddingBytes(byteStream.size());
byteStream.write(padBytes);
byte[] unencrypted = byteStream.toByteArray();
byteStream.close();
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
byte[] encrypted = cipher.doFinal(unencrypted);
return Base64.encode(encrypted);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 对密文进行解密.
* @param text 需要解密的密文
* @return 解密得到的明文
*/
private String decrypt(String text) {
byte[] originalArr;
try {
// 设置解密模式为AES的CBC模式
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
// 使用BASE64对密文进行解码, 解密
originalArr = cipher.doFinal(Base64.decode(text));
} catch (Exception e) {
throw new RuntimeException("计算解密文本错误");
}
String plainText;
String fromCorpid;
try {
// 去除补位字符
byte[] bytes = PKCS7Padding.removePaddingBytes(originalArr);
// 分离16位随机字符串,网络字节序和corpId
byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
int plainTextLegth = bytes2int(networkOrder);
plainText = new String(Arrays.copyOfRange(bytes, 20, 20 + plainTextLegth), CHARSET);
fromCorpid = new String(Arrays.copyOfRange(bytes, 20 + plainTextLegth, bytes.length), CHARSET);
} catch (Exception e) {
throw new RuntimeException("计算解密文本长度错误");
}
// corpid不相同的情况
if (!fromCorpid.equals(corpId)) {
throw new RuntimeException("计算文本密码错误");
}
return plainText;
}
/**
* 数字签名
* @param token isv token
* @param timestamp 时间戳
* @param nonce 随机串
* @param encrypt 加密文本
* @return
*/
public String getSignature(String token, String timestamp, String nonce, String encrypt) {
try {
String[] array = new String[]{token, timestamp, nonce, encrypt};
Arrays.sort(array);
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 4; i++) {
sb.append(array[i]);
}
String str = sb.toString();
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(str.getBytes());
byte[] digest = md.digest();
StringBuffer hexstr = new StringBuffer();
String shaHex = "";
for (int i = 0; i < digest.length; i++) {
shaHex = Integer.toHexString(digest[i] & 0xFF);
if (shaHex.length() < 2) {
hexstr.append(0);
}
hexstr.append(shaHex);
}
return hexstr.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String getRandomStr(int count) {
String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < count; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
/**
* int转byte数组,高位在前
*/
public static byte[] int2Bytes(int count) {
byte[] byteArr = new byte[4];
byteArr[3] = (byte) (count & 0xFF);
byteArr[2] = (byte) (count >> 8 & 0xFF);
byteArr[1] = (byte) (count >> 16 & 0xFF);
byteArr[0] = (byte) (count >> 24 & 0xFF);
return byteArr;
}
/**
* 高位在前bytes数组转int
* @param byteArr
* @return
*/
public static int bytes2int(byte[] byteArr) {
int count = 0;
for (int i = 0; i < 4; i++) {
count <<= 8;
count |= byteArr[i] & 0xff;
}
return count;
}
}
|
评论