☰
drawio 中 CryptoJS AES 加密裁剪包(aes.min.js 4.2.0)的构建、安全升级与实时协作集成解析
2026/9/26 19:04:53 网站建设 项目流程
  • 前端
  • 图形学

【免费下载链接】drawio

draw.io is a JavaScript, client-side editor for general diagramming.

项目地址:https://gitcode.com/gh_mirrors/dr/drawio
点击查看免费下载

本指南围绕 drawio 仓库中 src/main/webapp/js/cryptojs/aes.min.js 及其 README 展开,说明这份"裁剪版 CryptoJS 4.2.0 rollup"的来龙去脉:如何从 npm 包手工打包、drawio 用它加密实时协作(realtime collaboration)消息的哪些环节、为什么从 3.1.2 升级到 4.2.0,以及升级后对线上格式兼容性、安全边界和构建流程的影响。读完你将掌握该文件的构建命令、DrawioFileSync中 AES/MD5 的调用链、CSPRNG 探测与失败关闭(fail-closed)机制,以及替换/升级该库时需要注意的验收清单。

1. aes.min.js 是什么:一份被"裁剪"的 CryptoJS 4.2.0

aes.min.js并不是 CryptoJS 的完整发行版,而是一份Trimmed CryptoJS 4.2.0 rollup,只包含六个模块,按依赖顺序拼接后压缩:

core + enc-base64 + md5 + evpkdf + cipher-core + aes

这个模块清单并非 drawio 自己拍脑袋定的,而是CryptoJS 上游为aes.js声明的依赖列表。由于 CryptoJS 4.x 移除了 3.x 时代内置的rollups/目录,drawio 只能从 npm 包手工拼接出对应 rollup,再交给 Closure Compiler 做压缩。

值得强调的是,这份裁剪是"克制的"——README 明确列出没有包含的内容:

  • 没有 SHA / HMAC / PBKDF2 / RC4 / Rabbit / DES 等其余算法;
  • 没有 CBC 之外的密码模式;
  • 没有 Pkcs7 之外的填充方式。

也就是说,这个文件只为满足 drawio 实时协作场景下"一个 AES-CBC + OpenSSL 口令 KDF + MD5"的最小需求而存在,避免了把整个 CryptoJS 全家桶塞进前端包体。

2. 从 npm 包手工构建 rollup:完整命令

README 给出了可复现的构建流程(在本地临时目录执行,需要 JDK 环境运行 Closure Compiler):

# 1) 拉取指定版本的 crypto-js npm 包并解包 npm pack crypto-js@<version> && tar xzf crypto-js-<version>.tgz # 2) 按上游 aes.js 的依赖顺序拼接六个模块文件,生成 rollup.js for f in core enc-base64 md5 evpkdf cipher-core aes; do cat package/$f.js; echo; done > rollup.js # 3) 用 Closure Compiler(SIMPLE_OPTIMIZATIONS、ES5 输入输出)压缩 java -jar etc/build/compiler.jar --compilation_level SIMPLE_OPTIMIZATIONS \ --language_in ECMASCRIPT5 --language_out ECMASCRIPT5 \ --js rollup.js --js_output_file rollup.min.js

产出rollup.min.js之后,再从当前仓库的aes.min.js文件头复制署名头(attribution header)并更新版本号即可。README 特别提醒:如果未来 CryptoJS 上游改变了aes.js的依赖列表,应当跟随新的依赖清单重新裁剪,而不是沿用本文件这份清单——这说明该 rollup 的模块集合是与上游声明强绑定的。

从产物内容可以印证上述过程:aes.min.js文件头即为CryptoJS v4.2.0、MIT 许可及"Trimmed rollup: core + enc-base64 + md5 + evpkdf + cipher-core + aes"的说明,随后是各模块以 UMD 包装形式依次出现的压缩代码,包括enc.Hex、enc.Latin1、enc.Utf8、WordArray.random(RNG 探测逻辑)、MD5、EvpKDF、CBC模式、Pkcs7填充、OpenSSL格式/KDF,以及最后注册的v.AES。

3. drawio 用它做什么:AES 加密实时协作消息 + MD5 生成通道密钥

3.1 AES 加解密:DrawioFileSync.objectToString / stringToObject

drawio 对 CryptoJS 的主要使用点是DrawioFileSync的序列化/反序列化方法:

  • DrawioFileSync.prototype.objectToString(DrawioFileSync.js):
    • 先把消息对象JSON.stringify;
    • 自 PROTOCOL 7 起,JSON 直接交给 pakodeflateRaw压缩再 Base64(旧版本先 URI-encode 再压缩,JSON 引号和非 ASCII 字符会被膨胀最多 3 倍,编码耗时占大头;文件格式Graph.compress不受影响);
    • 若指定了maxLength且压缩后数据超限,返回null让调用方丢弃超大载荷(加密只会让数据变大,且 CryptoJS 的 base64 编码器会为超大载荷逐字符建数组而抛RangeError);
    • 当存在通道密钥this.key且CryptoJS可用时,调用CryptoJS.AES.encrypt(data, this.key).toString()完成加密。
  • DrawioFileSync.prototype.stringToObject(DrawioFileSync.js):
    • 先CryptoJS.AES.decrypt(data, this.key).toString(CryptoJS.enc.Utf8)解密;
    • 再 pakoinflateRaw解压,并兼容旧客户端写入的 URI-encoded 载荷(以%开头则decodeURIComponent);
    • 最后JSON.parse。

这两个方法同时被P2P 协作路径复用:在 P2PCollab.js 中,发送加密消息时直接构造msg = {bytes: sync.objectToString(msg), data: 'aes'},data: 'aes'是为了让旧服务器不会丢弃消息;接收侧则用sync.stringToObject(msg.bytes)还原(P2PCollab.js)。

3.2 字符串密钥触发 OpenSSL KDF(EvpKDF)

关键机制是:objectToString传入的this.key是字符串(而非 32 字节的二进制密钥)。CryptoJS 检测到字符串密钥时,会走口令派生路径——用 OpenSSL KDF(EvpKDF,默认 MD5 迭代 1 轮,见 rollup 内v.kdf.OpenSSL实现)从口令派生出 AES 密钥与 IV,其中8 字节 salt 来自WordArray.random。

由此引出本文后面要讲的安全边界:AES 密钥实际派生自通道密钥(channel key,即this.key,一个共享秘密),salt 只是公开的、按 OpenSSLSalted__格式明文前置在密文上的随机数。

3.3 MD5 用途:通道密钥派生

README 列出的 MD5 使用点与源码一一对应:

  • OneDriveFile.prototype.getChannelKey(OneDriveFile.js):以"创建时间戳 + 创建者用户 ID"拼接后取CryptoJS.MD5(...).toString()作为通道密钥;
  • nextcloud插件(plugins/nextcloud.js):以instanceId + id拼接取CryptoJS.MD5(...).toString();
  • README 提到的monday插件同样依赖该库的 MD5。

对照之下,Google Drive 的DriveFile.prototype.getChannelKey(DriveFile.js)不做哈希,而是直接读取存储的自定义属性key——README 特意点出这一差异,说明并非所有存储后端都走 MD5 派生。

4. 为什么是 4.2.0:一次由安全公告驱动的升级

4.1 3.1.2 的两个问题

升级前 drawio 用的是 CryptoJS 3.1.2,其问题有两个:

  1. 弱随机源:3.1.2 的WordArray.random用Math.random()播种(seed),被 Dependabot 标记为GHSA-rg76-677x-56q9/CVE-2026-71851(alert #310);
  2. 弱 KDF 默认值:3.1.2 存在弱 PBKDF2 默认值问题(issue #162),4.2.0 一并修复。

4.x 系列改用crypto.getRandomValues作为熵源,4.2.0 同时清掉了 #162。

4.2 真实暴露面:salt 碰撞而非密钥恢复

README 对风险边界做了精确界定,不夸大、也不轻描淡写:

  • 在 3.1.2 下,不安全的 RNG 只用于生成KDF salt;
  • 而 salt 在 OpenSSLSalted__格式下以明文形式前置在密文中,本来就是要公开的;
  • 因此实际风险是salt 碰撞导致同一通道密钥下 (key, IV) 复用,而不是 AES 密钥被恢复——因为 AES 密钥派生自通道密钥(共享秘密),与 RNG 无关。

换言之,升级 4.2.0 消除的是"同一共享密钥 + 弱随机 salt 造成 (key, IV) 复用"这类密码学使用风险。

4.3 确定性输出逐字节比对

在升级前,仓库对库产生的所有确定性输出都做了与 3.1.2 的逐字节比对,覆盖:

  • MD5;
  • Utf8 / Latin1 / Hex / Base64 编码器;
  • EvpKDF;
  • 固定 salt 下的 AES;
  • 双向跨版本解密(3.1.2 加密 → 4.2.0 解密,反之亦然)。

这保证了升级不是"换了把锁":任何依赖这些确定性输出的存量数据与在线会话都保持可读。

5. 行为差异与失败关闭(fail-closed)机制

5.1 没有原生 crypto 时:从静默降级到显式抛错

  • 3.1.2:在无原生加密(native crypto)的环境下,静默回退到Math.random();
  • 4.2.0:直接抛出Native crypto module could not be used to get secure random number.

需要澄清的是,只有加密受此影响——AES.decrypt和MD5从不触碰 RNG,解密与哈希在无 CSPRNG 环境下依然可用。

5.2 RNG 探测链与不安全上下文

4.2.0 的WordArray.random按以下顺序探测可用熵源(见aes.min.js中core模块的探测代码):

self.crypto → globalThis.crypto → window.msCrypto → global.crypto → require('crypto')

并且getRandomValues在不安全上下文(insecure contexts,即普通 HTTP 部署)下也可用,所以纯 HTTP 部署的 drawio 实例不受影响。

5.3 可被覆盖的window.crypto与isEncryptionAvailable兜底

虽然探测链覆盖广,README 仍指出一个可攻击点:window.crypto是一个可配置(configurable)的访问器属性,嵌入页面或插件可以用Object.defineProperty替换它,从而让前两步探测失效(进而使整个探测链失败)。

drawio 对此的兜底实现是DrawioFileSync.prototype.isEncryptionAvailable(DrawioFileSync.js):

  • 若没有通道密钥或没有 CryptoJS,直接返回true(本就不加密,谈不上风险);
  • 否则做一次CryptoJS.lib.WordArray.random(8)探测:成功则缓存encryptionAvailable = true,抛错则缓存false并通过EditorUi.logError记录(文件 ID 经哈希后才进日志,避免原始文件 ID 泄漏);
  • 结果每会话只探测一次,缓存于静态属性DrawioFileSync.encryptionAvailable,不重载页面不会改变。

探测结果为假时,objectToString会抛出No CSPRNG for realtime encryption(DrawioFileSync.js),实时同步被保持关闭。README 解释了为什么不能回退到明文:接收方仍持有通道密钥,会尝试解密而得到乱码,且明文载荷会落入缓存——因此"发不出去"比"发出去但谁都读不了"更安全。这就是典型的失败关闭设计:宁可让带密钥通道的实时协作不可用,也不发一条接收方读不了、缓存却能读的消息。

6. 构建集成与替换方式

6.1 逐字拼接,永不重编译

构建阶段,aes.min.js会被<concat>逐字拼进base.min.js,且不会再经过 Closure 编译。这意味着:

  • 文件内的 UMD 包装和require('crypto')引用不会经过 Closure 处理(避免被优化器改写破坏);
  • 替换该库就是一次直接的文件替换(straight file swap),不需要改动构建链。

开发模式下,Devel.js 通过mxscript(drawDevUrl + 'js/cryptojs/aes.min.js')直接按开发 URL 加载该文件;生产构建则把内容拼入base.min.js。此外该文件也出现在 service-worker.js 的预缓存清单中("js/cryptojs/aes.min.js"),说明其作为运行时静态资源参与 Service Worker 预缓存。

6.2 线上格式兼容:4.2.0 与 3.1.2 可互通

由于 EvpKDF / OpenSSL 格式在版本间未变,4.2.0 的 rollup 在线上是升级兼容的:同一协作会话里,一个 3.1.2 客户端和一个 4.2.0 客户端依然能互相读取对方的消息。这是第 4.3 节双向跨版本解密验证在真实场景中的意义。

6.3 升级/替换验收清单(可操作建议)

结合 README 与源码,替换或升级aes.min.js时应至少验证:

  1. 模块集合:确认新版本aes.js的依赖声明是否仍是core, enc-base64, md5, evpkdf, cipher-core, aes,有变化就跟随新清单;
  2. 确定性输出:MD5、四种编码器(Utf8/Latin1/Hex/Base64)、EvpKDF、固定 salt 的 AES 结果与旧版本逐字节一致;
  3. 双向互操作:旧版本加密 → 新版本解密、新版本加密 → 旧版本解密均通过;
  4. CSPRNG 行为:确认无原生 crypto 时抛错、有getRandomValues(含 HTTP 不安全上下文)时可用,并确认isEncryptionAvailable探测/缓存逻辑仍能覆盖;
  5. 构建产物:替换后检查base.min.js中拼接内容、service-worker.js预缓存清单与开发模式Devel.js加载路径是否同步。

7. 小结

aes.min.js是 drawio 实时协作加密链路中一个"小而关键"的组件:它以裁剪 rollup 的方式只引入 CryptoJS 的 AES-CBC(OpenSSL KDF 口令派生)与 MD5,为DrawioFileSync的消息加密和OneDriveFile/nextcloud等后端的通道密钥派生提供基础;升级到 4.2.0 修复了 3.1.2 弱随机源(Math.random())与弱 KDF 默认值问题,同时通过逐字节比对保证线上格式兼容;而isEncryptionAvailable的失败关闭设计则确保在 CSPRNG 被嵌入页或插件破坏时,实时协作宁可不启动也不发送任何接收方无法解密的消息。理解这份文件的构建命令、使用点与安全边界,是排查 drawio 实时同步加密问题、或在其基础上做定制升级的前提。

  • 前端
  • 图形学

【免费下载链接】drawio

draw.io is a JavaScript, client-side editor for general diagramming.

项目地址:https://gitcode.com/gh_mirrors/dr/drawio
点击查看免费下载
上一篇:Spotify下载器终极指南:快速免费下载Spotify音乐并保存完整元数据
下一篇:三步解锁百度网盘高速下载:告别龟速,拥抱光速

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询