资讯详情

RSA加密与身份证校验在鸿蒙上的Flutter适配实践

📅 2026/10/13 11:29:53 | 华诺云谱 👁 阅读
RSA加密与身份证校验在鸿蒙上的Flutter适配实践
业务里出现一个需要校验并加密身份证号的三方库很多团队第一反应是直接引入一个现成的 Flutter 包。我当时在模拟项目X里用的就是 rsa_id_number——在 Android 和 iOS 上跑得挺顺等把工程切到鸿蒙侧准备出 HAP 包那天编译直接挂了。更麻烦的是光靠升级三方库版本根本解决不了因为问题不在依赖冲突而在“原生能力缺位”。折腾了一周左右我把这套“鸿蒙化适配”的完整路径摸了出来既要把身份标识当作敏感资产来治理又要把 RSA 加解密、校验位算法、脱敏展示这些事在鸿蒙环境里重新落地。这篇文章就来拆一拆适合正在做 Flutter 鸿蒙迁移、或者准备把加密类三方库塞进鸿蒙工程的同学参考。1. 鸿蒙化之前先弄明白rsa_id_number 这个库的价值和工作边界1.1 它到底封装了哪几类能力很多人看到库名里有“rsa”以为它只是个加密工具实际上身份标识类业务里需要的通常是四件事一起做合法性校验判断一个 18 位号码是否符合现行编码规则包括正则格式、前 17 位加权求和后的校验码比对、出生日期合法性、地区码是否存在。信息提取从号码里解析出出生日期、性别、所在行政区划。性别用的是倒数第二位奇偶判断奇数为男偶数为女。脱敏展示默认场景下不能直接显示完整号码保留前三位和后四位中间用掩码代替这是身份标识资产治理里使用频次最高的能力。RSA 加解密与签名传输前用公钥加密服务端用私钥解密敏感操作时还要做签名验签防止号码被中间过程篡改。这是这类库的标准结构。把它拆开看核心价值其实分两层算法层和平台能力层。算法层包括校验规则、脱敏规则、RSA 填充逻辑这些跟操作系统没有关系只要编程语言能表达就能跑平台能力层包括随机数安全熵源、密钥生成、系统级加密框架调用这些跟操作系统绑定得很深。1.2 为什么“鸿蒙化”不是升级版本号就能解决一开始我的想法很简单既然 Flutter 是跨平台的把库升级到最新版总该支持鸿蒙了吧。后来发现这个思路有个前提漏洞——Flutter 跨平台只保证“纯 Dart 代码”能跨一旦涉及原生能力靠的是各终端的原生工程去实现。rsa_id_number 里用的加密随机数、密钥对生成在 Android 上走的是 Android 的加密 Provider在 iOS 上走的是 Security framework。鸿蒙侧如果没实现对应的原生插件Flutter 引擎调用到这一层就会断。表现可能是两种编译期直接报错插件包没有提供鸿蒙的ohos目录实现HAP 打包时找不到对应符号。运行期才暴露能编译过但调用Random.secure()或者密钥生成时抛出类似“找不到实现”的异常。这就是为什么“升级三方库”治标不治本。鸿蒙 NEXT 的新内核不再兼容 Android APK原生实现必须专门为鸿蒙环境重新提供这是绕不开的第一步认知。2. 从依赖冲突到跑不起来一次真实适配过程复盘2.1 排查链路先分清是 Dart 层还是原生层我当时遇到的现象比较典型flutter build hap能过安装到测试机上之后打开页面点“加密”按钮应用直接退出。第一反应是看崩溃日志日志里指向的调用栈一半在 Dart 侧一半在加密库的原生侧。我建议按这个顺序排查能省很多时间先单独写一个最小 Flutter 页面只调rsa_id_number的校验函数不动加密。如果校验能跑说明纯 Dart 的算法层在鸿蒙上没问题。再单独调密钥生成或公钥加密看是否在这里崩。这一步能确认问题是不是出在原生能力映射上。如果加密崩了把依赖里带android、ios目录的插件逐个列出重点检查它们有没有ohos目录或鸿蒙适配文件。在我这个案子里纯校验逻辑正常加密部分崩定位到是底层加密 Provider 缺失。这就引出了选型问题是给这个插件补一个鸿蒙实现还是换一种对接方式。2.2 三条路线怎么选重编译、桥接、还是自研替换实战中通常会碰到三条路线我把它们放在一起对比过路线改造量风险性能维护成本A. 纯 Dart 重编译小只调 gradle/ohos 配置中随机数熵源或加密 Provider 仍可能依赖平台一般中B. 算法层保留 原生能力桥接到鸿蒙 cryptoFramework中需要写通道低系统框架能力可靠高可走硬件加速低C. 完全自研替换大校验、加解密全重写高容易引入密钥管理漏洞取决于实现高我的选择是 B校验位、脱敏这些纯算法逻辑尽量保留在 Dart 侧因为它们在 Android 和 iOS 上已经被验证过RSA 密钥生成、加解密这些平台相关操作下沉到鸿蒙系统加密框架 cryptoFramework通过通道暴露给 Dart 层调用。这样做的理由很实际算法层不重写回归风险小平台层用系统级加密框架比自己在 Dart 里实现 RSA 更稳而且鸿蒙系统的加密框架能利用设备级安全硬件做密钥保护这一点对身份标识类敏感数据非常重要。2.3 一个容易被忽略的边界插件是给纯 Flutter 工程用还是混合工程用鸿蒙上 Flutter 工程的形态和 Android 不太一样。如果是纯 Flutter 工程插件入口在entry模块里如果是混合开发Flutter 模块被嵌入到原生 HAP 工程里插件注册时机、通道生命周期都不一样。我踩过的坑是在原生工程里先初始化了 ArkTS 侧的加密服务但 Flutter 引擎还没绑定完成通道注册失败导致 Dart 侧调用时拿到“通道不存在”的异常。后来改成在 Flutter 创建完成后统一注册问题才消失。适配时要先把工程形态定下来这决定了插件代码挂载方式。3. 核心算法复刻身份证校验位规则在鸿蒙上的无损移植3.1 校验位规则回顾为什么最后一位能暴露输入错误现行 18 位身份证号码由 17 位本体码和 1 位校验码组成。前 6 位是行政区划中间 8 位是出生日期接下来 3 位是顺序码最后 1 位是校验码。校验码不是随机生成的它由前 17 位加权计算而来。算法如下权重数组固定为[7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]前 17 位数字分别乘以对应权重后求和和值对 11 取余余数对应校验码表[1, 0, X, 9, 8, 7, 6, 5, 4, 3, 2]中的字符这样设计的好处是相邻两位互换、错填一位这类常见手工录入错误有很大概率在最后一位校验码上暴露出来。所以校验码算法是身份标识校验的核心也是移植中优先级最高的部分。3.2 Dart 与 ArkTS 的对照实现Dart 侧原来的逻辑大概是这样的const Listint weight [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]; const String checkCodes 10X98765432; bool validateIdNumber(String raw) { final id raw.toUpperCase(); if (!RegExp(r^\d{17}[\dX]$).hasMatch(id)) { return false; } var sum 0; for (var i 0; i 17; i) { sum int.parse(id[i]) * weight[i]; } return checkCodes[sum % 11] id[17]; }在鸿蒙侧ArkTS 的写法要保持算法逻辑完全一致避免引入行为偏差const WEIGHTS: number[] [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]; const CHECK_CODES: string 10X98765432; function validateIdNumber(raw: string): boolean { const id raw.toUpperCase(); const pattern /^\d{17}[\dX]$/; if (!pattern.test(id)) { return false; } let sum 0; for (let i 0; i 17; i) { sum Number(id[i]) * WEIGHTS[i]; } return CHECK_CODES[sum % 11] id[17]; }两个版本必须通过同一组数据做交叉验证。我当时构造了一批模拟号码来做单测比如990011199912311351X这种纯测试用结构地区码、生日、顺序码都是按规则生成的假数据不是真实身份信息。在 Dart 侧校验通过在 ArkTS 侧校验也要通过结果不一致就说明移植出了问题。3.3 移植时最容易翻车的地方大整数和字符串索引表面看两个版本代码很像实际容易在三个细节上翻车int.parse(id[i])的行为差异Dart 里字符串下标拿到的是单字符字符串直接转数字没问题ArkTS 里如果用默认的字符转数字再替换容易把X也算进去。必须先做整体正则校验保证走到加权那一步一定是纯数字。大数值溢出前 17 位加权求和最大也就是 100 多正常用 int 不会溢出。但如果有人改算法时不注意用 Decimal 或浮点型求和结果可能因为精度误差导致校验码错位。不要用 double整数计算就是整数计算。校验码大小写用户输入小写x在 Dart 侧通过toUpperCase统一处理ArkTS 侧也要做同样处理。两个平台大小写行为本身没有差异但如果你把校验逻辑再封装给后端接口一定要约束同一份规则否则会出现“前端校验通过后端校验失败”的尴尬情况。把校验函数放到独立的工具类里不掺和加密逻辑是这次适配后我养成的习惯。算法层和序列化层分离后面排查问题会轻松很多。4. RSA 治理实战密钥、填充、跨端互操作和短数据细节4.1 密钥规范从格式到存储都不能随心所欲RSA 在跨端场景里最容易出问题的不是算法本身而是密钥编码。鸿蒙的 cryptoFramework 导出密钥时默认的 DER 编码结构可能和 Flutter 侧加密库期望的格式不一致。常见的有两种格式PKCS#1只包含 RSA 的模数和公钥指数结构简单。PKCS#8外层套了算法标识是更通用的格式。如果两端对密钥格式的预期不一致解密时会直接报错或者解析出错误密钥。我遇到的情况是鸿蒙导出的公钥是 PKCS#8 的 PEM而 Dart 侧某个纯加密实现只认 PKCS#1导致加密结果到了服务端解不开。规范化做法是三个统一统一密钥格式内部全部用 PKCS#8导出和导入都走同一套转换。统一填充模式加密用 OAEP 或 PKCS1 v1.5签名用独立配置不要混用。统一 Base64 编码规则PEM 里的换行符、头尾标记要按标准处理有些库会在 Base64 字符串里带换行必须做归一化后再传给另一端。密钥存储上公钥可以放代码里或配置中心私钥则不应该出现在 Flutter 层。理想状态是私钥只存在于服务端或鸿蒙安全硬件环境端侧拿到私钥本身就是治理失败。4.2 加密基操鸿蒙 cryptoFramework 对接 Dart 的调用链Dart 侧原本封装了encryptIdNumber之类的方法内部直接调用插件。鸿蒙化之后我建议 Dart 侧只保留方法签名实际加密交给通道转发到 ArkTSconst MethodChannel _channel MethodChannel(app/id_rsa_channel); FutureString encryptIdNumber(String plainText) async { final result await _channel.invokeMethod(rsaEncrypt, { plain: plainText, }); return result as String; }ArkTS 侧注册通道后调用系统加密框架import { cryptoFramework } from kit.BasicServicesKit; const generator cryptoFramework.createAsyKeyGenerator(RSA2048); const keyPair await generator.generateKeyPair(); const cipher cryptoFramework.createCipher(RSA_PKCS1_v1_5); await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, keyPair.pubKey, null); const cipherText await cipher.doFinal(Uint8Array.fromText(plainText));这两段代码表达的是调用逻辑具体 API 名称要对照你当前 SDK 版本。核心思路是Dart 只传明文不碰密钥ArkTS 侧拿到明文后加密再把 Base64 密文返回。这里有一个很多人会踩的坑不要把密文打印到日志里。密文本身虽然不能直接还原但它泄露了加密模式和密钥指纹配合其他信息可以做侧信道分析。身份标识资产的治理原则是凡是和身份号有关的数据默认不出现在日志、崩溃上报、埋点里。4.3 短数据也要注意的边界填充造成的长度上限身份证号只有 18 位很多人觉得 RSA 加密随便做。但换到另一头服务端解密时如果约定的是“分块加密”那每块长度上限就是关键参数。以 2048 位密钥为例PKCS1 v1.5 填充单次最多能加密256 - 11 245字节。OAEP 配合 SHA-256单次最多能加密256 - 2 - 32 222字节。身份证号 18 字节远小于上限不需要分段。但如果你把加密能力抽象成通用方法未来可能有人拿去加密地址、手机号、长文本这时不做长度校验就会出现“数据过长”的运行时异常。我在工具类里加了一个前置检查超过上限就抛业务异常而不是等加密框架报错。加密之后把字节数组转 Base64 返回。注意不要用 UTF-8 字符串直接承载二进制密文那会导致密文损坏、服务端解密失败。这个低级错误在联调阶段很容易出现排查时第一反应先检查编码。4.4 签名验签和加密要分开不能因为是同一个 RSA 就共用密钥如果用同一对密钥既做加密又做签名风险在于私钥的两种用途混在一起密钥泄露面变大。比较好的做法是传输加密用一对密钥公钥在端侧私钥在服务端。接口签名用另一对密钥私钥在服务端公钥在端侧。这样即使端侧的公钥被替换攻击者也无法伪造签名即使端侧加密公钥泄露也拿不到私钥解密数据。密钥用途分离是身份标识资产治理里的基础规范适配过程中顺手就能做掉。5. 鸿蒙工程改造落地目录、配置与调用链设计5.1 工程结构原生能力放哪里最合适无论是纯 Flutter 还是混合工程我都建议把鸿蒙原生能力单独抽成一个模块不要散落在entry里。结构大致是这样ohos目录存放 ArkTS 插件实现lib目录存放 Dart 侧封装example只作为联调样例插件实现内部再拆分id_crypto.dart对外暴露validate、encrypt、decrypt、mask等方法id_crypto_ohos.ets鸿蒙侧的通道入口只负责把 Dart 的调用转换成 cryptoFramework 操作id_crypto_stub.dart非鸿蒙平台回退逻辑保证开发调试时还能在别的平台跑这样抽的好处是将来如果鸿蒙 SDK 更新只需要改ets文件Dart 侧代码和业务调用方完全不用动。5.2 通道设计参数、超时和错误返回都需要规范MethodChannel 的调用链路有个特点Dart 侧和 ArkTS 侧是异步通信任何一侧抛异常另一侧拿到的错误信息可能被简化成笼统的PlatformException。我的建议是通道协议里统一规定三件事入参必须是非敏感的上下文对象比如只传明文数据和会话 ID不要在参数里带密钥、盐值。超时机制必须有有些加密操作在设备繁忙时会超过默认超时要给一个可配置的业务超时避免页面卡死。错误码要语义化约定1001是格式错误、1002是超时、1003是密钥未初始化这样上层能针对不同错误给用户不同提示而不是统一弹一个“操作失败”。另外通道本身也要做并发保护。连续快速调用 RSA 加密时如果每次都重新生成密钥对性能和资源开销都很大。实测下来更稳的做法是应用启动时初始化一次密钥上下文后续加密请求复用已加载的密钥只在极少数场景下重新加载。5.3 适配完成后的三层验证清单单测过了并不代表真正适配完成。我建议验证清单包含三层层级验证内容通过标准算法层校验码、脱敏规则在 Dart 和 ArkTS 上跑同一批模拟数据两端结果完全一致通道层Dart 调用 ArkTS 加密后再用 ArkTS 解密明文与原始输入逐字节一致业务层在真实业务路径里跑完整流程包括日志、埋点、并发模拟无明文身份证号泄漏无异常退出其中通道层验证最值得单独做Dart 加密、ArkTS 解密是一条链路ArkTS 加密、Dart 解密是另一条链路。两条链路都要走通否则相当于只验证了单向通道服务端或者本地的数据解密还可能是断的。6. 身份数据合规治理鸿蒙适配之外更重要的事6.1 明文身份号码最容易漏的三个出口代码里写着写着身份证号就不知不觉流出去了。最容易漏的三个出口我在适配过程中都撞到过日志出口联调时随手print入参和返回值一不小心就把明文打到了日志文件里。适配过程中我特意全局搜索了print、console、Log等关键字凡是和加密模块相邻的日志一律去掉身份信息。崩溃上报出口如果崩溃时异常对象携带了明文输入上报平台会记录完整堆栈。这个问题不查不知道一查吓一跳。数据库明文出口本地缓存如果直接存明文等于把身份标识资产放在一个容易拿的地方。至少要做加密存储哪怕只是用系统 Keystore 包装过的密钥做对称加密。治理的第一步不是加复杂系统而是先做盘点和收敛哪些页面会展示明文、哪些接口会传明文、哪些函数会接收明文全部列出来。鸿蒙适配这个过程其实是很好的梳理机会因为你会重新审视一遍代码调用链。6.2 密钥要分级不能一张证书打天下身份标识相关业务至少要有两套 RSA 体系一套用于传输加密保护数据在客户端到服务端链路上的安全。一套用于签名验签保证请求来源可信、数据没有被篡改。如果系统里有多个业务场景密钥还要按业务维度隔离。比如用户资料场景和金融级核身场景不能共用一个密钥对。一旦某个业务场景的私钥泄露隔离的设计能把影响范围控制在单个场景内而不是整个系统崩盘。6.3 前端展示最小化脱敏不是可有可无的功能我在适配时把脱敏函数单独抽成了一个方法String maskIdNumber(String raw) { if (raw.length ! 18) return raw; return raw.replaceRange(3, 14, ***********); }保留前三位和后四位中间部分用掩码替代。为什么保留前三位因为前三位是行政区划代码用于地域识别保留后四位则方便用户在个别场景下核对自己的号码尾部。中间 11 位涉及生日和顺序码是最敏感的部分。脱敏要默认生效。只有在身份认证、开卡、授权这类明确需要完整号码的场景才在二次确认后展示明文。这个原则听起来简单但真正落地需要在前端组件、接口返回值、日志工具三个层面同时执行少一环都可能漏。这次鸿蒙化适配做完之后我最大的体会是跨端移植表面上是在处理编译报错、通道注册、API 差异实际上是对整个身份标识处理流程做了一次深度梳理。校验算法给所有输入兜底RSA 加密给传输兜底脱敏和密钥分级给业务治理兜底。后续如果要把这套能力再扩展成别的加密工具库我也会坚持同一个原则算法层可移植、平台层可替换、密钥和日志治理不可妥协。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑