由nodejs升级引起的构建错误:0308010C:digital envelope routines::unsupported

12,851 阅读8分钟

前言

最近,发现自己电脑上的nodejs版本居然还是14+,而最新的已经到了18+,由于并没有使用任何版本管理工具,于是直接升级到了最新的版本(v18.14.1),然后悲剧就发生了。
电脑里以往的绝大部分的Vue和React项目,在执行脚本构建命令如 npm run devnpm run build 时,都出现同样的错误,导致构建失败,并且报类似下面的错误:

ssl-error.png

查找原因

出现了问题,自然要想办法解决。
在网上搜索了一圈,发现该问题早已出现,一般描述的大致原因就是:当 nodejs 升级到17+版本以后,开始支持 OpenSSL 3.0,而 OpenSSL 3.0 对各种摘要算法做了更严格的限制,可能会导致一些程序运行错误。

webpack源码与配置

但其实,并不是所有的项目都会出现,比如使用了最新版本的 webpack 工具的项目就能够正常运行,所以得搞清楚其中的具体原因,到底是哪些地方影响了项目运行。
而要搞清原因,还得回到错误信息里,仔细查看上图里的错误信息,可以发现:Error 出现在了 webpack 包的 lib/util/createHash.js 代码文件,查看该文件,可知主要是用于创建hash值,这部分的核心代码如下所示:

switch (algorithm) {
  case 'debug':
    return new DebugHash()
  default:
    return new BulkUpdateDecorator(require('crypto').createHash(algorithm))
}

从上面的代码,我们可以发现:webpackhash 使用了 nodejs 中的 crypto 加密模块,根据传入的算法名称创建 Hash 对象。

我们直接代码测试,进一步发现当前版本的 webpack,这里的参数 algorithm 算法都是使用的 md4,搜索 webpack 源码还能发现很多地方直接写死了 md4 算法名:

md4.png

而查看webpack文档,也有类似的介绍。
webpack 的源码里,我们还找到了一个配置属性:output.hashFunction,在webpack配置说明文档里也找到该属性的说明:

hashFunction.png

如上图所示,该属性定义webpack可使用的散列算法,默认也是 md4

查看nodejs的crypto模块

在回到错误源码上,主要是这句代码出错:

require('crypto').createHash(algorithm)

上面的代码里调用了 crypto 模块的 createHash 方法,我们查看该方法的文档,可以发现有这样一段话:

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

可见 webpack 源码里的 algorithm 就表示 OpenSSL 能够支持的可用算法,——在源码注释里也有解释。 而 md4 显然也是其中一种算法,但这里却创建失败了。

到这里,我们已能发现问题所在:crypto 模块的 createHash 方法在使用 md4 算法创建 Hash 对象时失败,无法创建,因此报错。

为什么会报错呢?继续寻找答案,我们直接在当前node环境下调用该方法:

require('crypto').createHash('md4')

立马报错:

node-error.png

错误信息和项目中的类似,出现不支持 unsupported,这里还出现了 opensslErrorStack 错误栈,初始化失败,网上提到 openssl 3.0 不支持 md4 算法看上去是正确的,但为什么不支持呢?

查看openssl问题

我们先了解下这里提到的 openssl 是什么?OpenSSL 是一个强大的安全套接字层密码库的开源项目,它提供了一个功能丰富 openssl 安全工具包,拥有多种加密算法,能处理对称与非对称算法密钥、数字证书编解码等等。

而上文也提到在 nodejs 的加密模块中,生成hash就借助了 openssl 等能力,其中通过命令 openssl list -digest-algorithms 可以查看当前平台支持哪些算法,在命令行执行后,可以发现有几十种算法,而 md4 也名列其中:

openssl-md4.png

那又为什么会创建失败呢,继续查看 openssl 3.0 相关的文档,经过寻找,终于发现问题的细节:

  • OpenSSL 中提供了5个 Provider (它表示算法实现的容器),我们需要了解如下两个:
    • default:默认,提供了所有标准的内置算法,如果没明确指定则将使用默认算法容器;
    • legacy:遗留,已不常用或者被反对使用的算法,正常情况下不可用,除非显式指定使用 legacy 容器;
  • openssl 3.0 正常都是使用 default 容器提供的算法,而 md4 却位于 legacy 算法容器中,所以正常情况下并不支持 md4 算法,因此导致错误。

legacy 算法容器中,包含 MD2、MD4、MDC2、RMD160、CAST5、BF、IDEA、SEED、RC2、RC4、RC5 和 DES 等算法。

解决方法

升级webpack

从前文可知,目前环境出现的问题,主要是 webpack 的使用了 md4 算法导致错误,可见还得在 webpack 工具上下功夫。

注意:只改变 webpack 的配置属性 hashFunction 值为其他算法,无法解决该问题,是因为 webpack 中很多地方写死了 md4 字符串。

如果我们把 webpack 进行升级到较新的版本,就可以解决此类问题。
据查,自 5.61.0 版本开始,webpack 就已解决了这个问题,可以查看同样创建hash的源码,新修改的如下所示:

switch (algorithm) {
  //...
  case "md4":
    if (createMd4 === undefined) {
      createMd4 = require("./hash/md4");
      if (BatchedHash === undefined) {
        BatchedHash = require("./hash/BatchedHash");
      }
    }
    return new BatchedHash(createMd4());
  // ...
  default:
    if (crypto === undefined) crypto = require("crypto");
    return new BulkUpdateDecorator(
      () => crypto.createHash(algorithm),
      algorithm
    );
}

以上代码可看出,针对 md4 已经做了专门处理,不再直接使用 crypto 模块创建,而是新建 md4 算法。

针对使用 @vue/cli-servicereact-scripts 工具构建的项目,由于还是依托 webpack,所以对应工具进行升级即可,其中:

  • @vue/cli-service5.0.1 版本开始,已经支持 webpack@5.61.0
  • react-scripts5.0.0 版本开始,已经支持 webpack@5.61.0

但是,由于升级构建工具,可能随之带来很多不可知的问题,不少依赖包都会因为不支持而报错,升级带来的成本并不小,所以很多人并不愿意升级构建工具。

当然,我们也可以自己修改 webpack 源码解决问题,只需要再创建hash对象的地方拦截 md4 换种算法就可以了,但还需要更改另外一个依赖包 terser-webpack-plugin,这个库里也有 md4

提供legacy容器环境

根据上文提到的错误原因,可知是由于我们使用了 openssllegacy 算法容器里的算法 md4,并不被标准默认容器支持,所以需要显式地指定算法容器环境。
指定 legacy 算法容器,使用 --openssl-legacy-provider,网上提供的很多解决方案,都是基于这个原则去做的,只不过都不交代具体的原因。

增加系统环境变量

Windows系统下,可在系统环境变量里添加一个新的变量:NODE_OPTIONS,值为:--openssl-legacy-provider
如下图所示:

系统变量.png

需要注意的是,
通过设置系统变量,我们只能解决 Git Bash 在系统右键启动时的构建命令正确执行,或者直接启动 git-bash.exe 软件,即单独的命令窗口能有效解决问题:

右键git-bash.png

而在VSCode中添加的 git-bash 工具,却并不起作用,即在下面的窗口下执行构建命令时仍然会报错:

vscode-terminal.png

我并没有找到VSCode设置git-bash时有啥特殊的配置,导致两者之间有差异,如果有人了解,希望可以在评论里告知。

在package.json中添加设置

一个能在新版本nodejs中完整解决问题的方案,就是在 package.json 配置的脚本命令中,增加对应的设置,如下所示:

"dev": "set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
"build": "set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build",

即增加 set NODE_OPTIONS=--openssl-legacy-provider 命令设置,就可以在 Git Bash 的右键命令以及VSCode中都能正常执行构建命令了。

注意,如果在 package.json 中添加脚本命令设置,会导致使用老版本的同事,无法执行脚本,因为老版本 nodejs 并不支持设置 --openssl-legacy-provider 属性值,将有如下提示并中断构建执行:

node: --openssl-legacy-provider is not allowed in NODE_OPTIONS

nodejs多版本管理或者版本降级

另外还有一种方案,就是不要使用最新版本的 nodejs,降级后重新使用V16的版本,低版本的 nodejs 可以保障项目正常运行。
或者,还可以使用 nodejs 的多版本管理工具,能够自由切换不同的版本,同样满足要求。关于 nodejs 版本管理的简单介绍,可查看博文 node 和 npm 如何进行版本升级

但是,已经使用了新版本的nodejs,再去用老版本的,也多少有些不那么回事。

最后

本文详细描述了 nodejs 升级后引起的前端老旧项目中构建工具生成hash报错的问题(0308010C:digital envelope routines::unsupported),以及给出的几种解决方案。
这几种解决方案各自都存在一些不完美的地方,但都能解决当前构建错误,可以根据自己的实际需求来确定使用哪种方案。