1. 为什么偏偏选encrypter_plus做鸿蒙化改造
先交代一个背景:我最近在帮团队做一款鸿蒙端的Flutter应用,App本身不复杂,复杂的是数据安全。项目要求用户隐私数据必须整体加密落盘,密钥不能明文出现在沙箱里,还要支持多账号数据隔离。第一反应是直接从Flutter生态找加密库,对比了一圈之后决定把encrypter_plus作为基础层,再为它补一套鸿蒙侧的原生能力,这套适配方案就是本文要聊的内容。
1.1 encrypter_plus到底解决了什么问题
encrypter_plus这个库在Flutter社区里的定位是“开箱即用的通用加密组件”。它不像pointycastle那样把算法底层全部暴露给你,也不像crypto那样只有哈希和基础摘要。它更接近工程封装:把AES、RSA、HMAC、SHA系列、Base64/Hex编码、随机数生成、密钥派生这些常用能力,收敛成几个可以直接在业务代码里调用的API。
我推荐它的核心理由只有一条:纯Dart实现。这意味着加解密逻辑不依赖Android/iOS原生代码,在鸿蒙适配版Flutter引擎上可以直接跑通。很多Flutter加密库是有原生依赖的,比如底层走OpenSSL或者iOS的CommonCrypto,到了鸿蒙平台,原生插件没有对应实现,基本只能干瞪眼。而encrypter_plus的算法层全部是Dart代码编译成字节码,鸿蒙的Flutter引擎只要能执行Dart,算法就能跑。
选它还有一个现实考量:测试成本低。纯Dart代码可以在本地直接用dart test跑单测,不需要起模拟器,也不需要真机。我在适配之前先写了几个标准向量用例,确认它在Dart VM上生成的密文和Node.js里用同样参数跑出来的结果一致,才敢拿它作为跨端加密基座。
1.2 鸿蒙上直接跑Flutter加密库,绕不开的三个现实问题
第一批坑不是算法跑不起来,而是“算法跑起来了,但不知道密钥放哪儿”。
问题一:Flutter鸿蒙适配版的生态还比较年轻。Flutter官方对鸿蒙的支持是通过OpenHarmony社区分支推进的,很多三方插件的android/、ios/目录之外并没有ohos/目录。加密库还好,纯Dart能顶半边天,但如果你用的是依赖原生加密芯片的库,那基本没有鸿蒙适配可能,只能自己写MethodChannel桥。
问题二:密钥存储没有现成方案。Flutter应用在鸿蒙上,如果直接按安卓习惯把密钥写进SharedPreferences或者普通文件,这就是“把保险柜钥匙贴在保险柜门上”。鸿蒙真正的密钥保护能力在系统级安全服务里,比如通用密钥库服务(HUKS),它能把密钥材料锁定在TEE或安全芯片中,应用层只能拿到密钥句柄,拿不到明文。想用这套能力,就逃不开写原生桥接。
问题三:加解密之后的密文结构和跨端一致性。如果App未来还要在Android/iOS上跑同一套加密逻辑,那么IV怎么生成、密文头怎么设计、MAC认证码放哪儿,这些格式问题必须一开始就定死。鸿蒙侧用原生算法生成的密文,和Dart侧用纯Dart算法生成的密文,如果参数和封装格式不统一,两边会互相解不开。
1.3 我的适配思路:算法层、能力层、存储层三线分离
经过两轮方案设计,我把整体结构拆成三层,适配和后续维护都轻松很多:
- 算法层:用
encrypter_plus做跨端统一的加解密、哈希、密钥派生,所有业务逻辑只认这一套接口; - 能力层:用鸿蒙原生Plugin暴露系统安全能力,包括HUKS密钥托管、硬件级随机数、安全存储接口;
- 存储层:偏好设置只放非敏感元数据,真正的敏感数据全部走“AES-GCM加密+文件落盘”,密钥只在内存和HUKS中流转。
这套分层的直接收益是:如果哪天Flutter官方引擎更新了,或者鸿蒙侧安全API升级了,改动只局限在能力层这一个Plugin里,算法层和业务层纹丝不动。
2. 鸿蒙化工程改造:从Flutter插件到原生桥接
2.1 工程结构准备:让插件工程长出ohos目录
如果你已经有一个现成的Flutter加密插件工程,鸿蒙化改造的第一步是在工程下增加鸿蒙插件目录。以标准Flutter插件工程为例,pubspec.yaml里声明的flutter_plugin_android_dependencies这些配置不用动,但要给鸿蒙插件入口增加声明。
一个可行的做法是把encrypter_plus做成一个本地路径依赖的fork包,在改完鸿蒙支持代码之后,通过dependency_overrides指回本地目录:
dependencies: encrypter_plus: path: ./third_party/encrypter_plus flutter: plugin: platforms: android: package: com.example.encrypter_plus_android pluginClass: EncrypterPlusPlugin ios: pluginClass: EncrypterPlusPlugin ohos: pluginClass: EncrypterPlusPluginohos平台声明里指向的就是鸿蒙原生侧的实现类EncrypterPlusPlugin。工程目录需要同步具备:
encrypter_plus/ ├── lib/ # Dart 层实现 ├── android/ # Android 插件 ├── ios/ # iOS 插件 └── ohos/ ├── plugin/ │ ├── src/main/ │ │ ├── ets/packages/encrypter_plus/EncrypterPlusPlugin.ets │ │ └── ets/packages/encrypter_plus/binding/... └── build-profile.json5这里要特别注意:不同Flutter鸿蒙适配引擎对ohos目录的约束不一致。有的要求插件类继承PluginBase,有的要求直接实现Plugin接口,注册方式也有差异。我建议在动手前先查清楚你用的鸿蒙Flutter SDK版本对应的插件模板,直接拿模板工程比对是最省事的。
2.2 Dart侧封装:把MethodChannel收敛成安全接口
在Dart侧,我没有让业务代码直接MethodChannel.invokeMethod,因为这样会让调用方感知到底层是不是原生实现。更好的设计是定义一组“能力接口”,比如:
Future<String?> generateRootKey(String alias)Future<String?> deriveBusinessKey({required String alias, required String salt})Future<Uint8List> aesGcmEncrypt({required Uint8List key, required Uint8List plain})Future<Uint8List> aesGcmDecrypt({required Uint8List key, required Uint8List sealedData})
具体桥接代码如下:
class EncrypterPlusBridge { static const MethodChannel _channel = MethodChannel('encrypter_plus_ohos'); Future<Uint8List> aesGcmEncrypt({ required Uint8List key, required Uint8List plain, }) async { final result = await _channel.invokeMethod('aesGcmEncrypt', { 'key': key, 'plain': plain, }); return result as Uint8List; } }plain用Uint8List传,不要转成String再传,避免编码问题。密钥材料在Dart侧只做临时中转,不要dump成可读字符串日志。
2.3 鸿蒙侧插件实现:接住HUKS和cryptoFramework
鸿蒙原生侧的核心任务有两个:接入HUKS做密钥托管,接入cryptoFramework做加解密。下面是简化版的ArkTS插件实现思路:
import huks from '@ohos.security.huks'; import cryptoFramework from '@ohos.security.cryptoFramework'; import { PluginBase } from '@ohos/flutter_ohos'; export class EncrypterPlusPlugin extends PluginBase { onAttachedToEngine(binding: any): void { binding.getFlutterEngine().getBinaryMessenger().setMessageHandler( 'encrypter_plus_ohos', (call: any, result: any) => { try { switch (call.method) { case 'huksGenerateAliasKey': this.generateKey(call.arguments, result); break; case 'aesGcmEncrypt': this.aesGcmEncrypt(call.arguments, result); break; case 'aesGcmDecrypt': this.aesGcmDecrypt(call.arguments, result); break; default: result.notImplemented(); } } catch (e) { result.error('plugin_error', JSON.stringify(e), null); } } ); } private async aesGcmEncrypt(args: any, result: any): Promise<void> { const key = args['key'] as Uint8Array; const plain = args['plain'] as Uint8Array; const cipher = cryptoFramework.createCipher('AES256|GCM'); const symKey = cryptoFramework.createSymKeyGenerator('AES256') .convertKey({ data: key } as cryptoFramework.DataBlob); await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, symKey, { iv: this.randomIv() }); const afterUpdate = cipher.doFinal({ data: plain } as cryptoFramework.DataBlob); result.success(this.wrapCipherText(afterUpdate)); } }这里有几个细节值得展开:
第一,HUKS生成的密钥是“不可导出的”。这是好事,也是麻烦。安全上它是最大优势,密钥材料不会暴露给应用进程;但你没法把它取出来同步到服务端做备份。所以实际方案里,HUKS管的是“根密钥”,真正用于业务数据加解密的是从根密钥派生出来的“业务密钥”,这个后续第4章会展开。
第二,cryptoFramework的Cipher,在当前版本里init时的tag/iv参数比较挑剔。GCM模式不允许重复使用同一个IV,否则认证加密等于裸奔。所以鸿蒙侧必须维护一个“IV生成器”,用系统安全随机数生成12字节IV。Encrypter_plus纯Dart侧的IV逻辑保持一致,最终密文格式才能互通。
第三,异步回调的坑。MethodChannel的result.success如果不在当前调用栈返回,Dart侧拿到的顺序可能出现异常。这个问题我第5章会专门复盘,这里只说结论:鸿蒙侧加解密和密钥生成操作全部是异步的,务必等Promise settle之后再回调result.success,而且要做好错误分支的兜底。
2.4 编译期最容易翻车的几个点
在实际编译调试中,我遇到最麻烦的问题集中在三类:
- 包名不匹配:
ohos目录里的类路径、module.json5里的ability声明、插件注册入口,三处名称必须完全一致,差一个字母都编译不过; - API版本差异:ArkTS的
cryptoFramework在不同版本API Level下方法签名有调整。比如密钥生成对象从createSymKeyGenerator改成createCipher的入参方式,低版本上可能连编译都过不了; - 权限声明遗漏:如果后续要使用生物认证解锁密钥,必须在
module.json5里声明ohos.permission.ACCESS_BIOMETRIC,漏了这个权限,运行时直接抛SecurityError。
我的建议是:先拿鸿蒙官方Sample跑通一个最小可用的HUKS加解密Demo,再把它嵌进Flutter插件工程里,分步验证比一次性梭哈省太多时间。
3. 设计工业级多重加密隔离架构
3.1 给业务数据分级:不是所有数据都值得走GCM
适配完加密能力之后,最难的不是“能不能加密”,而是“哪些数据要加密、加密到什么强度”。我按敏感程度把App内的数据做了四档分类:
| 数据级别 | 典型数据 | 处理方式 | 密钥来源 |
|---|---|---|---|
| L0 公开 | 版本号、主题配置、语言包 | 明文存储 | 无 |
| L1 普通 | 用户昵称、设备偏好 | 明文+MAC完整性校验 | HMAC业务密钥 |
| L2 敏感 | 联系人、聊天记录、位置轨迹 | AES-256-GCM加密 | 业务数据密钥 |
| L3 机密 | 开发者证书、支付凭据、恢复种子 | 双重加密+白名单权限 | 根密钥派生专用密钥 |
分级的目的是控制加密成本。AES-GCM虽然快,但如果App里几百个字段全部走GCM加解密,列表页的光标滚动都会受影响。把“公开数据”和“敏感数据”用策略隔离开,是一个工业级实现的基本功。
每一级数据都有独立的“处理边界”:L1不加密但要做完整性校验,避免被篡改;L2加密后落盘,密钥只存在内存和HUKS中;L3在L2基础上还会做一次应用级二次加密,防止同一个密钥泄露后全盘失效。
3.2 算法选型:为什么我把密码学栈收敛到AES-GCM
现在主流对称加密算法就那几个,但真正适合移动端全链路加密的,我推荐组合是AES-256-GCM + PBKDF2/HKDF + RSA/ECC信封加密。
一个常见的质疑是:“CBC模式不是也能用吗?为什么非要GCM?” 关键差别在认证能力。CBC模式下,攻击者如果知道明文的某些字节,可以翻转密文对应位置的比特,导致解密结果被定向篡改,而且你根本察觉不到。GCM模式在加密的同时会生成一个认证标签(Authentication Tag),解密时先用密钥校验整段密文的完整性,校验失败直接拒绝解密,这从根本上堵死了“密文剪贴/比特翻转”的攻击路径。
放一张简明的对比表:
| 模式 | 加密 | 认证 | 并行性 | 鸿蒙侧支持 | 推荐度 |
|---|---|---|---|---|---|
| AES-ECB | 是 | 无 | 高 | 支持但语义不安全 | 不推荐 |
| AES-CBC | 是 | 无(需额外HMAC) | 低 | 支持 | 可用,需叠加认证 |
| AES-GCM | 是 | 内建认证标签 | 中 | 支持 | 推荐 |
这里还有一个工程细节:GCM模式对于IV的长度建议12字节。它本质上是CTR模式的变体,内部会把IV扩展成16字节的计数器初始块,如果你传16字节IV,NIST虽然允许但有些实现会生成不太标准的随机数方式;而少于12字节则可能引入安全噪音。实测下来12字节最顺。
3.3 全链路加密流程与密文结构设计
有了算法选型,下一步是把“加密”变成“一套可复用的流程”。我的标准加密流程是:
- 生成12字节随机IV;
- 用业务密钥(32字节)初始化GCM Cipher;
- 加密明文,得到密文+16字节认证标签;
- 按统一格式封包,输出
sealedData。
为了让密文在不同端之间可交换、可升级,我给密文设计了一个可扩展头:
┌──────────┬──────────┬──────────┬──────────────────┬────────┐ │ version │ algoId │ iv │ ciphertext │ tag │ │ 1 byte │ 1 byte │ 12 bytes │ ... │ 16 bytes └──────────┴──────────┴──────────┴──────────────────┴────────┘version:当前封包格式版本,未来算法升级时靠它来做兼容;algoId:标记用的算法组合,比如0x01代表AES-256-GCM;iv:随机生成的12字节初始化向量;ciphertext:实际密文数据;tag:16字节GCM认证标签,跟在密文后面。
这一段封包逻辑在encrypter_plus的Dart侧实现一份,鸿蒙原生侧实现一份,两边以字节数组为唯一交换格式,不做字符串近似处理。这样即使Android、iOS、鸿蒙三端数据互相迁移,也能无缝解开。
3.4 密钥分层:根密钥只负责“锁”,不负责“开”
前面反复提“多层密钥”,这里把设计的完整链路画出来:
- 根密钥(Master Key / HUKS Key):由HUKS在安全硬件内生成,永不导出。它的唯一职责是保护下一层业务密钥;
- 业务密钥(Business Key):由根密钥通过KDF(如HKDF)结合盐值派生而来,用于生成数据密钥和保护数据密钥。这个密钥可以在内存中存在,但一旦App进程被调试器挂住,需要立刻销毁副本;
- 数据密钥(Data Encryption Key, DEK):每个敏感数据集随机生成一个DEK,用业务密钥加密之后存到数据文件头里。单条数据被破解,最多泄露该DEK对应的一个数据集,不会连坐到其他数据集。
为什么不做成“一根密钥通吃”?答案是轮换和爆炸半径。如果根密钥可以解密所有数据,那一旦根密钥泄露,全盘数据完蛋;而如果根密钥只保护业务密钥,业务密钥泄露,我们只需更新业务密钥并重新封装所有数据密钥。这就是“工业级隔离”最关键的地方:隔离内网和生产网,隔离数据和密钥,隔离不同敏感级别的数据。
4. 安全存储实战:把密钥锁进系统,把密文写进沙箱
4.1 HUKS密钥托管:根密钥的生成与使用
HUKS(HarmonyOS Universal Keystore)是鸿蒙侧密钥托管的基座,它做的事情是:把密钥材料生成或导入到安全硬件(TEE/安全芯片)里,应用进程里永远拿不到明文。
实际使用中,我建议在应用启动后完成一次根密钥的“在场确认”,如果发现指定别名下没有密钥,就生成一个:
import huks from '@ohos.security.huks'; const alias = 'app_master_root_key'; const properties = [{ tag: huks.HuksTag.HUKS_TAG_ALGORITHM, value: huks.HuksKeyAlg.HUKS_ALG_AES, }, { tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256, }, { tag: huks.HuksTag.HUKS_TAG_PURPOSE, value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT | huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT, }, { tag: huks.HuksTag.HUKS_TAG_DIGEST, value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256, }];HUKS提供了精确的访问控制能力,包括:是否允许导出、是否需要用户认证、认证有效时长等。如果你要保护支付凭据这类L3数据,可以把HUKS_TAG_KEY_AUTH_ACCESS_TYPE设成用户认证后才可用,配合指纹/人脸解锁。
注意:如果用户反复解锁失败或者系统重启,HUKS里的密钥可能被锁定或失效,这是安全策略不是bug。生产环境一定要做好“密钥不可用”时的降级提示,不要直接让App崩溃。
4.2 敏感数据落盘:不要裸用SharedPreferences
再强调一次:SharedPreferences和普通文件里不要直接写明文密钥和明文敏感数据。系统提供的Preferences方便是好用,但它的加密能力(如果系统层做了加密)是服务于普通文件加密的,并不是面向单字段粒度保全设计的。更麻烦的是,很多开发者为了查问题,会打着日志把密钥打出来——这是最致命的安全坏习惯。
我采用的落盘策略是这样的:
| 存储目标 | 内容 | 方式 |
|---|---|---|
| 非敏感业务元数据 | 用户ID、主题色、最后登录时间 | Preferences明文 |
| 敏感业务数据 | 对话记录、订单详情、健康数据 | AES-GCM封包后写入独立文件 |
| 密钥原材料 | HUKS根密钥、内存中的业务密钥 | 不落盘 |
| 短期一次性票据 | 上传令牌、验证码 | 内存缓存 + 极短TTL |
在实际写入文件时,我会把封包后的sealedData和元数据(比如创建时间、数据版本、别名)放在同一个二进制文件里。文件头10字节保持固定,方便识别格式;之后紧跟封包密文。这样的设计便于后续迁移和恢复。
4.3 多账号多域隔离:每个账号一个密钥体系
多账号场景是加密隔离最容易做糊的地方。如果所有账号共用一套密钥,A账号退出后,B账号理论上还能解A的数据。正确的做法是:每个账号生成独立的主密钥域,密钥派生时加入账号专属盐值。
具体来说:
- 登录时根据账号ID生成一个盐值:
salt = SHA256.alias + accountId - 业务密钥从根密钥派生时必须带上这个盐值;
- 退出登录时,内存里所有密钥缓存全部销毁,只保留HUKS里的根密钥;
- 切换账号时,业务密钥重新派生,数据文件指定读取对应账号的密钥域。
这么设计后,即使同一台设备上A、B两个账号的数据文件都泄露,只要攻击者没有拿到根密钥(在HUKS里,拿不到),就解不开任何账号的数据。
4.4 换机、升级与恢复:容易被忽略的“密钥生命周期”
单机加解密跑通不算完,真正到了生产环境你会遇到灵魂拷问:用户换手机怎么办?用户卸载重装后数据还能恢复吗?如果直接依赖HUKS,换机后HUKS里面的根密钥可不会跟着走。
我的处理方案是“信封加密+可恢复副本”:
- 对每个业务数据域生成一个随机的DEK;
- DEK用“恢复密钥”加密后保存在云端备份里;
- 恢复密钥本身由用户口令(密码/助记词)经PBKDF2派生。这样HUKS根密钥丢失,只要用户还记得口令,还能通过恢复密钥解开DEK,找回所有云备份数据。
信任边界如下:用户口令强度决定了恢复密钥强度,所以应用内做口令设置时不要允许太弱的密码,至少要保证80bit以上的熵。
5. 实测踩坑记录:从“能跑”到“扛打”的两个关键问题
5.1 偶发解密失败:鸿蒙侧异步回调“插队”揭密
第一个坑发生在服务器端加密、端上解密的联调阶段。现象是:App偶发解密报错,但同一个密文,在Dart VM单元测试里怎么跑都能解开,真机上时好时坏。一开始我以为是算法参数问题,反复比对IV、Key、Tag都没发现异常。
排查链路是这样走的:
- 先加日志看调用顺序。在Dart侧每个
invokeMethod前后打日志,发现解密线程里两个加密请求的调用顺序和结果顺序错位了。A请求先发出,B请求后发,但B的解密结果先回来。 - 查MethodChannel的默认调度。发现鸿蒙桥接的异步回传并发执行时,回传顺序并不严格依赖发起顺序。如果业务层并发发起多个加密/解密操作,后发起的请求先完成,先发起的请求后完成,而我的解密代码用了共享的
Cipher实例,Cipher在GCM模式下内部有上下文状态,同时操作等于串扰。 - 修复:Cipher实例按请求隔离 + 业务层串行化。一方面保证每个请求都创建独立的Cipher实例和IV;另一方面对单一数据的加解密请求体,设计成串行队列,减少并发状态共享。
这个坑最隐蔽的点是:单元测试里通常会逐个等Future完成,不会并发多个操作,所以完全测不出来。生产环境只有压力测试和长稳跑测才能暴露。
提示:密钥对象和Cipher对象都不能跨请求复用。它们是“有状态”的,一个操作结束必须重置或者重新创建。
5.2 系统升级后HUKS密钥失效:安全硬件和业务数据之间的“婚姻”
第二个坑更严重。某台测试机在鸿蒙系统大版本升级之后,所有原先由HUKS生成的根密钥全部不可用,应用里已加密数据一个都解不开。这在安全设计上是合理的:TEE固件升级后,旧密钥材料可能不再受保护,系统选择直接废弃,避免旧密钥在不安全环境里续命。
但业务上不能接受“用户升级系统后数据全丢”。我的补救思路是这样的:
- 迁移测试:在每个版本发布前,准备一台旧系统设备,存好一批加密数据,升级到新系统后验证能否解密;
- 兜底保留:如果使用HUKS的
HUKS_TAG_KEY_AUTH_ACCESS_TYPE,必须给用户提供“重新认证”的完整路径,而不是静默失败; - 密钥冷备:根密钥虽然不能从HUKS导出,但业务密钥可以在生成时用用户口令进行“冷备份加密”并上传。升级后如果根密钥不可用,可走口令恢复流程。
这套组合拳保住了数据可恢复性,代价是安全边界从“纯TEE保护”变为“TEE + 用户口令双因素保护”。对于支付类L3数据,建议还是以“宁可不可用,也不要可破解”为准。
5.3 性能实测:加解密到底拖不拖后腿
我在这里放一组实测数据,供选型时参考(测试机型是麒麟芯片开发板,数据仅供量级参考):
| 操作 | 数据量 | 耗时 |
|---|---|---|
| AES-256-GCM 加密 | 100字节 | ≈ 0.05 ms |
| AES-256-GCM 解密 | 100字节 | ≈ 0.05 ms |
| AES-256-GCM 加密 | 1 MB | ≈ 4.5 ms |
| AES-256-GCM 解密 | 1 MB | ≈ 4.2 ms |
| PBKDF2(10万次迭代,32字节密钥) | — | ≈ 180 ms |
| HUKS 根密钥生成 | 256-bit AES | ≈ 1.2 ms |
结论是:AES-GCM本身性能完全不是瓶颈,真正的性能大头在于PBKDF2这类密钥派生算法。所以生产环境里,口令派生不要每次启动都跑10万次迭代,可以缓存派生结果一段时间,或者只在登录、解锁、换机恢复时跑一次。
注意:加密性能要结合日志脱敏一起考虑。千万不能让日志系统把密钥片段或者完整密文打出来。我踩过一次坑,同事为了排查问题在Debug包打印了完整DEK,后来这条日志直接进了崩溃上报系统,连同用户数据一起被上传到了远端。这是一个非常严重的安全事故。
6. 密钥轮换与后续扩展:让加密体系可持续演进
6.1 密钥轮换怎么做到“业务无感”
密钥轮换是工业级加密体系里最容易偷懒、也最不能偷懒的部分。我的做法是DEK轮换:
- 业务密钥不定期更新(比如每季度);
- 每个数据集的DEK不动,只需要用新的业务密钥重新“信封加密”DEK;
- 已有密文不用重新加密,因为解密时先解开DEK,再用DEK解数据。
这样做的好处是效率极高:轮换一个密钥域,只需要重写几行密钥元数据,不需要把几百GB业务数据全部重加密。
6.2 朝向未来:国密算法与更细粒度的隔离
鸿蒙生态里做金融、政务类需求,绕不开国密算法(SM2/SM3/SM4)的要求。encrypter_plus当前版本对国密支持有限,应对方案是在能力层扩展一个NationalCryptoPlugin,用鸿蒙原生侧实现SM4加解密,再通过统一的桥接接口暴露给Dart层。
在架构上,算法层会抽象成“算法供应者”接口:
abstract class CipherAlgorithm { String get name; Uint8List encrypt(Uint8List key, Uint8List iv, Uint8List plain); Uint8List decrypt(Uint8List key, Uint8List iv, Uint8List cipherText); }默认实现是AES-GCM,如果业务需要国密,就再注册一个SM4实现。业务代码只依赖这个抽象接口,不会因为算法替换而大改。
6.3 最后一段适配心得
这套鸿蒙化加密适配做下来,我最大的体会是:加密从来不是“算法会不会”的问题,而是“密钥在哪、泄露后影响半径多大、数据如何恢复”的工程问题。encrypter_plus帮我省掉了跨端算法对齐的工作,鸿蒙的HUKS帮我解决了密钥托管,但真正的“工业级”,是靠清晰的数据分级、严谨的密文封包格式和完整的密钥生命周期管理撑起来的。
如果你也在做Flutter鸿蒙应用的安全方案,我建议先从最小闭环开始:先跑通“Dart加密 → 鸿蒙HUKS托管根密钥 → 密文落盘 → 重启解密”这条链路,再逐步叠加多账号隔离、密钥轮换和云备份。安全方案最忌讳一步到位,因为复杂度一旦失控,出了问题连排查都无从下手。先把基座打稳,比什么都重要。