package foundation.util;
|
|
import java.io.ByteArrayOutputStream;
|
import java.nio.charset.Charset;
|
import java.security.MessageDigest;
|
import java.util.Arrays;
|
import java.util.HashMap;
|
import java.util.Map;
|
import java.util.Random;
|
|
import javax.crypto.Cipher;
|
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.SecretKeySpec;
|
|
import org.apache.commons.codec.binary.Base64;
|
|
public class DingCallbackCrypto {
|
private static final Charset CHARSET = Charset.forName("utf-8");
|
private static final Base64 base64 = new Base64();
|
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 企业自建应用-事件订阅, 使用appKey
|
* 企业自建应用-注册回调地址, 使用corpId
|
* 第三方企业应用, 使用suiteKey
|
*
|
* @throws DingTalkEncryptException 执行失败,请查看该异常的错误码和具体的错误信息
|
*/
|
public DingCallbackCrypto(String token, String encodingAesKey, String corpId) throws Exception {
|
if (null == encodingAesKey || encodingAesKey.length() != AES_ENCODE_KEY_LENGTH) {
|
throw new Exception();
|
}
|
this.token = token;
|
this.corpId = corpId;
|
aesKey = Base64.decodeBase64(encodingAesKey + "=");
|
}
|
|
public Map<String, String> getEncryptedMap(String plaintext) throws Exception {
|
return getEncryptedMap(plaintext, System.currentTimeMillis(), getRandomStr(16));
|
}
|
|
/**
|
* 将和钉钉开放平台同步的消息体加密,返回加密Map
|
*
|
* @param plaintext 传递的消息体明文
|
* @param timeStamp 时间戳
|
* @param nonce 随机字符串
|
* @return
|
* @throws DingTalkEncryptException
|
*/
|
private Map<String, String> getEncryptedMap(String plaintext, Long timeStamp, String nonce)
|
throws Exception {
|
if (null == plaintext) {
|
throw new Exception();
|
}
|
if (null == timeStamp) {
|
throw new Exception();
|
}
|
if (null == nonce) {
|
throw new Exception();
|
}
|
// 加密
|
String encrypt = encrypt(getRandomStr(RANDOM_LENGTH), plaintext);
|
String signature = getSignature(token, String.valueOf(timeStamp), nonce, encrypt);
|
Map<String, String> resultMap = new HashMap<String, String>();
|
resultMap.put("msg_signature", signature);
|
resultMap.put("encrypt", encrypt);
|
resultMap.put("timeStamp", String.valueOf(timeStamp));
|
resultMap.put("nonce", nonce);
|
return resultMap;
|
}
|
|
/**
|
* 密文解密
|
*
|
* @param msgSignature 签名串
|
* @param timeStamp 时间戳
|
* @param nonce 随机串
|
* @param encryptMsg 密文
|
* @return 解密后的原文
|
* @throws DingTalkEncryptException
|
*/
|
public String getDecryptMsg(String msgSignature, String timeStamp, String nonce, String encryptMsg)
|
throws Exception {
|
//校验签名
|
String signature = getSignature(token, timeStamp, nonce, encryptMsg);
|
if (!signature.equals(msgSignature)) {
|
throw new Exception();
|
}
|
// 解密
|
String result = decrypt(encryptMsg);
|
return result;
|
}
|
|
/*
|
* 对明文加密.
|
* @param text 需要加密的明文
|
* @return 加密后base64编码的字符串
|
*/
|
public String encrypt(String random, String plaintext) throws Exception {
|
try {
|
byte[] randomBytes = random.getBytes(CHARSET);
|
byte[] plainTextBytes = plaintext.getBytes(CHARSET);
|
byte[] lengthByte = Util.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);
|
String result = base64.encodeToString(encrypted);
|
return result;
|
} catch (Exception e) {
|
throw new Exception();
|
}
|
}
|
|
/*
|
* 对密文进行解密.
|
* @param text 需要解密的密文
|
* @return 解密得到的明文
|
*/
|
private String decrypt(String text) throws Exception {
|
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对密文进行解码
|
byte[] encrypted = Base64.decodeBase64(text);
|
// 解密
|
originalArr = cipher.doFinal(encrypted);
|
} catch (Exception e) {
|
throw new Exception();
|
}
|
|
String plainText;
|
String fromCorpid;
|
try {
|
// 去除补位字符
|
byte[] bytes = PKCS7Padding.removePaddingBytes(originalArr);
|
// 分离16位随机字符串,网络字节序和corpId
|
byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
|
int plainTextLegth = Util.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 Exception();
|
}
|
|
// corpid不相同的情况
|
if (!fromCorpid.equals(corpId)) {
|
throw new Exception();
|
}
|
return plainText;
|
}
|
|
/**
|
* 数字签名
|
*
|
* @param token isv token
|
* @param timestamp 时间戳
|
* @param nonce 随机串
|
* @param encrypt 加密文本
|
* @return
|
* @throws DingTalkEncryptException
|
*/
|
public String getSignature(String token, String timestamp, String nonce, String encrypt)
|
throws Exception {
|
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 Exception();
|
}
|
}
|
|
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();
|
}
|
}
|