返回

React Native Expo: 解决'crypto not found'错误

javascript

解决 React Native Expo 中的 "crypto not found" 错误

在 React Native Expo 应用开发中,使用诸如 crypto 这类 Node.js 核心模块时,经常会遇到 ReferenceError: Property 'crypto' doesn't exist 这样的错误。 这个错误一般在直接使用 import * as crypto from 'crypto' 时发生,并且多数情况出现在 iOS 模拟器或者真机上使用 Xcode 运行时。该问题的核心原因在于,Expo 默认的 JavaScript 引擎(Hermes)并不直接支持 Node.js 的核心模块,这与 Node.js 环境本身不同。

原因分析

  • 运行环境差异: 当你通过 npm run ios 启动应用时,Expo 通常会使用 Metro bundler 将你的 JavaScript 代码打包,而 Metro 知道如何处理 Node.js 模块的引入。但是当通过 Xcode 直接运行时,则可能跳过部分流程或使用不同的配置。
  • Hermes 不兼容: Expo 应用通常在 iOS 设备上运行在 Hermes 引擎上,Hermes 不具备直接处理 crypto 模块的能力。 crypto 模块本身依赖于 Node.js 的底层实现。
  • 模块导入方式: 直接 import * as crypto from 'crypto' 通常适用于 Node.js 环境,但不适用于浏览器或者 React Native 的环境。

解决方案

理解错误产生的原因,我们可以通过以下几种方法解决问题:

方案一:使用 react-native-get-random-values 和 crypto-js

这个方法不直接依赖 Node.js 核心模块,而是使用 JavaScript 实现的替代库。 它在浏览器和 React Native 中工作良好。

操作步骤:

  1. 安装依赖: 使用 npm 或 yarn 安装 react-native-get-random-valuescrypto-js:
npm install react-native-get-random-values crypto-js
  1. 在应用入口处引入 react-native-get-random-values: 在应用的最顶层文件 (例如:App.js 或者 index.js) 的首行,添加引入代码,它会在内部创建一个全局 crypto 对象供使用。 避免重复导入。
  import 'react-native-get-random-values';
  //你的其余的import 代码....
  1. 修改 crypto 相关代码 : 把原来导入的 import * as crypto from 'crypto'; 注释掉, 然后改用 crypto-js 进行加密处理:
   // import * as crypto from 'crypto'; //删除此行
   import CryptoJS from 'crypto-js';
 
    const encrypt = (payload: DeviceVerificationPayload) => {
       const algorithm = 'aes-256-cbc';
       const encryptionAlgoKey = 'Key';
       const encryptionKey = base64ToUtf8(encryptionAlgoKey);
       const key = CryptoJS.SHA256(encryptionKey);
       const iv = CryptoJS.lib.WordArray.random(16); // 使用 crypto-js 生成随机IV
       const stringifiedPayload = JSON.stringify(payload);
       const cipher = CryptoJS.AES.encrypt(stringifiedPayload, key, {
         iv: iv,
         mode: CryptoJS.mode.CBC
       });
      return cipher.toString();
    };

    const base64ToUtf8 = (str:string): string =>{
       return CryptoJS.enc.Base64.parse(str).toString(CryptoJS.enc.Utf8);
     };
 ```
**安全提示:**  使用  `crypto-js` 时务必注意安全配置, 确保加密过程符合你的需求。并且密钥的管理也需额外重视, 可以采用环境变量或者其他的密钥安全管理方式.
4.  **重新运行应用** : 使用 `npm run ios` 重新构建并运行应用程序,验证问题是否解决。如果Xcode还报一样的错误, 确保删掉你本地app的安装, 清空缓存,然后再在Xcode重新构建运行应用.

**原理说明:**   `react-native-get-random-values` 主要功能是填充全局 `crypto` 对象提供加密需要的随机值功能,`crypto-js` 是纯JavaScript 版本的加密库,可以在 React Native  环境直接使用. 结合使用避免了直接依赖 Node.js 核心模块.

#### 方案二:使用 expo-crypto 库

`expo-crypto` 是 Expo 官方提供的一个封装加密功能的库,能够安全且便捷地在 React Native 应用中使用。这个方法需要移除旧的引入方式并且使用 expo 的版本。

**操作步骤:** 
 1. **安装依赖:** 
```bash
  npx expo install expo-crypto
  1. 修改 crypto 相关代码 : 移除原来的引入并改成如下使用 expo 版本,这里需要自行修改适配expo-crypto 的api。

     // import * as crypto from 'crypto'; 删除此行
     import * as Crypto from 'expo-crypto';
    
      const encrypt = async (payload: DeviceVerificationPayload) => {
         const algorithm = 'aes-256-cbc';
         const encryptionAlgoKey = 'Key';
         const encryptionKey = base64ToUtf8(encryptionAlgoKey);
         const key = await Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256,encryptionKey)
         const iv = await Crypto.getRandomBytesAsync(16) ;
          const stringifiedPayload = JSON.stringify(payload);
    
         const bufferPayload = new TextEncoder().encode(stringifiedPayload);
         const encrypted  = await Crypto.cryptoEncryptAsync(
               {
               algorithm,
               key:  new Uint8Array(Array.from(key)).buffer,
               iv : new Uint8Array(Array.from(iv)).buffer ,
             } , 
               new Uint8Array(Array.from(bufferPayload)).buffer
         )
         return Buffer.from(encrypted).toString('hex')
       };
    
      const base64ToUtf8 = (str:string): string =>{
          return  Crypto.Base64.decode(str)
       };
    
    

    注意 : expo-crypto 的 api 与 Node.js 核心模块的 api 有一定差别。需要根据文档来使用。 expo-crypto 中所有的 async 类型的函数, 请记得使用 async await 。同时一些方法的入参,要记得使用对应的 ArrayBuffer 进行数据传递。

  2. 重新运行应用: 重新构建并运行应用程序, 验证问题是否已经解决。 同样,请确保清空本地app 的安装缓存之后再进行测试。

    原理说明: expo-crypto 是 Expo 提供的安全加密库,在 Expo 环境有更好的兼容性. 它提供了多种加密方法和实用工具,在实际项目中更值得优先考虑。

其他考虑

  • 模块冲突: 有时依赖之间可能存在版本冲突导致此问题。可以尝试更新或者降级相关依赖,或者清空 node_modules,重新安装依赖来解决。

  • 调试困难: 如果调试过程中仍然出现错误,可利用 React Native 调试工具以及 Xcode 日志进行逐步排查,理清代码逻辑和运行步骤,精准定位错误源头。

以上两种方案均能解决 "crypto not found" 错误,你可以根据项目的具体情况以及对第三方库的偏好进行选择。 始终保持库版本的最新,定期审查更新文档,这样能避免很多未知的错误和风险。