package doumeemes.core.utils.dingding; 
 | 
  
 | 
import java.io.ByteArrayOutputStream; 
 | 
import java.nio.charset.Charset; 
 | 
import java.security.MessageDigest; 
 | 
import java.security.Permission; 
 | 
import java.security.PermissionCollection; 
 | 
import java.util.Arrays; 
 | 
import java.util.HashMap; 
 | 
import java.util.Map; 
 | 
import java.util.Random; 
 | 
import java.security.Security; 
 | 
import java.lang.reflect.Field; 
 | 
  
 | 
import javax.crypto.Cipher; 
 | 
import javax.crypto.spec.IvParameterSpec; 
 | 
import javax.crypto.spec.SecretKeySpec; 
 | 
  
 | 
import com.alibaba.fastjson.JSON; 
 | 
  
 | 
import org.apache.commons.codec.binary.Base64; 
 | 
  
 | 
/** 
 | 
 * 钉钉开放平台加解密方法 
 | 
 * 在ORACLE官方网站下载JCE无限制权限策略文件 
 | 
 * JDK6的下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html 
 | 
 * JDK7的下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html 
 | 
 * JDK8的下载地址 https://www.oracle.com/java/technologies/javase-jce8-downloads.html 
 | 
 */ 
 | 
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 DingTalkEncryptException { 
 | 
        if (null == encodingAesKey || encodingAesKey.length() != AES_ENCODE_KEY_LENGTH) { 
 | 
            throw new DingTalkEncryptException(DingTalkEncryptException.AES_KEY_ILLEGAL); 
 | 
        } 
 | 
        this.token = token; 
 | 
        this.corpId = corpId; 
 | 
        aesKey = Base64.decodeBase64(encodingAesKey + "="); 
 | 
    } 
 | 
  
 | 
    public Map<String, String> getEncryptedMap(String plaintext) throws DingTalkEncryptException { 
 | 
        return getEncryptedMap(plaintext, System.currentTimeMillis(), Utils.getRandomStr(16)); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * 将和钉钉开放平台同步的消息体加密,返回加密Map 
 | 
     * 
 | 
     * @param plaintext 传递的消息体明文 
 | 
     * @param timeStamp 时间戳 
 | 
     * @param nonce     随机字符串 
 | 
     * @return 
 | 
     * @throws DingTalkEncryptException 
 | 
     */ 
 | 
    public Map<String, String> getEncryptedMap(String plaintext, Long timeStamp, String nonce) 
 | 
            throws DingTalkEncryptException { 
 | 
        if (null == plaintext) { 
 | 
            throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_PLAINTEXT_ILLEGAL); 
 | 
        } 
 | 
        if (null == timeStamp) { 
 | 
            throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_TIMESTAMP_ILLEGAL); 
 | 
        } 
 | 
        if (null == nonce) { 
 | 
            throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_NONCE_ILLEGAL); 
 | 
        } 
 | 
        // 加密 
 | 
        String encrypt = encrypt(Utils.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 DingTalkEncryptException { 
 | 
        //校验签名 
 | 
        String signature = getSignature(token, timeStamp, nonce, encryptMsg); 
 | 
        if (!signature.equals(msgSignature)) { 
 | 
            throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_SIGNATURE_ERROR); 
 | 
        } 
 | 
        // 解密 
 | 
        String result = decrypt(encryptMsg); 
 | 
        return result; 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * 对明文加密. 
 | 
     * @param text 需要加密的明文 
 | 
     * @return 加密后base64编码的字符串 
 | 
     */ 
 | 
    private String encrypt(String random, String plaintext) throws DingTalkEncryptException { 
 | 
        try { 
 | 
            byte[] randomBytes = random.getBytes(CHARSET); 
 | 
            byte[] plainTextBytes = plaintext.getBytes(CHARSET); 
 | 
            byte[] lengthByte = Utils.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 DingTalkEncryptException(DingTalkEncryptException.COMPUTE_ENCRYPT_TEXT_ERROR); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * 对密文进行解密. 
 | 
     * @param text 需要解密的密文 
 | 
     * @return 解密得到的明文 
 | 
     */ 
 | 
    private String decrypt(String text) throws DingTalkEncryptException { 
 | 
        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 DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_ERROR); 
 | 
        } 
 | 
  
 | 
        String plainText; 
 | 
        String fromCorpid; 
 | 
        try { 
 | 
            // 去除补位字符 
 | 
            byte[] bytes = PKCS7Padding.removePaddingBytes(originalArr); 
 | 
            // 分离16位随机字符串,网络字节序和corpId 
 | 
            byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); 
 | 
            int plainTextLegth = Utils.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 DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_LENGTH_ERROR); 
 | 
        } 
 | 
  
 | 
        // corpid不相同的情况 
 | 
        if (!fromCorpid.equals(corpId)) { 
 | 
            throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_CORPID_ERROR); 
 | 
        } 
 | 
        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 DingTalkEncryptException { 
 | 
        try { 
 | 
            String[] array = new String[] {token, timestamp, nonce, encrypt}; 
 | 
            Arrays.sort(array); 
 | 
            System.out.println(JSON.toJSONString(array)); 
 | 
            StringBuffer sb = new StringBuffer(); 
 | 
            for (int i = 0; i < 4; i++) { 
 | 
                sb.append(array[i]); 
 | 
            } 
 | 
            String str = sb.toString(); 
 | 
            System.out.println(str); 
 | 
            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 DingTalkEncryptException(DingTalkEncryptException.COMPUTE_SIGNATURE_ERROR); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    public static class Utils { 
 | 
        public Utils() { 
 | 
        } 
 | 
  
 | 
        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(); 
 | 
        } 
 | 
  
 | 
        public static byte[] int2Bytes(int count) { 
 | 
            byte[] byteArr = new byte[] {(byte)(count >> 24 & 255), (byte)(count >> 16 & 255), (byte)(count >> 8 & 255), 
 | 
                    (byte)(count & 255)}; 
 | 
            return byteArr; 
 | 
        } 
 | 
  
 | 
        public static int bytes2int(byte[] byteArr) { 
 | 
            int count = 0; 
 | 
  
 | 
            for (int i = 0; i < 4; ++i) { 
 | 
                count <<= 8; 
 | 
                count |= byteArr[i] & 255; 
 | 
            } 
 | 
  
 | 
            return count; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    public static class PKCS7Padding { 
 | 
        private static final Charset CHARSET = Charset.forName("utf-8"); 
 | 
        private static final int BLOCK_SIZE = 32; 
 | 
  
 | 
        public PKCS7Padding() { 
 | 
        } 
 | 
  
 | 
        public static byte[] getPaddingBytes(int count) { 
 | 
            int amountToPad = 32 - count % 32; 
 | 
            if (amountToPad == 0) { 
 | 
                amountToPad = 32; 
 | 
            } 
 | 
  
 | 
            char padChr = chr(amountToPad); 
 | 
            String tmp = new String(); 
 | 
  
 | 
            for (int index = 0; index < amountToPad; ++index) { 
 | 
                tmp = tmp + padChr; 
 | 
            } 
 | 
  
 | 
            return tmp.getBytes(CHARSET); 
 | 
        } 
 | 
  
 | 
        public static byte[] removePaddingBytes(byte[] decrypted) { 
 | 
            int pad = decrypted[decrypted.length - 1]; 
 | 
            if (pad < 1 || pad > 32) { 
 | 
                pad = 0; 
 | 
            } 
 | 
  
 | 
            return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); 
 | 
        } 
 | 
  
 | 
        private static char chr(int a) { 
 | 
            byte target = (byte)(a & 255); 
 | 
            return (char)target; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    public static class DingTalkEncryptException extends Exception { 
 | 
        public static final int SUCCESS = 0; 
 | 
        public static final int ENCRYPTION_PLAINTEXT_ILLEGAL = 900001; 
 | 
        public static final int ENCRYPTION_TIMESTAMP_ILLEGAL = 900002; 
 | 
        public static final int ENCRYPTION_NONCE_ILLEGAL = 900003; 
 | 
        public static final int AES_KEY_ILLEGAL = 900004; 
 | 
        public static final int SIGNATURE_NOT_MATCH = 900005; 
 | 
        public static final int COMPUTE_SIGNATURE_ERROR = 900006; 
 | 
        public static final int COMPUTE_ENCRYPT_TEXT_ERROR = 900007; 
 | 
        public static final int COMPUTE_DECRYPT_TEXT_ERROR = 900008; 
 | 
        public static final int COMPUTE_DECRYPT_TEXT_LENGTH_ERROR = 900009; 
 | 
        public static final int COMPUTE_DECRYPT_TEXT_CORPID_ERROR = 900010; 
 | 
        private static Map<Integer, String> msgMap = new HashMap(); 
 | 
        private Integer code; 
 | 
  
 | 
        static { 
 | 
            msgMap.put(0, "成功"); 
 | 
            msgMap.put(900001, "加密明文文本非法"); 
 | 
            msgMap.put(900002, "加密时间戳参数非法"); 
 | 
            msgMap.put(900003, "加密随机字符串参数非法"); 
 | 
            msgMap.put(900005, "签名不匹配"); 
 | 
            msgMap.put(900006, "签名计算失败"); 
 | 
            msgMap.put(900004, "不合法的aes key"); 
 | 
            msgMap.put(900007, "计算加密文字错误"); 
 | 
            msgMap.put(900008, "计算解密文字错误"); 
 | 
            msgMap.put(900009, "计算解密文字长度不匹配"); 
 | 
            msgMap.put(900010, "计算解密文字corpid不匹配"); 
 | 
        } 
 | 
  
 | 
        public Integer getCode() { 
 | 
            return this.code; 
 | 
        } 
 | 
  
 | 
        public DingTalkEncryptException(Integer exceptionCode) { 
 | 
            super((String)msgMap.get(exceptionCode)); 
 | 
            this.code = exceptionCode; 
 | 
        } 
 | 
    } 
 | 
    static { 
 | 
        try { 
 | 
            Security.setProperty("crypto.policy", "limited"); 
 | 
            RemoveCryptographyRestrictions(); 
 | 
        } catch (Exception var1) { 
 | 
        } 
 | 
  
 | 
    } 
 | 
    private static void RemoveCryptographyRestrictions() throws Exception { 
 | 
        Class<?> jceSecurity = getClazz("javax.crypto.JceSecurity"); 
 | 
        Class<?> cryptoPermissions = getClazz("javax.crypto.CryptoPermissions"); 
 | 
        Class<?> cryptoAllPermission = getClazz("javax.crypto.CryptoAllPermission"); 
 | 
        if (jceSecurity != null) { 
 | 
            setFinalStaticValue(jceSecurity, "isRestricted", false); 
 | 
            PermissionCollection defaultPolicy = (PermissionCollection)getFieldValue(jceSecurity, "defaultPolicy", (Object)null, PermissionCollection.class); 
 | 
            if (cryptoPermissions != null) { 
 | 
                Map<?, ?> map = (Map)getFieldValue(cryptoPermissions, "perms", defaultPolicy, Map.class); 
 | 
                map.clear(); 
 | 
            } 
 | 
  
 | 
            if (cryptoAllPermission != null) { 
 | 
                Permission permission = (Permission)getFieldValue(cryptoAllPermission, "INSTANCE", (Object)null, Permission.class); 
 | 
                defaultPolicy.add(permission); 
 | 
            } 
 | 
        } 
 | 
  
 | 
    } 
 | 
    private static Class<?> getClazz(String className) { 
 | 
        Class clazz = null; 
 | 
  
 | 
        try { 
 | 
            clazz = Class.forName(className); 
 | 
        } catch (Exception var3) { 
 | 
        } 
 | 
  
 | 
        return clazz; 
 | 
    } 
 | 
    private static void setFinalStaticValue(Class<?> srcClazz, String fieldName, Object newValue) throws Exception { 
 | 
        Field field = srcClazz.getDeclaredField(fieldName); 
 | 
        field.setAccessible(true); 
 | 
        Field modifiersField = Field.class.getDeclaredField("modifiers"); 
 | 
        modifiersField.setAccessible(true); 
 | 
        modifiersField.setInt(field, field.getModifiers() & -17); 
 | 
        field.set((Object)null, newValue); 
 | 
    } 
 | 
    private static <T> T getFieldValue(Class<?> srcClazz, String fieldName, Object owner, Class<T> dstClazz) throws Exception { 
 | 
        Field field = srcClazz.getDeclaredField(fieldName); 
 | 
        field.setAccessible(true); 
 | 
        return dstClazz.cast(field.get(owner)); 
 | 
    } 
 | 
  
 | 
} 
 |