基于NodeJS开发钉钉回调接口实现AES-CBC加解密

yizhihongxing

下面是关于基于NodeJS开发钉钉回调接口实现AES-CBC加解密的完整攻略。

简介

钉钉回调接口是钉钉提供的一种主动通知机制,允许开发者注册特定类型的事件(比如用户离职、群组变化等),当事件发生时,钉钉会向开发者指定的服务器推送消息,以便开发者及时获取钉钉中发生的各种变化情况。

为保证安全性,钉钉回调接口推送的消息采用了AES-CBC加密方式,需要在服务器端对消息进行解密才能得到其真正内容。

此文档将讲述如何基于NodeJS开发钉钉回调接口实现消息的AES-CBC加解密。

开发环境

  • Node.js 10.x
  • npm 6.x

步骤

步骤1:安装必要的依赖

使用Node.js开发钉钉回调接口需要使用到一些第三方库来辅助开发,这些库可以通过npm包管理器进行安装。在命令行中执行以下命令:

npm install express body-parser crypto --save

其中:

  • express:一个流行的Node.js Web框架,用于搭建服务器端的Web应用程序;
  • body-parser:一个Node.js中间件,用于解析HTTP请求体中的数据;
  • crypto:Node.js支持的加密解密库。

步骤2:创建服务器

创建一个express实例:

const express = require('express')
const bodyParser = require('body-parser')
const app = express()

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))

const port = process.env.PORT || 3000

app.listen(port, () => {
  console.log(`Server is listening on port ${port}...`)
})

在此示例中,我们使用express以及body-parser来解析HTTP请求,在端口3000上启动HTTP服务器。

步骤3:接收推送消息并解密

当钉钉推送消息到服务器时,我们需要对消息进行解密以获取其真实内容。下面是解密的具体步骤:

3.1:获取消息体和签名

钉钉推送的消息体保存在HTTP请求体的encrypt参数中,签名则在HTTP请求头的x-ns-signature参数中,因此,我们需要从HTTP请求中提取这两个参数。

app.post('/', (req, res) => {
  const encrypt = req.body.encrypt
  const signature = req.headers['x-ns-signature']
  ...
})

3.2:验证签名

我们需要将消息体和回调url上的token按照顺序拼接,接着使用签名算法(HMAC_SHA256)对拼接后的字符串进行HMAC_SHA256计算,最后将计算结果转为16进制表示的字符串。最终的签名结果应该是和HTTP请求头中的x-ns-signature参数中的值相同。

app.post('/', (req, res) => {
  const encrypt = req.body.encrypt
  const signature = req.headers['x-ns-signature']

  // 验证签名是否正确
  const token = 'your_token'
  const timestamp = req.headers['x-ns-timestamp']
  const plainText = token + timestamp + encrypt
  const hmac = crypto.createHmac('sha256', token)
  hmac.update(plainText)
  if (signature !== hmac.digest('hex')) {
    res.status(401).send('signature invalid')
    return
  }
  ...
})

3.3:解密消息体

我们需要使用AES-CBC算法解密消息体,首先需要解密Base64编码后的AES密钥(HTTP请求头的x-ns-ekey参数),得到真正的AES密钥;接着使用AES密钥和IV(初始向量,HTTP请求头的x-ns-iv参数),对消息体进行解密,得到消息的原始内容。

app.post('/', (req, res) => {
  const encrypt = req.body.encrypt
  const signature = req.headers['x-ns-signature']

  // 验证签名是否正确
  const token = 'your_token'
  const timestamp = req.headers['x-ns-timestamp']
  const plainText = token + timestamp + encrypt
  const hmac = crypto.createHmac('sha256', token)
  hmac.update(plainText)
  if (signature !== hmac.digest('hex')) {
    res.status(401).send('signature invalid')
    return
  }

  // 解密消息体
  const ekey = Buffer.from(req.headers['x-ns-ekey'], 'base64')
  const iv = Buffer.from(req.headers['x-ns-iv'], 'base64')
  const decipher = crypto.createDecipheriv('aes-256-cbc', ekey, iv)
  let decrypted = decipher.update(encrypt, 'base64', 'utf8')
  decrypted += decipher.final('utf8')
  console.log(decrypted)
  ...
})

最终的解密结果即为变量decrypted中保存的内容。

步骤4:构造回复消息并加密

通常情况下,我们需要在接收到消息之后向钉钉服务端回复一条确认消息。回复消息也需要进行加密处理,加密时需要按照以下步骤进行:

4.1:构造回复明文

回复消息的明文格式必须与接收到的消息格式相同,所以我们需要将解密后的消息体中的msgtype值复制到回复消息中,同时指定一个响应指令(HTTP响应头的x-ns-responsekey参数)。

app.post('/', (req, res) => {
  const encrypt = req.body.encrypt
  const signature = req.headers['x-ns-signature']

  // 验证签名是否正确
  const token = 'your_token'
  const timestamp = req.headers['x-ns-timestamp']
  const plainText = token + timestamp + encrypt
  const hmac = crypto.createHmac('sha256', token)
  hmac.update(plainText)
  if (signature !== hmac.digest('hex')) {
    res.status(401).send('signature invalid')
    return
  }

  // 解密消息体
  const ekey = Buffer.from(req.headers['x-ns-ekey'], 'base64')
  const iv = Buffer.from(req.headers['x-ns-iv'], 'base64')
  const decipher = crypto.createDecipheriv('aes-256-cbc', ekey, iv)
  let decrypted = decipher.update(encrypt, 'base64', 'utf8')
  decrypted += decipher.final('utf8')
  console.log(decrypted)

  // 构造回复消息
  const response = {
    msgtype: JSON.parse(decrypted).msgtype,
    ... // 其他自定义属性
  }
  const responseText = JSON.stringify(response)
  const responseKey = 'your_response_key'
  ...
})

4.2:加密回复密文

使用步骤3.3中获取到的AES-CBC密钥和IV,对回复消息进行加密处理,得到回复密文。

app.post('/', (req, res) => {
  const encrypt = req.body.encrypt
  const signature = req.headers['x-ns-signature']

  // 验证签名是否正确
  const token = 'your_token'
  const timestamp = req.headers['x-ns-timestamp']
  const plainText = token + timestamp + encrypt
  const hmac = crypto.createHmac('sha256', token)
  hmac.update(plainText)
  if (signature !== hmac.digest('hex')) {
    res.status(401).send('signature invalid')
    return
  }

  // 解密消息体
  const ekey = Buffer.from(req.headers['x-ns-ekey'], 'base64')
  const iv = Buffer.from(req.headers['x-ns-iv'], 'base64')
  const decipher = crypto.createDecipheriv('aes-256-cbc', ekey, iv)
  let decrypted = decipher.update(encrypt, 'base64', 'utf8')
  decrypted += decipher.final('utf8')
  console.log(decrypted)

  // 构造回复消息
  const response = {
    msgtype: JSON.parse(decrypted).msgtype,
    ... // 其他自定义属性
  }
  const responseText = JSON.stringify(response)
  const responseKey = 'your_response_key'

  // 加密回复消息
  const responseIv = crypto.randomBytes(16)
  const ekey2 = crypto.createHash('sha256').update(responseKey).digest().slice(0, 32)
  const cipher = crypto.createCipheriv('aes-256-cbc', ekey2, responseIv)
  let encrypted = cipher.update(responseText, 'utf8', 'base64')
  encrypted += cipher.final('base64')

  res.header('Content-Type', 'application/json')
  res.header('x-ns-ekey', Buffer.from(ekey2).toString('base64'))
  res.header('x-ns-iv', responseIv.toString('base64'))
  res.header('x-ns-responsekey', 'your_response_key')
  res.send({ encrypt: encrypted })
})

最终得到的回复消息密文保存在变量encrypted中,可以将其直接返回到钉钉服务器。

示例

下面是两个钉钉回调接口的示例,可供参考:

示例1:获取用户信息

请求

POST /user/get HTTP/1.1
Host: your_callback_url
x-ns-signature: 1ca36f3b164901f28a5e83aa4fced54acc73e2beabcedf9ded100bf682588bbc
x-ns-timestamp: 1580469492913
Content-Type: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 264

{
    "encrypt": "rkoZgNlKv...TBlxdFxmlF/S452J1Y4LoJQb24="
}

其中encrypt参数为加密后的消息体。

响应

HTTP/1.1 200 OK
Content-Type: application/json
x-ns-ekey: RoTyMNawUTwWHGQBNSz1GfWb+3qGOU6hJo57eOhy/vA=
x-ns-iv: OQtLErqQyKK+bl4b+1b1Tg==
x-ns-responsekey: your_response_key

{
    "encrypt": "lpikvT7NVJYW+...5U+IKTn5UPNsDD04g1qXlw=="
}

其中encrypt参数为加密后的响应消息体。

示例2:更新部门信息

请求

POST /department/update HTTP/1.1
Host: your_callback_url
x-ns-signature: 1ca36f3b164901f28a5e83aa4fced54acc73e2beabcedf9ded100bf682588bbc
x-ns-timestamp: 1580469492913
Content-Type: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 264

{
    "encrypt": "rkoZgNlKv...TBlxdFxmlF/S452J1Y4LoJQb24="
}

其中encrypt参数为加密后的消息体。

响应

HTTP/1.1 200 OK
Content-Type: application/json
x-ns-ekey: RoTyMNawUTwWHGQBNSz1GfWb+3qGOU6hJo57eOhy/vA=
x-ns-iv: OQtLErqQyKK+bl4b+1b1Tg==
x-ns-responsekey: your_response_key

{
    "encrypt": "lpikvT7NVJYW+...5U+IKTn5UPNsDD04g1qXlw=="
}

其中encrypt参数为加密后的响应消息体。

总结

本文介绍了如何使用Node.js开发钉钉回调接口的AES-CBC加解密功能,主要包括解析HTTP请求、检测签名正确性、解密回调消息体、加密回复消息体等步骤。

在实际开发中,需要注意检测签名的正确性,以保证消息能够安全可靠地在客户端和服务端之间传递。另外,需要注意加解密所使用的算法和密钥,这些信息需要保密存储,避免泄漏给攻击者。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:基于NodeJS开发钉钉回调接口实现AES-CBC加解密 - Python技术站

(0)
上一篇 2023年6月8日
下一篇 2023年6月8日

相关文章

  • NodeJs Express路由使用流程解析

    下面是关于Node.js Express路由使用流程的完整攻略。 什么是路由? 路由是Web应用中控制URI(或称为URL)请求的一部分。它是基于URL和HTTP方法(如GET、POST、PUT和DELETE)来选择一个处理程序(handler)。 在Node.js中,我们可以使用Express框架来构建Web应用程序,Express可以让我们很方便地处理H…

    node js 2023年6月8日
    00
  • node.js中的fs.exists方法使用说明

    当然,下面我会给您详细讲解“node.js中的fs.exists方法使用说明”的完整攻略: 简介 在 Node.js 中,fs 模块是用于操作文件的API模块。其中 fs.exists 方法用于判断指定路径是否存在。但是需要注意的是,fs.exists 方法已经在Node.js v10.0版本中被废弃了,因此在使用时要改用更加稳定的 fs.stat 方法来替…

    node js 2023年6月8日
    00
  • Ajax 的初步实现(使用vscode+node.js+express框架)

    下面是详细讲解“Ajax 的初步实现(使用vscode+node.js+express框架)”的完整攻略: 1. 简介 Ajax (Asynchronous JavaScript and XML) 是在不需要重新加载整个页面的情况下,能够更新部分页面的技术。本篇教程将介绍如何使用 vscode、node.js 和 express 框架实现 Ajax 功能。 …

    node js 2023年6月8日
    00
  • node.js中的buffer.Buffer.byteLength方法使用说明

    让我来讲解一下“node.js中的buffer.Buffer.byteLength方法使用说明”的攻略。 一、Buffer.byteLength方法的定义与作用 Buffer.byteLength(string, [encoding])方法是node.js中Buffer构造函数的一个实例方法,用于返回一个字符串的字节长度。在计算字符串的字节长度时,可以指定字…

    node js 2023年6月8日
    00
  • js 对象使用的小技巧实例分析

    下面为你详细讲解“js 对象使用的小技巧实例分析”的完整攻略。 1. 对象的创建与赋值 对象有多种创建方式,包括字面量语法、构造函数以及 Object.create() 方法等。其中最常用的是字面量语法,具体示例如下: let person = { name: "张三", age: 18, gender: "male"…

    node js 2023年6月8日
    00
  • node.js中的path.isAbsolute方法使用说明

    当你在使用Node.js处理文件路径时,你可以使用 path 模块提供的 isAbsolute() 方法来判断一个路径是否为绝对路径。 方法语法 path.isAbsolute(path) 其中,path 是需要判断的路径字符串。 该方法返回一个布尔值,如果某个路径是绝对路径,则返回 true,否则返回 false。 方法示例 下面是两个示例来说明 path…

    node js 2023年6月8日
    00
  • JavaScript中的垃圾回收与内存泄漏示例详解

    JavaScript中的垃圾回收与内存泄漏示例详解 垃圾回收 JavaScript是一种高级语言,它使用垃圾回收机制来管理内存。垃圾回收机制是一种自动化的功能,可以检测和删除不再使用的对象,从而释放占用的内存。 在JavaScript中,垃圾回收机制有两种:标记清除和引用计数。标记清除是JavaScript引擎中最常用的垃圾回收机制,它遍历所有的对象并标记它…

    node js 2023年6月8日
    00
  • NodeJS远程代码执行

    NodeJS远程代码执行是指攻击者通过网络将恶意代码传递到目标服务器上,并执行该代码。这种攻击方式往往能够导致服务器系统的完全崩溃或者数据泄露等严重后果,因此需要我们注意和提高防御能力。 下面是远程代码执行的攻击途径和防御措施: 攻击途径 由于网络协议漏洞或脆弱性的存在 通过注入不受信任或者非法内容到网络请求中 通过渗透 web 程序环境中的代码脆弱性,绕过…

    node js 2023年6月8日
    00
合作推广
合作推广
分享本页
返回顶部