Skip to content

Instantly share code, notes, and snippets.

@mlicheng
Created July 9, 2021 03:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mlicheng/c4a6d4e541c16a43b2e1f3b60951fa09 to your computer and use it in GitHub Desktop.
Save mlicheng/c4a6d4e541c16a43b2e1f3b60951fa09 to your computer and use it in GitHub Desktop.
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;
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