nodejs升级引起的构建错误:0308010C:digital envelope routines::unsupported
作者:mmseoamin日期:2023-12-25

文章目录

    • 查找原因
      • webpack源码与配置
      • 查看nodejs的crypto模块
      • 查看openssl问题
      • 解决方法
        • 升级webpack
        • 提供legacy容器环境
          • 增加系统环境变量
          • 在package.json中添加设置
          • nodejs版本降级或多版本管理
          • 最后

            最近,发现自己电脑上的nodejs版本居然还是14+,而最新的已经到了18+,由于并没有使用任何版本管理工具,于是直接升级到了最新的版本(v18.14.1),然后悲剧就发生了。

            电脑里以往的绝大部分的Vue和React项目,在执行脚本构建命令如 npm run dev 或 npm run build 时,都出现同样的错误,导致构建失败,并且报类似下面的错误:

            在这里插入图片描述

            查找原因

            出现了问题,自然要想办法解决。

            在网上搜索了一圈,发现该问题早已出现,一般描述的大致原因就是:当 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))
            }
            

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

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

            在这里插入图片描述

            而查看webpack文档,也有类似的介绍。

            从 webpack 的源码里,我们还找到了一个配置属性:output.hashFunction,在webpack配置说明文档里也找到该属性的说明:

            在这里插入图片描述

            如上图所示,该属性定义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')
            

            立马报错:

            在这里插入图片描述

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

            查看openssl问题

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

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

            在这里插入图片描述

            那又为什么会创建失败呢,继续查看 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-service 和 react-scripts 工具构建的项目,由于还是依托 webpack,所以对应工具进行升级即可,其中:

                • @vue/cli-service 在 5.0.1 版本开始,已经支持 webpack@5.61.0
                • react-scripts 在 5.0.0 版本开始,已经支持 webpack@5.61.0

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

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

                  提供legacy容器环境

                  根据上文提到的错误原因,可知是由于我们使用了 openssl 的 legacy 算法容器里的算法 md4,并不被标准默认容器支持,所以需要显式指定算法容器环境。

                  指定 legacy 算法容器,使用 --openssl-legacy-provider,网上提供的很多解决方案,都是基于这个原则去做的,只不过都不交代具体的原因。

                  增加系统环境变量

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

                  如下图所示:

                  在这里插入图片描述

                  需要注意的是,

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

                  在这里插入图片描述

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

                  在这里插入图片描述

                  我并没有找到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),以及给出的几种解决方案。

                  这几种解决方案各自都存在一些不完美的地方,但都能解决当前构建错误,可以根据自己的实际需求来确定使用哪种方案。