增加HarmonyOS鸿蒙App 签名 JSON 验签解密能力,支持 JWS 验签、JWE 解密、先签后解链式调用,纯 Dart 零权限三接口即取即用 —— signed_json 鸿蒙使用指南
开发工具: 华为云码道
本文配套仓库: icapps/flutter-signed-json
鸿蒙适配后仓库:https://atomgit.com/oh-flutter/flutter-signed-json
本文配套仓库:https://atomgit.com/CPF-Flutter/fluttertpc_signed_json(TAG:0.0.2-ohos-1.0.0,分支:master),文中示例代码位于仓库 example/ 目录。

什么是签名 JSON(JWS/JWE)? 在配置分发、远程下发场景中,服务端需要将业务 JSON 数据安全地传递给客户端。直接传输明文 JSON 存在被篡改和泄露的风险。签名 JSON(JSON Web Signature,JWS)用私钥对 JSON 做数字签名,客户端用公钥验签确认数据完整性;加密 JSON(JSON Web Encryption,JWE)用公钥对 JSON 加密,客户端用私钥解密获取原文。两者可链式组合——外层签名保护完整性,内层加密保护机密性,形成「先签后解」的双重安全链路。
把服务端下发的签名 JSON 在客户端验签并解密,是远程配置分发、安全策略下发、许可证校验场景下的高频需求:用 ES512 公钥验签 JWS 确认数据未被篡改、用 RSA 私钥解密 JWE 获取业务 JSON、先验签再解密完成完整链路。鸿蒙应用同样需要这个能力。本文介绍如何使用鸿蒙化适配后的 Flutter 三方库 signed_json,用 verify / decrypt / verifyAndDecrypt 三个方法在鸿蒙 App 内完成 JWS 验签、JWE 解密与链式调用,全部走纯 Dart 路径零权限,并附上 OpenHarmony-6.1.1.120 真机的完整实测记录。
一、最终运行效果
应用启动后,页面自动运行六个测试场景并以卡片列表展示结果:验签成功(绿色勾号)、解密成功(绿色勾号)、链式调用成功(绿色勾号)、篡改签名验签失败(红色叉号,按预期抛出异常)、错误证书验签失败(红色叉号,按预期抛出异常)、错误证书解密失败(红色叉号,按预期抛出异常)。每张卡片显示场景标题、耗时与结果详情。
| 验证点 | 结果 |
|---|---|
| 应用启动,Flutter 页面正常渲染六场景卡片 | 通过 |
| 场景一:verify 验签 remote_config.signed 成功,返回业务 JSON | 通过 |
| 场景二:decrypt 解密 remote_config.enc 成功,返回业务 JSON | 通过 |
| 场景三:verifyAndDecrypt 链式调用成功,返回最终业务 JSON | 通过 |
| 场景四:篡改签名最后一个字符,验签按预期失败抛出异常 | 通过 |
| 场景五:用不相关 EC 公钥验签,按预期失败抛出异常 | 通过 |
| 场景六:用不相关 RSA 私钥解密,按预期失败抛出异常 | 通过 |
| 全程无需申请任何敏感权限 | 通过 |
鸿蒙技术点:FlutterPage 与 XComponent 渲染管线
鸿蒙侧的 Flutter 渲染入口是FlutterPage组件,它在Index.ets中被@Entry组件的build()方法直接使用。FlutterPage内部封装了XComponent——OpenHarmony 提供的底层渲染画布组件。XComponent通过 NAPI 桥接 C++ 引擎层,将 Flutter 的 Skia 渲染管线挂载到鸿蒙的渲染树中,使 Dart 层的 Widget 树(包括六场景卡片列表)在鸿蒙设备上完整渲染。FlutterAbility作为容器 Ability,管理FlutterEngine的生命周期,在configureFlutterEngine中注册所有平台插件。
二、signed_json 是什么
signed_json 原库(pub.dev 0.0.2,作者 icapps)是一个用于读取、校验并解密签名 JSON(JWS/JWE)的 Flutter 库。它配合 sign_config 工具生成的签名产物使用:用公钥(JWK)对 Base64 编码的 JWT 做 ES512(ECDSA P-521)验签,可选地用私钥做 RSA1_5 解密,最后对载荷做 ZLib 解压,返回业务 JSON。鸿蒙适配版在其基础上新增了 OpenHarmony / HarmonyOS 平台支持:Dart 层零改动,验签与解密全部走纯 Dart 路径(jose 库 + ZLibCodec),OHOS 平台原生插件为声明式桩实现。
几个对使用者友好的特点:
- 纯 Dart 加解密:ES512 验签、RSA 解密、ZLib 解压全部由 Dart 标准库与
jose库完成,不调用任何系统 API,逻辑在所有平台完全一致; - 零权限:OHOS 平台桩仅注册通道契约,实际加解密不经过原生通道,不需要在
module.json5中申请任何权限; - Isolate 后台计算:通过
compute()将验签与解密放到后台 Isolate 执行,避免阻塞 UI 线程; - 篡改检测:签名被修改任何一个字符都会导致验签失败并抛出异常,确保数据完整性;
- 链式安全:
verifyAndDecrypt先验签外层 JWS 得到内层 JWE,再解密得到最终 JSON——「外层签名保护完整性,内层加密保护机密性」的双重安全链路; - 跨平台一套代码:Android(原生实现)、iOS、鸿蒙、Web 共用同一套 Dart API,运行在哪端就按哪端的方式加解密。
接口说明
SignedJson 构造器
| 名称 | 描述 | 类型 | 参数类型 | 返回值 | 必填 | 鸿蒙平台支持 |
|---|---|---|---|---|---|---|
verificationCert | 验签公钥 JWK 字符串 | 属性 | String | — | 是 | 是 |
decryptionCert | 解密私钥 JWK 字符串(可选) | 属性 | String? | — | 否 | 是 |
SignedJson 方法
| 名称 | 描述 | 类型 | 参数类型 | 返回值 | 必填 | 鸿蒙平台支持 |
|---|---|---|---|---|---|---|
verify<T> | 对 JWS 串验签,返回解压并解析后的 JSON | 方法 | String encoded | Future | 是 | 是 |
decrypt<T> | 对 JWE 串解密,返回解压并解析后的 JSON | 方法 | String encoded | Future | 是 | 是 |
verifyAndDecrypt<T> | 先验签再解密,返回最终业务 JSON | 方法 | String encoded | Future | 是 | 是 |
各平台加解密路径
| 平台 | 验签路径 | 解密路径 | 说明 |
|---|---|---|---|
| Android | 原生(MethodChannel) | 原生(MethodChannel) | Platform.isAndroid 为 true,走原生实现 |
| iOS | 纯 Dart(jose + ZLib) | 纯 Dart(jose + ZLib) | useNativeSignedJson 为 false |
| OpenHarmony / HarmonyOS | 纯 Dart(jose + ZLib) | 纯 Dart(jose + ZLib) | Platform.isAndroid 为 false |
| Web | 纯 Dart(jose + ZLib) | 纯 Dart(jose + ZLib) | Web 桩为声明式 |
三、环境准备
本文所有实测均在以下环境完成:
| 项 | 版本 | 说明 |
|---|---|---|
| Flutter(ohos 版) | 3.44.9+ohos-0.0.1-canary1 | 主验证环境,真机实测 |
| DevEco Studio | 26.0.0(26.0.0.821) | 构建环境 |
| 编译 SDK | 5.1.0(18) | 宿主工程 compatibleSdkVersion 同值,保留带括号的旧格式 |
| 真机 | OpenHarmony-6.1.1.120(API 24) | ohos-arm64 |
鸿蒙技术点:compatibleSdkVersion 与 API Level 的对应关系
compatibleSdkVersion是鸿蒙工程build-profile.json5中的关键字段,声明应用的最低兼容 API 版本。鸿蒙的 API 版本与系统版本一一对应:5.1.0(18)对应 API 18,6.1.0(23)对应 API 23,6.1.1.120对应 API 24。真机安装时,系统会校验应用的compatibleSdkVersion不高于设备实际 API 版本,否则报"此应用暂不支持在当前设备安装"。本文 example 工程设为5.1.0(18),在 API 24 真机上可正常安装运行。
两点提醒:
- 编译鸿蒙目标需要使用 ohos 版 Flutter SDK,普通 Flutter SDK 无法编译 ohos 产物;
- 若在真机上安装应用报"此应用暂不支持在当前设备安装",是宿主工程的
compatibleSdkVersion高于设备 API 导致的,与插件无关,处理方式见 FAQ Q3。
四、引入依赖
进入工程目录,在 pubspec.yaml 中添加 git 依赖:
dependencies:
signed_json:
git:
url: https://atomgit.com/CPF-Flutter/fluttertpc_signed_json
ref: 0.0.2-ohos-1.0.0
执行命令拉取依赖:
flutter pub get
TAG 命名规则:
原库版本-ohos-版本号。
| Flutter 框架版本 | TAG 名称 | 分支名 |
|---|---|---|
| 3.44 | 0.0.2-ohos-1.0.0 | master |
说明:该 TAG 已在 Flutter 3.44.9+ohos-0.0.1-canary1 + OpenHarmony-6.1.1.120(API 24)真机上实测通过。
compatibleSdkVersion设为5.1.0(18)即可在 API 24 真机安装运行。库依赖jose ^0.3.2(JOSE 协议 Dart 实现)和computer ^3.2.1(Isolate 并发工具),均由纯 Dart 实现。
五、代码接入
5.1 导入库
import 'dart:convert';
import 'package:signed_json/signed_json.dart';
导入后即可使用 SignedJson 类。该类接收验签公钥(JWK 字符串)和可选的解密私钥(JWK 字符串),提供 verify / decrypt / verifyAndDecrypt 三个方法。
5.2 构造 SignedJson 实例
/// verificationCert 为验签公钥 JWK(ES512 / P-521)
static const verifyCert = '''{
"kty": "EC",
"use": "sig",
"crv": "P-521",
"kid": "signed_json",
"x": "AT0MMaUvyZdRZaCIqkLZPks0NE4kPZhgTwIivnr0tDhcl7Ao9SSdiXPuCsZS3HaAuQq5Kk1sSWGwNtteiq5JsiSg",
"y": "AVKxKaIYJewNWA3DvD5qzpW9vSMt6A1crUqVpQ-MKAOVsCL_RwwvQoedRoMbYpf6T7XLflECaI58pBtv8JfF8zVb",
"alg": "ES512"
}''';
/// decryptionCert 为解密私钥 JWK(RSA,可选)
static const decryptCert = '''{
"kty": "RSA",
"n": "lgtcwDDTKxWLLnpVTGCad7LWnRP...",
"e": "AQAB",
"d": "hlv6XGfcUNJj5eTKCTDgYZuVrucW9Q...",
"alg": "RSA1_5"
}''';
final signedJson = SignedJson(verifyCert, decryptionCert: decryptCert);
verifyCert 是 EC(椭圆曲线)公钥的 JWK 表示,算法为 ES512(ECDSA P-521)。decryptCert 是 RSA 私钥的 JWK 表示,算法为 RSA1_5。两个密钥均可通过 sign_config 工具或 mkjwk.org 生成。
代码逐段分析:JWK 密钥格式
JWK(JSON Web Key)是一种用 JSON 表示密钥的标准格式。EC 类型的 JWK 包含kty(密钥类型,EC)、crv(曲线,P-521)、x和y(公钥坐标,Base64URL 编码)、alg(算法,ES512)。RSA 类型的 JWK 包含kty(RSA)、n(模数)、e(指数,通常AQAB即 65537)、d(私钥指数)等参数。jose库的JsonWebKey.fromJson方法将 JWK JSON 解析为可用的密钥对象,用于验签或解密操作。
5.3 验签(verify)
/// JWS 签名串(Compact Serialization 格式)
static const encoded =
'eyJhbGciOiJFUzUxMiIsImtpZCI6InNpZ25lZF9qc29uIn0.eNqr5lJQUCrOz02NL0mtKFGyUlAKBnJKMjLz0hXgLCWuWgALBg2D.AXMx-2iQVtaDIaV14lXhw7j6hW0D0HKyWYu9kUz1W4W0j8gOlDU2YWipyEDWGAGXKgdmIpWl1SeWrVlXdJeVLYFlAe2pqriYxevIzEErRdP1VwLDP7lCiLERCaiz_usAAY2fiHVNqEqFhPr8bxpnWIxiEcoG2BB5zUEoEH_kZ51I0jgV';
final Map<String, dynamic> config =
await signedJson.verify<Map<String, dynamic>>(encoded);
print(jsonEncode(config));
verify 方法接收 JWS 签名串(Compact Serialization 格式,三段以 . 分隔:Header.Payload.Signature),用 verificationCert 公钥验签。验签通过后,对 Payload 做 ZLib 解压并 jsonDecode 返回业务 JSON。验签失败(签名被篡改或证书不匹配)时抛出异常。
代码逐段分析:_verifyOnBackgroundThread 验签流程
验签在后台 Isolate 中执行(通过compute()调度)。流程分五步:第一步,JsonWebSignature.fromCompactSerialization(encoded)将 JWS 签名串解析为 JWS 对象。第二步,JsonWebKey.fromJson(jsonDecode(cert))将验签公钥 JWK 解析为密钥对象。第三步,JsonWebKeyStore()..addKey(jwk)将公钥添加到密钥库。第四步,jws.getPayload(keyStore)验证签名并提取 Payload(若签名不匹配则抛出异常)。第五步,ZLibCodec().decoder.convert(payload.data)对 Payload 做 ZLib 解压,utf8.decode(unzipped)转为字符串返回。
5.4 解密(decrypt)
/// JWE 加密串(Compact Serialization 格式)
static const encryptedEncoded =
'eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiUlNBMV81In0.a5a73YZgaQw...';
final Map<String, dynamic> decrypted =
await signedJson.decrypt<Map<String, dynamic>>(encryptedEncoded);
print(jsonEncode(decrypted));
decrypt 方法接收 JWE 加密串(Compact Serialization 格式,五段以 . 分隔:Header.EncryptedKey.IV.Ciphertext.AuthenticationTag),用 decryptionCert 私钥解密。解密后同样做 ZLib 解压并 jsonDecode 返回业务 JSON。构造 SignedJson 时未提供 decryptionCert 而调用 decrypt 会抛出 ArgumentError。
代码逐段分析:_decryptOnBackgroundThread 解密流程
解密同样在后台 Isolate 中执行。流程与验签类似:JsonWebEncryption.fromCompactSerialization(encoded)解析 JWE 对象,JsonWebKey.fromJson解析私钥,jwe.getPayload(keyStore)用私钥解密并提取 Payload,ZLibCodec解压后返回字符串。JWE 的解密过程涉及 RSA 解密对称密钥、AES 解密载荷两个阶段——外层 RSA1_5 解密出对称密钥,内层 A128CBC-HS256 用对称密钥解密实际载荷。
5.5 先验签再解密(verifyAndDecrypt)
/// 链式签名串:外层 JWS 签名,Payload 为内层 JWE 加密串
static const chainEncoded =
'eyJhbGciOiJFUzUxMiIsImtpZCI6InNpZ25lZF9qc29uIn0.eJwFwce2Q0AAANAvko...';
final Map<String, dynamic> chained =
await signedJson.verifyAndDecrypt<Map<String, dynamic>>(chainEncoded);
print(jsonEncode(chained));
verifyAndDecrypt 方法实现了「先签后解」的完整链路:先用 verificationCert 验签外层 JWS,得到内层 JWE 加密串;再用 decryptionCert 解密内层 JWE,得到最终业务 JSON。这种模式适用于「外层签名保护完整性,内层加密保护机密性」的配置分发场景。
代码逐段分析:verifyAndDecrypt 链式调用
链式调用分两阶段。第一阶段,调用internalVerify(certVerify, encoded)验签外层 JWS——与verify方法相同的流程,返回内层 JWE 加密串。第二阶段,调用internalDecrypt(certDecrypt, 内层JWE串)解密内层 JWE——与decrypt方法相同的流程,返回最终业务 JSON 字符串。最后通过compute(parseAndDecode, result)将 JSON 字符串解析为 Dart 对象返回。两阶段均在后台 Isolate 中执行,避免阻塞 UI 线程。
5.6 篡改检测
/// 篡改签名:将 encoded 最后一个字符从 'V' 改为 'g'
static const tamperedEncoded =
'eyJhbGciOiJFUzUxMiIsImtpZCI6InNpZ25lZF9qc29uIn0.eNqr5lJQUCrOz02NL0mtKFGyUlAKBnJKMjLz0hXgLCWuWgALBg2D.AXMx-2iQVtaDIaV14lXhw7j6hW0D0HKyWYu9kUz1W4W0j8gOlDU2YWipyEDWGAGXKgdmIpWl1SeWrVlXdJeVLYFlAe2pqriYxevIzEErRdP1VwLDP7lCiLERCaiz_usAAY2fiHVNqEqFhPr8bxpnWIxiEcoG2BB5zUEoEH_kZ51I0jgg';
try {
await signedJson.verify<Map<String, dynamic>>(tamperedEncoded);
} catch (e) {
print('验签失败(符合预期): $e');
}
JWS 签名串的第三段(Signature)是 ECDSA 签名值,修改任何一个字符都会导致签名验证失败。jose 库在验签时检查签名有效性,不匹配时抛出异常——这是签名 JSON 防篡改的核心保障。
鸿蒙技术点:Isolate 在鸿蒙上的行为一致性
compute()是 Flutter Foundation 层提供的 Isolate 并发工具,将耗时计算放到后台 Isolate 执行。在鸿蒙平台上,Flutter Engine 的 C++ 层完整实现了 Dart Isolate 的创建、消息传递与销毁机制。compute内部通过Isolate.run创建新的 Isolate,将参数序列化传递过去,计算完成后将结果序列化传回主 Isolate。这意味着jose库的 ES512 验签和 RSA 解密在鸿蒙上与 Android/iOS 行为完全一致——Isolate 隔离的内存空间、独立的事件循环、消息传递的序列化语义全部由引擎层保证。
5.7 实战:远程配置安全分发
实际业务中常见的场景是服务端将业务配置签名加密后下发,客户端验签解密后使用。下面是一个可直接使用的组件:
class RemoteConfigLoader {
final SignedJson _signedJson;
RemoteConfigLoader(String verifyCert, String decryptCert)
: _signedJson = SignedJson(verifyCert, decryptionCert: decryptCert);
/// 加载远程配置:先验签确保完整性,再解密获取配置内容
Future<Map<String, dynamic>> load(String signedAndEncrypted) async {
try {
return await _signedJson.verifyAndDecrypt<Map<String, dynamic>>(
signedAndEncrypted);
} on ArgumentError catch (e) {
throw Exception('解密私钥未配置: $e');
} catch (e) {
throw Exception('配置验签或解密失败,数据可能被篡改: $e');
}
}
/// 仅验签加载(无加密场景)
Future<Map<String, dynamic>> loadSigned(String signed) async {
try {
return await _signedJson.verify<Map<String, dynamic>>(signed);
} catch (e) {
throw Exception('配置验签失败,数据可能被篡改: $e');
}
}
}
验签或解密失败时,异常信息明确提示"数据可能被篡改",业务可据此降级处理(如使用本地默认配置、拒绝加载等)。该组件可直接嵌入应用启动流程,在配置下发后安全加载。
六、运行与验证
以下为 demo 工程的真机实测记录。仓库中 example 的 Bundle Name 为 com.example.signed_json_example。
| 设备项 | 值 |
|---|---|
| 机型 | OpenHarmony 真机 |
| 系统版本 | OpenHarmony-6.1.1.120 |
| API 版本 | 24 |
| 构建环境 | Flutter 3.44.9+ohos-0.0.1-canary1, DevEco Studio 26.0.0 |
6.1 验证一:验签成功场景
安装、启动 demo:
# 构建 hap 后安装
hdc install entry-default-signed.hap
# 启动 demo
hdc shell aa start -b com.example.signed_json_example -a EntryAbility
应用启动后,initState 调用 _runAll() 依次执行六个场景。场景一「verify 验签」:用 verifyCert 公钥对 encoded JWS 串验签,验签通过后 ZLib 解压 Payload,jsonDecode 返回业务 JSON。卡片显示绿色勾号,详情展示截断后的 JSON 内容,耗时在毫秒级别。

6.2 验证二:解密与链式调用
# 查看运行日志
hdc shell hilog | grep signed_json_demo
场景二「decrypt 解密」:用 decryptCert 私钥对 encryptedEncoded JWE 串解密,解密成功后返回业务 JSON。场景三「verifyAndDecrypt 链式调用」:先验签外层 JWS 得到内层 JWE,再解密内层 JWE 得到最终 JSON——两阶段均成功。日志输出:
signed_json_demo | 2. decrypt => OK {"key":"value",...}
signed_json_demo | 3. verifyAndDecrypt => OK {"key":"value",...}

6.3 验证三:篡改与错误证书检测
场景四「篡改签名」:将 encoded 最后一个字符从 V 改为 g,jose 库验签时检测到签名不匹配,抛出异常。卡片显示红色叉号,详情为"按预期抛出异常"。
场景五「错误证书」:用不相关的 EC 公钥 wrongVerifyCert 验签 encoded,公钥与签名不匹配,抛出异常。
场景六「错误解密证书」:用不相关的 RSA 私钥 wrongDecryptCert 解密 encryptedEncoded,私钥与密文不匹配,抛出异常。
日志输出:
signed_json_demo | 4. verify(篡改签名) => EXCEPTION ...
signed_json_demo | 5. verify(错误证书) => EXCEPTION ...
signed_json_demo | 6. decrypt(错误证书) => EXCEPTION ...

6.4 验证四:插件注册日志
通过 hdc hilog 抓取运行日志:
hdc shell hilog -r
hdc shell aa start -b com.example.signed_json_example -a EntryAbility
hdc shell hilog | grep SignedJsonPlugin
日志输出:
SignedJsonPlugin: Attached to engine (OHOS 使用纯 Dart 路径)
插件由 GeneratedPluginRegistrant 注册成功,MethodChannel('signed_json') 通道就绪。但由于 useNativeSignedJson 在鸿蒙上为 false(Platform.isAndroid 为 false),Dart 层不经过原生通道,直接走纯 Dart 路径(jose 库验签解密 + ZLibCodec 解压)。原生插件仅保证注册表完整与通道契约对齐。
实测结论:
| 验证点 | 结果 |
|---|---|
| 应用启动,Flutter 页面正常渲染六场景卡片 | 通过 |
| 场景一:verify 验签成功,返回业务 JSON | 通过 |
| 场景二:decrypt 解密成功,返回业务 JSON | 通过 |
| 场景三:verifyAndDecrypt 链式调用成功,返回最终 JSON | 通过 |
| 场景四:篡改签名验签失败,按预期抛出异常 | 通过 |
| 场景五:错误证书验签失败,按预期抛出异常 | 通过 |
| 场景六:错误证书解密失败,按预期抛出异常 | 通过 |
| 全程无需申请任何敏感权限 | 通过 |
以下是操作的视屏,可以参考一下:
七、工作原理
整个调用链路如下:
Dart: SignedJson.verify(encoded)
→ SignedJsonUtil.internalVerify(cert, encoded)
→ useNativeSignedJson? → Platform.isAndroid
→ true (Android): NativeSignedJson.verify → MethodChannel('signed_json')
→ false (OHOS/iOS/Web): run(_verifyOnBackgroundThread, {cert, encoded})
→ compute(_verifyOnBackgroundThread, map) // 后台 Isolate
→ JsonWebSignature.fromCompactSerialization(encoded)
→ JsonWebKey.fromJson(jsonDecode(cert))
→ JsonWebKeyStore()..addKey(jwk)
→ jws.getPayload(keyStore) // ES512 验签
→ ZLibCodec().decoder.convert(payload.data) // ZLib 解压
→ utf8.decode(unzipped) // 返回 JSON 字符串
→ run(parseAndDecode, jsonString) // jsonDecode
→ 返回 Map<String, dynamic>
Dart: SignedJson.decrypt(encoded)
→ SignedJsonUtil.internalDecrypt(cert, encoded)
→ useNativeSignedJson? → false (OHOS)
→ run(_decryptOnBackgroundThread, {cert, encoded})
→ JsonWebEncryption.fromCompactSerialization(encoded)
→ JsonWebKey.fromJson(jsonDecode(cert))
→ jwe.getPayload(keyStore) // RSA1_5 解密 + A128CBC-HS256
→ ZLibCodec().decoder.convert(payload.data)
→ utf8.decode(unzipped)
→ run(parseAndDecode, jsonString)
Dart: SignedJson.verifyAndDecrypt(encoded)
→ internalDecrypt(certDecrypt, await internalVerify(certVerify, encoded))
→ 先验签外层 JWS → 得到内层 JWE 串
→ 再解密内层 JWE → 得到最终 JSON 字符串
→ run(parseAndDecode, result)
ArkTS: SignedJsonPlugin.onMethodCall (声明式桩)
→ 正常不会到达(OHOS 走纯 Dart 路径)
→ 若被调用: result.notImplemented()
Dart 侧的验签与解密全部为纯 Dart 实现,通过 jose 库完成 ES512 验签和 RSA1_5 解密,通过 ZLibCodec 完成解压。useNativeSignedJson 的判断条件是 Platform.isAndroid——只有 Android 平台走原生通道,鸿蒙、iOS、Web 均走纯 Dart 路径。
鸿蒙技术点:useNativeSignedJson 平台分流机制
base_signed_json.dart中的bool get useNativeSignedJson => Platform.isAndroid是平台分流的核心。这一判断在编译时即可确定——鸿蒙环境下Platform.isAndroid为 false,因此internalVerify和internalDecrypt不会调用NativeSignedJson.verify/decrypt(不经过MethodChannel('signed_json')),而是直接走纯 Dart 路径:将参数封装为ComputerArgs的 JSON Map,通过compute()传递到后台 Isolate,在 Isolate 中执行jose库的验签/解密 +ZLibCodec解压。这一机制保证了鸿蒙平台上的加解密行为与 iOS、Web 完全一致。
代码逐段分析:ComputerArgs Isolate 参数序列化
ComputerArgs是一个简单的数据类,包含cert(密钥 JWK 字符串)和encoded(JWS/JWE 签名串)两个字段。compute()方法要求参数和返回值都是可序列化的(因为需要跨 Isolate 传递)。ComputerArgs.toJson()将对象转为Map<String, dynamic>,在主 Isolate 序列化后传递到后台 Isolate,后台 Isolate 中通过ComputerArgs.fromJson反序列化重建对象。这一序列化-反序列化过程由 Dart VM 的消息传递机制保证,在鸿蒙上由引擎层实现,行为与 Android/iOS 一致。
鸿蒙侧插件实现(ArkTS)核心代码:
import {
FlutterPlugin, FlutterPluginBinding, MethodCall,
MethodCallHandler, MethodChannel, MethodResult,
} from '@ohos/flutter_ohos';
export default class SignedJsonPlugin implements FlutterPlugin, MethodCallHandler {
private channel: MethodChannel | null = null;
private static readonly TAG: string = 'SignedJsonPlugin';
getUniqueClassName(): string {
return 'SignedJsonPlugin';
}
onAttachedToEngine(binding: FlutterPluginBinding): void {
this.channel = new MethodChannel(binding.getBinaryMessenger(), 'signed_json');
this.channel.setMethodCallHandler(this);
console.info(SignedJsonPlugin.TAG, 'Attached to engine (OHOS 使用纯 Dart 路径)');
}
onDetachedFromEngine(binding: FlutterPluginBinding): void {
if (this.channel !== null) {
this.channel.setMethodCallHandler(null);
this.channel = null;
}
console.info(SignedJsonPlugin.TAG, 'Detached from engine');
}
async onMethodCall(call: MethodCall, result: MethodResult): Promise<void> {
// 防御性处理:OHOS 实际走纯 Dart 路径,正常不会到达此处
try {
switch (call.method) {
case 'verify':
case 'verifyAndDecrypt':
case 'decrypt':
result.notImplemented();
break;
default:
result.notImplemented();
break;
}
} catch (e) {
result.error('UNHANDLED_EXCEPTION', `处理 ${call.method} 时发生异常: ${e}`, null);
}
}
}
说明:
SignedJsonPlugin是一个声明式桩实现。鸿蒙平台上useNativeSignedJson为 false,Dart 层不会经由MethodChannel('signed_json')发起调用——加解密全部在纯 Dart 路径完成。原生插件的存在是为了满足 Flutter 插件注册机制的要求(pubspec.yaml中声明了ohos: pluginClass: SignedJsonPlugin),保证注册表完整与通道契约对齐。即使被调用,也统一返回notImplemented,交由纯 Dart 兜底,绝不抛出未处理异常。
鸿蒙技术点:GeneratedPluginRegistrant 自动生成机制
GeneratedPluginRegistrant.ets由 Flutter 工具链根据pubspec.yaml中的ohos平台配置自动生成。当flutter pub get解析到signed_json依赖声明了ohos: pluginClass: SignedJsonPlugin时,工具会在宿主工程的example/ohos/entry/src/main/ets/plugins/下生成注册代码,将new SignedJsonPlugin()添加到FlutterEngine的插件列表。EntryAbility.configureFlutterEngine中一行GeneratedPluginRegistrant.registerWith(flutterEngine)即完成插件注册。这一机制是 Flutter 插件生态在鸿蒙上无缝衔接的关键。
八、常见问题
Q1:verify 和 decrypt 有什么区别?
verify 用于验签 JWS(JSON Web Signature)——数据用私钥签名,客户端用公钥验签确认数据完整性和来源真实性。验签不需要私钥,只需公钥。decrypt 用于解密 JWE(JSON Web Encryption)——数据用公钥加密,客户端用私钥解密获取原文。解密需要私钥。两者可独立使用,也可通过 verifyAndDecrypt 链式组合——先验签外层 JWS 确保完整性,再解密内层 JWE 获取机密内容。
Q2:验签失败抛出什么异常?
验签失败时,jose 库的 jws.getPayload(keyStore) 方法会抛出异常——签名不匹配时抛出 JoseException,JWS 格式错误时抛出 FormatException,ZLib 解压失败时抛出 ZLibDecoderException。建议在业务层用 try/catch 捕获所有异常,统一视为"数据被篡改或格式错误"处理。
Q3:真机安装 demo 时提示"此应用暂不支持在当前设备安装"?
这是宿主工程的 compatibleSdkVersion 高于真机 API 版本导致的安装校验失败,与插件无关。将 build-profile.json5 中的 compatibleSdkVersion 调整为不高于真机 API 的版本(如 5.1.0(18),注意保留带括号的旧格式)即可。本文配套仓库的 example 已用此配置在 OpenHarmony-6.1.1.120(API 24)真机上安装实测通过。
Q4:如何生成验签和解密密钥?
使用 sign_config 工具或 mkjwk.org 在线生成。验签密钥:EC 类型,Key Size 选择 P-521,Key Use 选择 Signature,Algorithm 选择 ES512,取公钥部分(verify.jwk)。解密密钥:RSA 类型,Key Size 选择 2048 及以上,Algorithm 选择 RSA1_5 或 PS512,取私钥部分(decrypt.jwk)。公钥可安全地内嵌在客户端代码中,私钥应妥善保管在服务端。
Q5:为什么 OHOS 平台走纯 Dart 路径而非原生实现?
base_signed_json.dart 中 useNativeSignedJson 的判断条件是 Platform.isAndroid——只有 Android 平台走原生实现(因为 Android 上 jose 库的某些依赖在旧版本可能有兼容性问题,原库选择用 Java 原生实现替代)。鸿蒙、iOS、Web 上的 jose 库工作正常,因此走纯 Dart 路径。这保证了加解密逻辑在鸿蒙上与 iOS、Web 完全一致,且零原生依赖、零权限。
Q6:compute 后台 Isolate 在鸿蒙上可用吗?
可用。compute() 是 Flutter Foundation 层的 Isolate 并发工具,在鸿蒙平台上由 Flutter Engine 的 C++ 层完整实现 Isolate 创建、消息传递与销毁机制。jose 库的 ES512 验签和 RSA 解密在后台 Isolate 中执行,不阻塞 UI 线程。序列化-反序列化过程由 Dart VM 的消息传递机制保证,行为与 Android/iOS 一致。
九、结语
回顾一下:在 pubspec.yaml 中以 git TAG 引入 signed_json,构造 SignedJson(verifyCert, decryptionCert: decryptCert) 实例,调用 verify / decrypt / verifyAndDecrypt 三个方法即可在鸿蒙 App 内完成 JWS 验签、JWE 解密与链式调用。加解密全部走纯 Dart 路径(jose 库 + ZLibCodec),通过 compute() 在后台 Isolate 执行,不阻塞 UI 线程。同一份 Dart 代码在 Android、iOS、鸿蒙、Web 四端行为一致——Android 走原生实现,其余三端走纯 Dart 路径。签名被篡改任何一个字符都会导致验签失败并抛出异常,确保数据完整性。已在 OpenHarmony-6.1.1.120(API 24)真机完整实测,验签、解密、链式调用、篡改检测、错误证书检测六个场景全部通过。
使用中发现任何问题,欢迎到配套仓库提 Issue,也欢迎发 PR 共建。
相关链接
欢迎加入 CPF-Flutter 鸿蒙社区,社区入口、环境搭建指南和本文相关链接统一放在这里:
附:signed_json 核心能力对照表
| 能力维度 | 实现方式 | 鸿蒙表现 | 跨平台一致性 |
|---|---|---|---|
| JWS 验签 | jose 库 ES512 + ZLibCodec 解压 | 纯 Dart 路径,一致 | Android 原生,iOS/鸿蒙/Web 纯 Dart |
| JWE 解密 | jose 库 RSA1_5 + A128CBC-HS256 + ZLibCodec | 纯 Dart 路径,一致 | Android 原生,iOS/鸿蒙/Web 纯 Dart |
| 链式调用 | 先验签 JWS 得 JWE,再解密 JWE 得 JSON | 纯 Dart 路径,一致 | 四端完全一致 |
| 篡改检测 | jose 库签名验证,不匹配抛异常 | 检测正常,抛异常 | 四端完全一致 |
| 错误证书检测 | 公钥/私钥不匹配,验签/解密失败 | 检测正常,抛异常 | 四端完全一致 |
| 后台计算 | compute() Isolate 并发 | 引擎层支持,一致 | VM 端一致 |
| 参数序列化 | ComputerArgs.toJson/fromJson | 消息传递正常 | VM 端一致 |
| 平台分流 | Platform.isAndroid 判断 | false,走纯 Dart | iOS/鸿蒙/Web 一致 |
| 原生桩 | MethodChannel('signed_json') | 声明式,notImplemented | 通道契约对齐各平台 |
| 权限要求 | 无 | 无敏感权限 | 四端均无 |
更多推荐





所有评论(0)