解决Node升级至16.20引起的webpack报错:0308010C:digital envelope routines::unsupported

582 阅读4分钟

背景

有一个项目从 node14 升级到 node16,启动时 webpack 报错 0308010C:digital envelope routines::unsupported,详细如下:

image.png

经我的同事测试,发现在 16.0 下是可以正常启动的,但在 16.20 不能运行会报错,他简单的定下结论只能运行在特定版本下,但没有更进一步找寻原因。

修复方案

先给方案,后面再给详细分析

方案一:设置环境变量 NODE_OPTIONS

# 先设置环境变量 NODE_OPTIONS(可以设置到全局的 profile 文件里去),再运行
export NODE_OPTIONS=--openssl-legacy-provider
npm run dev

# 或者运行时直接指定
NODE_OPTIONS=--openssl-legacy-provider npm run dev

# 可以直接放进 bash_profile 文件中去
# 如果当前 node 使用 openssl 3,则启用 legacy-provider
unset NODE_OPTIONS
[[ $(node -p "process.versions.openssl") > "3" ]] && export NODE_OPTIONS=--openssl-legacy-provider

# 又或是直接修改 package.json 中的 script,直接追加环境变量
# 依据自己的平台设置环境变量,又或是使用 cross-env 这样的库来设置跨平台环境变量
"scripts": {
    "dev": "NODE_OPTIONS=--openssl-legacy-provider nuxt",
    "build": "NODE_OPTIONS=--openssl-legacy-provider nuxt build",
    ...
}

推荐修改 package.json 的方式,能够让团队中的所有人共享设置。

方案二:升级 webpack 到 5.56.0

老版本的 webpack,代码内部写死了很多 md4,哪怕后面的某个版本允许自行设置 output.hashFunction,但还是有很多其他地方在使用 md4。升级到版本 5.56.0 之后,webpack 内部对 md4 做了包装,不再只依赖 nodejscrypto 模块,可以实现对老加密方法的兼容。

分析

分析错误堆栈

按照错误输出,死在了 node_modules/webpack/lib/util/createHash.js 第 135 行,require("crypto").createHash(algorithm)。打印 algorithm 这个变量,值为 md4

image.png

crypto 是一个 nodejs 内置的模块,我们直接在 nodejs 运行环境中,测试 require("crypto").createHash("md4"),提示报错。用更为熟悉的 md5 做测试是可行的。至此,确认问题确实是 md4 不被支持。

image.png

阅读 nodejs 官方文档

按照 nodejs 文档中,对于 crypto.createHash 的描述:

image.png

该算法取决于平台上 OpenSSL 版本支持的可用算法。 例如“sha256”、“sha512”等。在 OpenSSL 的最新版本中, openssl list -digest-algorithms 将显示可用的摘要算法。

文档中还有一段描述:

image.png

一些具有已知弱点且在实践中无关紧要的算法只能通过 legacy provider,默认情况下不启用。

image.png

可以使用 --openssl-legacy-provider 来使得 OpenSSL 3.0 启用 legacy provider。nodejs v17.0.0 和 v16.17.0 开始引入。

这也能解释为何在 nodejs 16.0.0 下正常,但升级到 16.20.0 报错。

验证结果

在启用 --openssl-legacy-provider 参数之后,不再报错: image.png

在运行 npm 命令时,这个参数要换种形式才能传给 node,比如方案一中的设置 NODE_OPTIONS 环境变量。

webpack 文档和代码

我的项目所使用的 webpack 4.46.0,报错是在创建 output.hashFunction

5.54.0webpack 允许设置 output.hashFunction,我们可以在 webpack 配置里自行设置为其他方法(比如md5、xxhash64),但这不能解决所有问题,还有其他地方写死了 md4。

image.png

5.61.0webpack 为 md4 提供了本地化实现,不再依靠 Openssl 3.0 默认不支持的 md4,至此,应该能彻底解决历史代码中写死的 md4。但这个版本是为 node17 提供的,在 16 下是否被支持我暂时没测试。

image.png

image.png

方案对比

方案二升级 webpack,但很可能你的项目使用的是 nuxt@vue/cli-service 这种内置了 webpack 的集成包,要升级 webpack 的版本,得同步去研究 nuxt 或者 vue-cli 的哪个版本使用了目标版本的 webpack,又或是还要解决其他一些联动的依赖,动静其实是比较大的。

方案一指定环境变量,操作简单,但可能为日后埋下祸根,万一日后 Openssl 再升级,彻底作废了某些加密函数,迟早还是要升级项目中的各个依赖的。

补充

在研究的过程中,发现我从 brew 安装的 node@16,使用的是 openssl 3,而从官网直接下载的 node 16.20.2,使用的是 openssl 1.1。

image.png image.png

再接着研究,才发现 brew 的 node@16,使用的编译选项指定使用 openssl 3 image.png

我们生产环境和测试环境,centos,如果从 yum 安装 node,最终是直接下载官方编译的版本,也会默认使用 openssl 1

image.png

至此,我没有去研究究竟哪个版本,官方的编译版本开始使用 openssl 3。现状是我的本地开发环境 brew 安装的 node@16 使用 openssl@3。将环境变量直接设置进 package.json,会导致生产环境出错,开发环境下,该环境变量还是设置在用户的 profile 最佳。