jsonmap = convert.convertFrom(MAPTYPE, json);
qyAccessToken.accesstime = System.currentTimeMillis();
qyAccessToken.token = jsonmap.get("access_token");
String exp = jsonmap.get("expires_in");
if (exp != null) qyAccessToken.expires = (Integer.parseInt(exp) - 100) * 1000;
}
return qyAccessToken.token;
}
/**
* 将公众平台回复用户的消息加密打包.
*
* - 对要发送的消息进行AES-CBC加密
* - 生成安全签名
* - 将消息密文和安全签名打包成xml格式
*
*
* @param replyMsg 公众平台待回复用户的消息,xml格式的字符串
* @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp
* @param nonce 随机串,可以自己生成,也可以用URL参数的nonce
*
* @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串
*/
protected String encryptQYMessage(String replyMsg, String timeStamp, String nonce) {
// 加密
String encrypt = encryptQY(random16String(), replyMsg);
// 生成安全签名
if (timeStamp == null || timeStamp.isEmpty()) timeStamp = Long.toString(System.currentTimeMillis());
String signature = sha1(qytoken, timeStamp, nonce, encrypt);
// System.out.println("发送给平台的签名是: " + signature[1].toString());
// 生成发送的xml
return "\n\n"
+ "\n"
+ "" + timeStamp + "\n"
+ "\n";
}
protected String decryptQYMessage(String msgSignature, String timeStamp, String nonce, String postData) {
// 密钥,公众账号的app secret
// 提取密文
String encrypt = postData.substring(postData.indexOf(""));
// 验证安全签名
if (!sha1(qytoken, timeStamp, nonce, encrypt).equals(msgSignature)) throw new RuntimeException("signature verification error");
return decryptQY(encrypt);
}
/**
* 对明文进行加密.
*
* @param randomStr String
* @param text 需要加密的明文
*
* @return 加密后base64编码的字符串
*/
protected String encryptQY(String randomStr, String text) {
ByteArray bytes = new ByteArray();
byte[] randomStrBytes = randomStr.getBytes(CHARSET);
byte[] textBytes = text.getBytes(CHARSET);
byte[] corpidBytes = qycorpid.getBytes(CHARSET);
// randomStr + networkBytesOrder + text + qycorpid
bytes.write(randomStrBytes);
bytes.writeInt(textBytes.length);
bytes.write(textBytes);
bytes.write(corpidBytes);
// ... + pad: 使用自定义的填充方式对明文进行补位填充
byte[] padBytes = encodePKCS7(bytes.size());
bytes.write(padBytes);
// 获得最终的字节流, 未加密
try {
// 加密
byte[] encrypted = createQYCipher(Cipher.ENCRYPT_MODE).doFinal(bytes.directBytes(), 0, bytes.size());
// 使用BASE64对加密后的字符串进行编码
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new RuntimeException("AES加密失败", e);
}
}
protected String decryptQY(String text) {
byte[] original;
try {
// 使用BASE64对密文进行解码
original = createQYCipher(Cipher.DECRYPT_MODE).doFinal(Base64.getDecoder().decode(text));
} catch (Exception e) {
throw new RuntimeException("AES解密失败", e);
}
try {
// 去除补位字符
byte[] bytes = decodePKCS7(original);
// 分离16位随机字符串,网络字节序和corpid
int xmlLength = (bytes[16] & 0xFF) << 24 | (bytes[17] & 0xFF) << 16 | (bytes[18] & 0xFF) << 8 | bytes[19] & 0xFF;
if (!qycorpid.equals(new String(bytes, 20 + xmlLength, bytes.length - 20 - xmlLength, CHARSET))) {
throw new RuntimeException("corpid校验失败");
}
return new String(bytes, 20, xmlLength, CHARSET);
} catch (RuntimeException e) {
if (e.getMessage().contains("corpid")) throw e;
throw new RuntimeException("解密后得到的buffer非法", e);
}
}
protected Cipher createQYCipher(int mode) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); //AES192、256位加密解密 需要将新版 local_policy.jar、US_export_policy.jar两个文件覆盖到 ${JDK_HOME}/jre/lib/security/下
if (qykeyspec == null) {
byte[] aeskeyBytes = Base64.getDecoder().decode(qyaeskey + "=");
qykeyspec = new SecretKeySpec(aeskeyBytes, "AES");
qyivspec = new IvParameterSpec(aeskeyBytes, 0, 16);
}
cipher.init(mode, qykeyspec, qyivspec);
return cipher;
}
protected void runAsync(Runnable runner) {
Thread thread = Thread.currentThread();
if (thread instanceof WorkThread) {
((WorkThread) thread).runAsync(runner);
return;
}
runner.run();
}
//-----------------------------------通用接口----------------------------------------------------------
// 随机生成16位字符串
protected static String random16String() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 16; i++) {
sb.append(BASE.charAt(RANDOM.nextInt(BASE.length())));
}
return sb.toString();
}
/**
* 用SHA1算法生成安全签名
*
* @param strings String[]
*
* @return 安全签名
*/
protected static String sha1(String... strings) {
try {
Arrays.sort(strings);
MessageDigest md = MessageDigest.getInstance("SHA-1");
for (String s : strings) md.update(s.getBytes());
return Utility.binToHexString(md.digest());
} catch (Exception e) {
throw new RuntimeException("SHA encryption to generate signature failure", e);
}
}
/**
* 获得对明文进行补位填充的字节.
*
* @param count 需要进行填充补位操作的明文字节个数
*
* @return 补齐用的字节数组
*/
private static byte[] encodePKCS7(int count) {
// 计算需要填充的位数
int amountToPad = 32 - (count % 32);
if (amountToPad == 0) amountToPad = 32;
// 获得补位所用的字符
char padChr = (char) (byte) (amountToPad & 0xFF);
StringBuilder tmp = new StringBuilder();
for (int index = 0; index < amountToPad; index++) {
tmp.append(padChr);
}
return tmp.toString().getBytes(CHARSET);
}
/**
* 删除解密后明文的补位字符
*
* @param decrypted 解密后的明文
*
* @return 删除补位字符后的明文
*/
private static byte[] decodePKCS7(byte[] decrypted) {
int pad = (int) decrypted[decrypted.length - 1];
if (pad < 1 || pad > 32) pad = 0;
return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
}
}