DingTalkEncryptor.js 4.2 KB
/* eslint-disable no-bitwise */
'use strict';

const CryptoJS  = require("crypto-js");
const Crypto = require('crypto');
const DingTalkEncryptException = require('./DingTalkEncryptException');

class DingTalkEncryptor {
  constructor(token, encodingAesKey, corpIdOrSuiteKey) {
    this.utf8 = 'utf-8';
    this.base64 = 'base64';
    this.AES_ENCODE_KEY_LENGTH = 43;
    this.RANDOM_LENGTH = 16;

    this.token = token;
    this.encodingAesKey = encodingAesKey;
    this.aesKey =  CryptoJS.enc.Base64.parse(encodingAesKey+'=');
    this.corpId = corpIdOrSuiteKey;
    this.keySpec = this.aesKey;
    this.iv =  CryptoJS.enc.Base64.parse((encodingAesKey+'=').substring(0,22));
  }

  // verify encodingAesKey
  set encodingAesKey(val) {
    if (!val || val.length !== this.AES_ENCODE_KEY_LENGTH) {
      throw new DingTalkEncryptException(900004);
    }
  }

  encrypt(random, plainText) {
    try {
      const wordArray = CryptoJS.lib.WordArray.create([],16); 
      const textLen = plainText.length; 
      const textLenBuf = CryptoJS.lib.WordArray.create([textLen & 255]); 
      const plainTextBuf = CryptoJS.enc.Utf8.parse(plainText);
      const cropIdBuf = CryptoJS.enc.Utf8.parse(this.corpId); 
      const finalBuf =wordArray.concat(textLenBuf).concat(plainTextBuf).concat(cropIdBuf) 
      const encrypted = CryptoJS.AES.encrypt(finalBuf, this.keySpec, {   
          iv: this.iv,
          mode: CryptoJS.mode.CBC,
          padding:CryptoJS.pad.Pkcs7 
      });  
      return encrypted.toString();
    } catch (e) {
      console.log(e)
      throw new DingTalkEncryptException(900007);
    }
  }

  decrypt(encrypted) {
    let decrypt;
    try {
      // decrypt   
      const ciphertext =  CryptoJS.enc.Base64.parse(encrypted);
      decrypt = CryptoJS.AES.decrypt( {ciphertext}, this.keySpec, {   
          iv: this.iv,
          mode: CryptoJS.mode.CBC,
          padding:CryptoJS.pad.Pkcs7 
      }); 
    } catch (e) {
      throw new DingTalkEncryptException(900008);
    }
    //encrypt = Base64_Encode(AES_Encrypt[random(16B) + msg_len(4B) + msg + $key]) 钉钉加密消息格式
    let cropId;
    let plainText;  
    try { 

      const finalDecrypt =  decrypt.toString(CryptoJS.enc.Hex);
      const textLen = parseInt(finalDecrypt.substring(32,8+32), 16) ;
      plainText = CryptoJS.enc.Hex.parse(finalDecrypt.substring(40, 40 + textLen*2)).toString( CryptoJS.enc.Utf8);
      cropId =   CryptoJS.enc.Hex.parse(finalDecrypt.substring(40 + textLen*2)).toString(CryptoJS.enc.Utf8);

    } catch (e) { 
      throw new DingTalkEncryptException(900009);
    }

    if (cropId != this.corpId) {
      console.log(cropId, this.corpId,plainText,cropId.length ,this.corpId.length)
      throw new DingTalkEncryptException(900010);
    } else {
      return plainText;
    }
  }

  getSignature(token, timestamp, nonce, encrypt) {
    timestamp = timestamp + '';
    const strArr = [token, timestamp, nonce, encrypt];
    strArr.sort();
    const sha1 = CryptoJS.SHA1(strArr.join('')); 
    return sha1.toString(CryptoJS.enc.Hex);
  }

  getEncryptedMap(plaintext, timeStamp, nonce) {
    timeStamp = timeStamp + '';
    if (plaintext == null) {
      throw new DingTalkEncryptException(900001);
    } else if (timeStamp == null) {
      throw new DingTalkEncryptException(900002);
    } else if (nonce == null) {
      throw new DingTalkEncryptException(900003);
    } else {
      const encrypt = this.encrypt(this.getRandomStr(this.RANDOM_LENGTH), plaintext);
      const signature = this.getSignature(this.token, timeStamp, nonce, encrypt);
      return {
        msg_signature: signature,
        encrypt: encrypt,
        timeStamp: timeStamp,
        nonce: nonce
      };
    }
  }

  getDecryptMsg(msgSignature, timeStamp, nonce, encryptMsg) {
    const signature = this.getSignature(this.token, timeStamp, nonce, encryptMsg);
    if (signature !== msgSignature) {
      throw new DingTalkEncryptException(900006);
    } else {
      return this.decrypt(encryptMsg);
    }
  }

  getRandomStr(size) {
    const base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    let randomStr = '';
    for (let i = size; i > 0; --i) {
      randomStr += base[Math.floor(Math.random() * base.length)];
    }
    return randomStr;
  };
}

module.exports = DingTalkEncryptor;