ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

React Native for OpenHarmony 实战:三方库 react-native-crypto-js 的鸿蒙化适配指南

React Native for OpenHarmony 实战:三方库 react-native-crypto-js 的鸿蒙化适配指南 react-native-crypto-js是crypto-js在 RN 生态里的一个常用入口包MD5、HMAC-MD5、AES 口令式加解密做本地数据摘要、接口签名、配置加密时会用到。它和需要把 iOS/Android 实现换成鸿蒙实现的那类库不一样——它是纯 JavaScript没有原生代码所以在鸿蒙上不需要原生适配。但这个交付版本有一个值得单独讲的点它确实改过 JS。上游 npm 包导出了HmacMD5可它一调用就抛TypeError—— 因为打包时把 HMAC 依赖的内部模块裁掉了。交付版用 CryptoJS 已有原语按 RFC 2104 把这段实现补齐了。所以本文要讲清四件事怎么判断一个库要不要原生适配、上游那个 bug 长什么样、交付版的补丁怎么验证、以及这个包实际支持哪些算法不能按 README 猜。环境准备本文不重复环境搭建步骤。RNOHReact Native for OpenHarmony开发环境的完整配置见官方开发者指南https://atomgit.com/CPF-RN/docs/blob/main/开发者指南/02-搭建准备/环境初始化.md一、先说结论不需要原生适配但需要一处 JS 修复项结论需要原生适配吗❌ 不需要无harmony/、无原生代码、无依赖、无harmony.autolinking需要 HAR / ohpm / autolinking / 权限吗❌ 都不需要需要 JS 层修复吗✅需要—— 上游 bundle 的HmacMD5一调用就抛TypeError不需要原生适配和不需要动代码是两件事。一个纯 JS 库也可能因为打包裁剪、平台分支、历史 bug 而在运行时不可用——这类问题不解决接进来照样跑不起来。二、判定过程三步判断一个 RN 库要不要鸿蒙原生适配这个方法在选库阶段就能用不用先装进来第一步看package.json有没有harmony字段npmview包名harmony--json有harmony.autolinkingohPackageName/etsPackageClassName/cppPackageClassName/cmakeLibraryTargetName那四个名字的一定是带原生实现的包必须走link-harmony ohpm hvigor。react-native-crypto-js的情况$npmview react-native-crypto-js version main dependencies--json{version:1.0.0,main:CryptoJS.js}# 无 harmony、无 dependencies第二步看仓库里有没有harmony/$gitclone https://atomgit.com/oh-react-native/react-native-crypto-js.git $lsreact-native-crypto-js CryptoJS.js package.json LICENSE README.OpenHarmony.md README.OpenHarmony_CN.md spec.json RN_react-native-crypto-js代码检查报告.md __tests__/没有harmony/、没有src/—— 全部实现就是根目录那一个CryptoJS.js124 行的压缩 bundle。第三步读代码里有没有原生调用或平台分支搜三个关键词就够了NativeModules/TurboModuleRegistry—— 有的话在调原生Platform.OS—— 有的话在按平台分叉行为requireNativeComponent—— 有的话是原生视图CryptoJS.js里一个都没有纯字符串与位运算。spec.json也把性质写明了{implementation:pure JavaScript; no native module or HAR required,upstreamCommit:f9bf05dbc6f92400f53204776ad968d20d305ac2}一个容易误判的点包里有android/或ios/目录不代表有原生实现。很多库把平台工程放进包里但实际不参与 RN 运行。判断要看有没有被 JS 侧真实调用——第一步的harmony.autolinking是最可靠的信号。三、上游那个 bug导出了却一调就炸先复现constCryptoJSrequire(react-native-crypto-js);// npm 上游 1.0.0typeofCryptoJS.HmacMD5;// function ← 看起来是有的CryptoJS.HmacMD5(message,secret);// TypeError: Cannot read properties of undefined (reading init)同一个 bundle 的 MD5 完全正常CryptoJS.MD5(abc).toString();// 900150983cd24fb0d6963f7d28e17f72所以问题只出在 HMAC 接线上游打包时把CryptoJS.algo.HMAC这类内部模块裁掉了却忘了把依赖它的HmacMD5helper 一起裁掉于是留下一个**导出了但一用就炸的 API**。这类问题为什么值得警惕因为它是检查存在性通不过的反面——你对typeof CryptoJS.HmacMD5 function的检查会通过甚至做静态检查、做类型定义都不会报错。只有真的调用一次才会暴露。四、交付版怎么修的交付版与 npm 上游做逐字节 diff差异只有两类上游 CryptoJS.js [16744 字节] SHA256 C31A3E861F66C1EE23C207B96540B9B5D813DDEFA4E8AF11D7CAE62EAE7A4DB8 交付 CryptoJS.js [17784 字节] SHA256 95E43C0117E3E6AADDA6CA43ED8F2BD86F7DD14F2E44589624D87D370280CE21 1 file changed, 27 insertions(), 9 deletions(-) # 9 行删除是版权注释去行尾空格新增的就是 18 行HmacMD5// The upstream bundle omits the internal HMAC module although it exports the// helper. Implement the documented MD5 HMAC using the bundled primitives.CryptoJS.HmacMD5function(message,secret){varWordArrayCryptoJS.lib.WordArray;varUtf8CryptoJS.enc.Utf8;varkeytypeofsecretstring?Utf8.parse(secret):secret;if(key.sigBytes64)keyCryptoJS.MD5(key);keykey.clone();key.clamp();varkeyWordskey.words.slice(0);while(keyWords.length16)keyWords.push(0);varinnerWordskeyWords.map(function(word){returnword^0x36363636;});varouterWordskeyWords.map(function(word){returnword^0x5c5c5c5c;});varmessageWordArraytypeofmessagestring?Utf8.parse(message):message;varinnerCryptoJS.MD5(WordArray.create(innerWords,64).concat(messageWordArray));returnCryptoJS.MD5(WordArray.create(outerWords,64).concat(inner));};这就是 RFC 2104 定义的 HMACHMAC(K, m) H( (K ⊕ opad) ‖ H( (K ⊕ ipad) ‖ m ) )对照着看每一行都在做什么代码RFC 2104 里的含义if (key.sigBytes 64) key CryptoJS.MD5(key)密钥长于分组长度时先做一次摘要MD5 的分组长度是 64 字节while (keyWords.length 16) keyWords.push(0)密钥补零到 64 字节16 个字word ^ 0x36363636ipad 0x36 重复word ^ 0x5c5c5c5copad 0x5c 重复内层MD5(ipad ‖ m)外层MD5(opad ‖ 内层)嵌套结构它没有另造算法而是复用 bundle 里已有的CryptoJS.MD5和WordArray——在一个被裁剪过的 bundle 里这是最小侵入的修法。补丁正确性怎么验补丁是手写的就必须独立验证。我用 Node 的crypto.createHmac(md5)逐例比对密钥长度0, 1, 3, 4, 5, 15, 16, 20, 63, 64, 65, 80, 128, 200, 4097 字节 消息 / a / message / Harmony RN / 鸿蒙 RN / 200 字节长消息 结果90/90 与 Node HMAC-MD5 一致特意覆盖了两个容易实现错的边界密钥长度 64 与 65—— 分组长度是 6464 字节不该被摘要、65 字节必须被摘要改错一边就会在 65 字节上露馅密钥长度 0—— 空密钥按标准补零。为什么不直接信那个新增的注释因为注释说的是用已有原语实现文档化的 MD5 HMAC——它是意图不是证据。90 组独立比对才是证据。五、能力边界不能按 README 猜测支持这个包有个很容易踩的预期差。上游 README 里列了 SHA 系列、PBKDF2、DES 等算法但这个 npm 包并没有导出它们。逐个探测 19 个名字名称上游交付MD5/HmacMD5functionfunctionAES/enc/lib/mode/pad/algoobjectobjectSHA1/SHA256/SHA512undefinedundefinedHmacSHA1/HmacSHA256/HmacSHA512undefinedundefinedPBKDF2undefinedundefinedDES/TripleDES/RC4/Rabbitundefinedundefined在 bundle 里搜关键词也印证了SHA256、PBKDF2、DES、TripleDES、HmacSHA出现次数都是 0而MD5出现 9 次、AES出现 2 次。所以这个包实际只有三个能力MD5、HmacMD5、AES。交付版因此在 README 里明确写了不能按 README 猜测支持并声明能力范围就是这三项。这是很实用的一条经验第三方库的 README 描述的是作者的意图或上游完整版的形态而你npm install装到的可能是裁剪过的子集。判断一个算法能不能用typeof一下比读 README 可靠。六、接入宿主零原生接线对照带harmony/的库这个库少了整整一排接线动作接线项带原生实现的库react-native-crypto-jsnpm installpackage.json加依赖需要需要这个躲不掉react-native link-harmony需要不需要工程级harmony/oh-package.json5加 HAR需要不需要模块级harmony/entry/oh-package.json5加 HAR需要不需要harmony/entry/src/main/ets/RNOHPackagesFactory.ets注册 Package需要不需要ohos.permission.*权限声明视库而定不需要metro.config.js的watchFolders需要本次需要一行原因见下实测一link-harmony跳过了它$ node_modules\.bin\react-native link-harmony --verbose ... info updated 4 file(s), linked 5 libraries, skipped 1 librarieslinked 5 libraries与加入它之前完全一样react-native-crypto-js不在列表里——因为它没有harmony.autolinking声明。实测二Metro 的重定向清单里也没有它[INFO] Redirected imports to 5 harmony-specific third-party package(s): [INFO] • 已接入的带鸿蒙实现的包 → 同名鸿蒙实现 …清单里没有react-native-crypto-js。注意这里不在清单里才是对的。Metro 的这个重定向是给有鸿蒙变体的包准备的把某个包名解析到它的鸿蒙实现纯 JS 库没有鸿蒙变体可重定向出现在清单里反而说明有问题。实测三模块级oh-package.json5一个字没动构建照样成功ohpm install --all0.45 秒。唯一需要的那一行以及它为什么可以省// metro.config.jswatchFolders:[...path.resolve(__dirname,../react-native-crypto-js),],这一行不是鸿蒙适配而是file:本地依赖的通病file:装进node_modules之后是链接Windows 上是 Junctionmetro 需要一个watchFolders条目才认得出它的真实目录。从 npm registry 装装成真目录就连这一行都不需要——真正的零接线。七、验证方法已知值向量 独立复算这个库的输出是确定性的MD5、HMAC-MD5以及用显式 keyIV 的 AES所以验证方式很明确逐字节对已知值。设备侧 100 项断言测试页把期望值放在cryptoVectors.json里由 PC 端 Node/OpenSSL 现场生成分四组跑组内容断言数结果AMD57 HmacMD545 AES-CBC 加密/解密12×276✅ 76/76BAES 口令模式往返、随机性、Salted__头、密文长度5✅ 5/5C19 个导出名的typeof与交付声明一致19✅ 19/19合计100✅100/100D上游 bundle vs 交付版对比不计入如实展示A 组的向量设计上刻意覆盖边界MD5空串、单字符、abc、中英文、1000 字节长文本HmacMD515 种密钥长度0 / 1 / 3 / 4 / 5 / 15 / 16 / 20 / 63 / 64 / 65 / 80 / 128 / 200 / 4097 字节× 3 种消息——把 HMAC 的块长边界64夹在中间AES-CBC128/192/256 位 × 4 种明文含空串、中文、100 字节多块加密与解密各断言一次AES 用显式 key IV而不是口令是因为口令模式每次都带随机 salt、密文不可复现显式 keyIV 才能拿到确定值去和 OpenSSL 比对。B 组口令模式与密文格式口令模式往返一致 ✅ 两次加密结果不同随机 salt✅ 密文不等于明文 ✅ 密文头 8 字节 Salted__hex 53616c7465645f5f✅ 密文总长度 32 字节16 字节头 16 字节密文块✅Salted__这个头说明它用的是OpenSSL 兼容的口令派生格式EVP_BytesToKey salt。我用库自身的 API 读这个头不引入额外依赖constparsedCryptoJS.enc.Base64.parse(ciphertext);constheadCryptoJS.enc.Hex.stringify(CryptoJS.lib.WordArray.create(parsed.words.slice(0,2),8),);// → 53616c7465645f5f Salted__C 组导出能力与交付声明一致把 19 个名字的typeof与交付声明逐一对照含 11 个必须为undefined的名字。断言不存在也是有意义的——它把交付的能力边界钉死将来上游补包或误改都能立刻发现。D 组在设备上直接对比上游 bundle 与交付版这一组是本文最有说服力的一屏。我把npm 上游那份有 bug 的CryptoJS.js也拷进宿主和设备上跑的交付版并排调用• 上游 npm bundle 的 HmacMD5 结果抛错 → TypeError: Cannot read property init of undefined • 上游 npm bundle 的 MD5(abc) 900150983cd24fb0d6963f7d28e17f72可用 • 交付版 HmacMD5 结果7e0d0767775312154ba16fd3af9771a2 • 两者是否一致否上游不可用交付版已修复这不是我推断上游坏了而是在同一台设备、同一个 JS 引擎上跑出来的对照结果。PC 端独立复算设备侧说和期望值一致还不够——期望值本身也要可信。所以所有期望值都由 PC 端 Node.js底层 OpenSSL算出关键结论在 PC 端再复算一次检查结果上游HmacMD5调用❌ 抛TypeError: Cannot read properties of undefined (reading init)交付版HmacMD5vs NodecreateHmac(md5)✅90/90 一致AES-128-CBC / AES-256-CBC显式 keyIVvs NodecreateCipheriv✅逐字节相同MD5(Harmony RN)✅b9f12de3a17ac5fb38584533d2ce5c35与 Node 一致aes-128-cbc 交付lAUC21ObYF3qZp45Wl3NQ3Sqkb8tOKSz8QkU1ffAww NodelAUC21ObYF3qZp45Wl3NQ3Sqkb8tOKSz8QkU1ffAww ✓ 一致 aes-256-cbc 交付RndHlDeJRl7ChXlsIKrFm4SqGQeld1fE8PyuxgvZDs NodeRndHlDeJRl7ChXlsIKrFm4SqGQeld1fE8PyuxgvZDs ✓ 一致八、真机验证验证环境Pura X View模拟器HarmonyOS 7.0.0(26.0.0) Beta2API 26ohos-x64。[cryptojs-test] A. 已知值向量MD5 / HmacMD5 / AES-CBC - 76/76 全部通过 [cryptojs-test] B. AES 口令模式与格式 - 5/5 全部通过 [cryptojs-test] C. 导出能力与交付声明一致 - 19/19 全部通过 [cryptojs-test] 口令模式密文示例U2FsdGVkX19rwGYlEmTic2OpA/FCf9CK3hZmFzmvKok [cryptojs-test] 对比上游 npm bundle 的 HmacMD5 结果抛错 → TypeError: Cannot read property init of undefined [cryptojs-test] 对比上游 npm bundle 的 MD5(abc) 900150983cd24fb0d6963f7d28e17f72可用 [cryptojs-test] 对比交付版 HmacMD5 结果7e0d0767775312154ba16fd3af9771a2 [cryptojs-test] 对比两者是否一致否上游不可用交付版已修复设备侧行为与 PC 端完全一致—— 这既说明纯 JS 库不需要原生适配也说明上游那个 bug 是打包裁剪问题与平台无关。编译代价assembleHap7 分 1 秒HAP 从 79.99 MB 涨到 80.09 MB约 102 KB其中包含两份 16.7 KB 的 bundle、27.5 KB 的向量和主 bundle 的增量。能力对照能力结果MD5(text)✅ 7 组已知值一致空串 / 单字符 / 中英文 / 1000 字节长文本HmacMD5(text, key)✅ 45 组已知值一致15 种密钥长度 × 3 种消息PC 端另复算 90 组AES.encrypt/decrypt显式 keyIV✅ 128/192/256 位 × 4 种明文密文与 OpenSSL 逐字节相同AES口令模式✅ 往返一致、随机 salt、OpenSSL 兼容Salted__格式导出能力✅ 19 个名字的typeof与交付声明一致原生接线✅ 零接线未改oh-package.json5、未触发 autolinking九、已知限制一、只支持三个能力MD5、HmacMD5、AESCBC。上游 README 里宣称的 SHA 系列、PBKDF2、DES、TripleDES、RC4、Rabbit在这个包里没有导出typeof全部是undefined。不要按 README 规划功能。二、AES 的口令模式用 OpenSSL 兼容格式。密钥由口令 随机 salt 按EVP_BytesToKey派生密文带Salted__头。密钥管理与协议升级由业务负责——这个库不提供 KDF 参数选择、不提供版本协商。三、摘要和 HMAC 是无认证的原语组合。MD5 与 HMAC-MD5 本身不构成安全协议MD5 早已不适合做抗碰撞用途用于签名场景时应评估是否该换成更强的摘要但本包不提供 SHA 系列。四、口令模式的随机数质量未评估。实现依赖运行时的随机源本次只验证了两次密文不同样本检查不等于证明随机性。五、未测大消息吞吐。纯 JS 实现对短文本没问题长文本的性能未做基准测试。六、交付版相对上游只做了两处改动。版权注释去尾空格 新增HmacMD5实现上游那个导出了但缺依赖的问题属于上游打包缺陷本交付是在 JS 层补齐不是改上游源码。七、其他 ROM / 真机未验证。适配方记录的是 OpenHarmony-7.0.0.105本次在 HarmonyOS 7.0.0(26.0.0) Beta2 模拟器上通过。十、常见问题Q这个库需要鸿蒙化适配吗A不需要原生适配——它是纯 JavaScript没有harmony/、没有原生代码、没有依赖、没有harmony.autolinking。但交付版确实改过 JS补上了上游缺失的HmacMD5实现。所以准确说法是不需要原生适配但需要一处 JS 修复。Q为什么typeof CryptoJS.HmacMD5是function调用却抛错A因为上游打包时把CryptoJS.algo.HMAC这类内部模块裁掉了但保留了对它的引用。typeof检查、静态检查、类型定义都不会报错只有真的调用一次才会暴露TypeError: Cannot read properties of undefined (reading init)。Q交付版是怎么修的会不会改坏了A用 bundle 里已有的CryptoJS.MD5和WordArray按 RFC 2104 实现 HMAC-MD5密钥超 64 字节先摘要、补零到 64 字节、ipad/opad 异或、内外双层 MD5。正确性用 Node 的createHmac(md5)逐例验证90/90 一致覆盖密钥长度 04097 字节。QREADME 里写了 SHA256、PBKDF2、DES为什么我用不了A这个 npm 包没有导出它们typeof全是undefined。README 描述的是上游完整版的形态而你装到的是裁剪过的子集。判断某个算法能不能用typeof一下比读 README 可靠。Q为什么我在metro.config.js里加了一行watchFoldersA这一行不是鸿蒙适配是file:本地依赖的通病——file:装进node_modules是链接Junctionmetro 需要watchFolders才认真实目录。从 npm registry 装成真目录就不需要这一行。Q怎么确认这个库真的没被 autolinking 接管A两个信号①link-harmony输出的linked N libraries不应因为它增加② Metro 的Redirected imports to N ...清单里不应出现它。注意这两个信号的方向——Redirected imports是鸿蒙变体重定向只有带鸿蒙实现的包才该出现在里面纯 JS 库出现在里面反而是异常。QAES 的密文为什么每次都不一样A口令模式下每次加密会生成随机 salt派生密钥和 IV 用所以同样明文 同样口令也会得到不同密文。这是正确行为。密文以 OpenSSL 兼容格式存储Base64 解码后前 8 字节是Salted__跟着 8 字节 salt。要拿确定性密文就传显式 key IV本页 A 组就是这么和 OpenSSL 比对的。QAES 用的是 CBC 还是别的模式A这个 bundle 里只有CBC配合 PKCS7 填充。GCM、CTR 等模式未包含也未验证。Q怎么确认这个交付版本是对的A四层证据① 与 npm 上游逐字节 diff只多了注释格式和 18 行HmacMD5② 设备侧100 项已知值断言全部通过期望值由 PC 端 Node/OpenSSL 生成③ PC 端独立复算——HMAC 90/90、AES 密文与 OpenSSL 逐字节相同④把上游那份 bundle 拷进宿主做同机对比直接看到上游抛错 / 交付版正确。Q为什么我在模拟器上编译要这么久A宿主已经带有原生缓存的库增量加一个纯 JS 库约67 分钟hvigor 会把整套流水线走一遍全新克隆的宿主首次编译要 3040 分钟。只改 JS 重新打包也是 67 分钟所以别把 UI 微调留到最后做。小结这个案子有两层值得记第一层是要不要适配的判断。三步就够了看package.json有没有harmony.autolinking、看仓库有没有harmony/、看代码有没有原生调用。三步都无就是纯 JS 库能省掉整套原生接线和一个 3040 分钟的首次编译。这个库的实际接入成本只有一行依赖102 KB HAP、7 分钟编译而且从 npm 装的话连metro.config.js都不用改。第二层是不需要原生适配≠不需要动代码。上游 bundle 导出了HmacMD5依赖的内部 HMAC 模块却被裁掉了——typeof是function一调用就TypeError。这类问题最有欺骗性存在性检查通不过只有真的调用一次才会暴露。由此还有两条方法论补丁必须独立验证不能只看注释。那 18 行是新写的手写实现它的注释说明的是意图不是证据。要证明它符合 RFC 2104就得拿createHmac(md5)逐例比对——而且要特意把分组长度边界64 / 65 字节和空密钥夹在里面改错一边立刻露馅。能力边界要实测不能读 README。上游 README 里列了 SHA、PBKDF2、DES但这个包一个都没导出。第三方库的 README 描述的是作者的意图或上游完整版的形态你装到的可能是裁剪过的子集。最后是验证设计上的一点把上游 bundle也拷进宿主、在同一台设备上并排调用比任何我推断上游有问题的说明都直接。做适配时多花十分钟造一个这样的对照比写三段解释有用。本篇用到的库项内容三方库react-native-crypto-js上游 1.0.0纯 JavaScript 实现交付仓库https://atomgit.com/oh-react-native/react-native-crypto-js交付 TAG1.0.0-ohos-1.0.0是否需要 HAR / ohpm / autolinking / 权限都不需要上游仓库https://github.com/imchintan/react-native-crypto-js基线 commitf9bf05dbc6f92400f53204776ad968d20d305ac2实际导出的能力MD5、HmacMD5、AESCBC宿主工程RNOH084Demo测试页rnAppKey CryptoJsTestAppreact-native-crypto-js:githttps://atomgit.com/oh-react-native/react-native-crypto-js.git#1.0.0-ohos-1.0.0importCryptoJSfromreact-native-crypto-js;constdigestCryptoJS.MD5(Harmony RN).toString();// b9f12de3a17ac5fb38584533d2ce5c35constmacCryptoJS.HmacMD5(message,secret).toString();// 7e0d0767775312154ba16fd3af9771a2// 口令模式每次随机 salt密文带 OpenSSL 兼容的 Salted__ 头constencryptedCryptoJS.AES.encrypt(hello 鸿蒙,secret key 123).toString();consttextCryptoJS.AES.decrypt(encrypted,secret key 123).toString(CryptoJS.enc.Utf8);// 显式 key IV确定性输出可与 OpenSSL 互操作constciphertextCryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(HarmonyOS 鸿蒙),CryptoJS.enc.Hex.parse(000102030405060708090a0b0c0d0e0f),{iv:CryptoJS.enc.Hex.parse(101112131415161718191a1b1c1d1e1f)},).toString();# 换页启动测试页force-stop 不能省换页参数只在冷启动生效hdc shell aa force-stop com.rnoh084.demo hdc shell aa start-bcom.rnoh084.demo-aEntryAbility--psrnAppKey CryptoJsTestApp验证环境项版本React Native0.84.1React19.2.3RNOHnpm / ohpmreact-native-oh/react-native-harmony/rnoh/react-native-openharmony0.84.3Node.jsv24.14.0DevEco Studio26.0.0.621HarmonyOS SDKAPI 2626.0.0.32设备HarmonyOS 7.0.0(26.0.0) Beta2 模拟器Pura X Viewohos-x64宿主 HAP 产物entry-default-signed.hap80.09 MB本次增量构建assembleHap7 分 1 秒验证规模设备侧100 项断言全通过PC 端 HMAC 复算 90/90、AES 与 OpenSSL 逐字节一致欢迎加入 CPF-RN 鸿蒙社区https://atomgit.com/CPF-RNReact Native for OpenHarmony 组织https://atomgit.com/oh-react-nativeRN 三方库鸿蒙适配清单https://atomgit.com/oh-react-native/rn-ohos-adaptation-overview
返回列表