Created
July 9, 2021 03:22
-
-
Save mlicheng/c4a6d4e541c16a43b2e1f3b60951fa09 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1. 请求处理 | |
const dingTalkCrypt = new DingTalkMsgCrypt( | |
"MLBVpjG4VLaUrZda2A", | |
"lPEjaswljAlrcjwTyU752yXxxOdkMYpK8VmlfuixdG4", | |
"ding4ee83cd86390472dbc961a6cb783455b" | |
); | |
const result = await dingTalkCrypt.getDecryptMsg(query.signature, query.timestamp, query.nonce, body.encrypt); | |
console.log("DEBUG plainText: ", JSON.stringify(result)); | |
const data = await dingTalkCrypt.getEncryptedMap("success", getNowTimestamp(), _.randomString(8)); | |
console.log("DEBUG data: ", JSON.stringify(data)); | |
// const check = await dingTalkCrypt.getDecryptMsg(data.msg_signature, data.timeStamp, data.nonce, data.encrypt); | |
// console.log("DEBUG check: ", JSON.stringify(check)); | |
return data; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2. DingTalkMsgCrypt | |
export class DingTalkMsgCrypt { | |
private id: string; | |
private token: string; | |
private key: Buffer; | |
private iv: Buffer; | |
/** | |
* 加解密信息构造函数 | |
* | |
* @param {String} token 开发者设置的Token | |
* @param {String} encodingAESKey 开发者设置的EncodingAESKey | |
* @param {String} id 对于普通企业开发,填写企业的Corpid;对于ISV来说,填写对应的suitekey | |
*/ | |
constructor(token: string, encodingAESKey: string, id: string) { | |
if (!token || !encodingAESKey || !id) { | |
throw new Error("please check arguments"); | |
} | |
this.token = token; | |
this.id = id; | |
const AESKey = Buffer.from(encodingAESKey + "=", "base64"); | |
if (AESKey.length !== 32) { | |
throw new Error("encodingAESKey invalid"); | |
} | |
this.key = AESKey; | |
this.iv = AESKey.slice(0, 16); | |
} | |
/** | |
* 获取签名 | |
* | |
* @param {String} timestamp 时间戳 | |
* @param {String} nonce 随机数 | |
* @param {String} encrypt 加密后的文本 | |
*/ | |
async getSignature(token, timestamp, nonce, encrypt) { | |
const shasum = crypto.createHash("sha1"); | |
const arr = [token, timestamp, nonce, encrypt].sort(); | |
shasum.update(arr.join("")); | |
return shasum.digest("hex"); | |
} | |
async decrypt(text: string) { | |
// 创建解密对象,AES采用CBC模式,数据采用PKCS#7填充;IV初始向量大小为16字节,取AESKey前16字节 | |
const decipher = crypto.createDecipheriv("aes-256-cbc", this.key, this.iv); | |
decipher.setAutoPadding(false); | |
let deciphered = Buffer.concat([decipher.update(text, "base64"), decipher.final()]); | |
deciphered = await this.decode(deciphered); | |
// 算法:AES_Encrypt[random(16B) + msg_len(4B) + msg + $CorpID] | |
// 去除16位随机数 | |
const content = deciphered.slice(16); | |
const length = content.slice(0, 4).readUInt32BE(0); | |
return { | |
message: content.slice(4, length + 4).toString(), | |
id: content.slice(length + 4).toString() | |
}; | |
} | |
/** | |
* 对明文进行加密 | |
* | |
* @param {String} text 待加密的明文 | |
*/ | |
async encrypt(text: string) { | |
// 算法:AES_Encrypt[random(16B) + msg_len(4B) + msg + $CorpID] | |
// 获取16B的随机字符串 | |
const randomString = crypto.pseudoRandomBytes(16); | |
const msg = Buffer.from(text); | |
// 获取4B的内容长度的网络字节序 | |
const msgLength = Buffer.alloc(4); | |
msgLength.writeUInt32BE(msg.length, 0); | |
const id = Buffer.from(this.id); | |
const bufMsg = Buffer.concat([randomString, msgLength, msg, id]); | |
// 对明文进行补位操作 | |
const encoded = await this.encode(bufMsg); | |
// 创建加密对象,AES采用CBC模式,数据采用PKCS#7填充;IV初始向量大小为16字节,取AESKey前16字节 | |
const cipher = crypto.createCipheriv("aes-256-cbc", this.key, this.iv); | |
cipher.setAutoPadding(false); | |
const cipheredMsg = Buffer.concat([cipher.update(encoded), cipher.final()]); | |
// 返回加密数据的base64编码 | |
return cipheredMsg.toString("base64"); | |
} | |
async getEncryptedMap(text: string, timestamp: number | string, nonce: string) { | |
timestamp = timestamp + ""; | |
if (text == null || timestamp == null || nonce == null) { | |
throw new WTError(1000001, ""); | |
} else { | |
const encrypt = await this.encrypt(text); | |
const signature = await this.getSignature(this.token, timestamp, nonce, encrypt); | |
return { | |
msg_signature: signature, | |
encrypt: encrypt, | |
timeStamp: timestamp, | |
nonce: nonce | |
}; | |
} | |
} | |
async getDecryptMsg(msgSignature: string, timestamp: string | number, nonce: string, encryptMsg: string) { | |
const signature = await this.getSignature(this.token, timestamp, nonce, encryptMsg); | |
console.log("token", this.token); | |
console.log("signature", signature); | |
console.log("msgSignature", msgSignature); | |
if (signature !== msgSignature) { | |
throw new WTInternalError("dingtalk signature error"); | |
} else { | |
return this.decrypt(encryptMsg); | |
} | |
} | |
// PKCS7算法-删除解密后明文的补位字符 | |
async decode(text) { | |
let pad = text[text.length - 1]; | |
if (pad < 1 || pad > 32) { | |
pad = 0; | |
} | |
return text.slice(0, text.length - pad); | |
} | |
// PKCS7算法-对需要加密的明文进行填充补位 | |
async encode(text: any) { | |
const blockSize = 32; | |
const textLength = text.length; | |
//计算需要填充的位数 | |
const amountToPad = blockSize - (textLength % blockSize); | |
const result = Buffer.alloc(amountToPad); | |
result.fill(amountToPad); | |
return Buffer.concat([text, result]); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment