开源鸿蒙平台 CMP 三方库「cryptography-kotlin」适配全流程
本文记录
cryptography-kotlin接入 OpenHarmony 的完整过程,覆盖密码学库盘点、provider 架构边界决策、鸿蒙 kotlinx 制品线选型、根构建替换、ohosArm64/ohosX64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。
cryptography-kotlin是 whyoleg 开发的 secure cryptography library for Kotlin Multiplatform:类型安全 API(CryptographyProvider.Default.get(SHA512).hasher().hash(...))背后是一套 provider 架构——库本身不实现算法,而是包装 OpenSSL 3.x / JCA / WebCrypto / CryptoKit,再套上统一的类型安全外壳;除算法层外它还提供四个不依赖任何 provider 的独立模块:SecureRandom、PEM 编解码、ASN.1 DER 编解码与 BigInt。本次适配与"只把示例页改成 ArkTS"有本质区别:provider 无关层的六个库模块(bigint / random / pem / asn1 / asn1-modules / core)的commonMain原样编译成 OpenHarmony KLIB,安全随机(/dev/urandom)、PEM 编解码、RFC 5280 结构的 ASN.1 DER 编解码与序列化级大整数真实运行在真机上,ArkUI 页面作为验收宿主。算法引擎层(AES / RSA / ECDSA / SHA 等 provider)因 ohos 尚无预构建 libcrypto 而明确划出边界、留待制品线补齐——这是本文要讲清楚的"适配边界决策"。项目地址: AtomGit/oh-tpc/cryptography-kotlin.git
上游仓库: whyoleg/cryptography-kotlin
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配
cryptography-kotlin 为 Kotlin Multiplatform 应用提供"一套类型安全 API、多个底层引擎"的密码学答案:JVM 走 JCA(可插 BouncyCastle)、Native 走 OpenSSL 3.x 预构建、Apple 走 CryptoKit、Web 走 WebCrypto;optimal provider 再按平台自动挑最优引擎。KMP 项目把这些选择放在 commonMain,由 Android、iOS、桌面、Web 共享。OpenHarmony 应用同样需要这套能力,但上游构建(Kotlin 2.3.20 / Gradle 9.4.1 / AGP 9.1.0 / 复杂的 build-logic 约定插件体系)不会生成 OpenHarmony 变体。
如果只把示例页面改成 ArkTS,页面可以显示按钮,却无法证明公共密码学层在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证:安全随机源、PEM/ASN.1 序列化运行时、BigInt 算术底座、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。 |
| 工具链不一致 | 上游 Kotlin 2.3.20 / Gradle 9.4.1 / AGP 9.1.0 与鸿蒙工具链无法共存于一个 Gradle 构建;官方 Kotlin 无 ohos target。 |
| 构建体系重 | 上游用 build-logic / build-settings 双 includeBuild 的约定插件体系(kover、dokka、vanniktech 发布、xcframework、AGP),对 fork 无保留价值。 |
| 依赖矩阵 | 6 个进入构建的模块依赖 kotlinx-io-core、kotlinx-serialization-core,每一个都要有 ohos 变体才能解析。 |
| expect/actual 缺口 | cryptography-random 的 defaultCryptographyRandom() 是 expect,actual 分布在 apple / linux / mingw / web 分组里,ohos 不属于任何一组,不补 actual 编译直接失败。 |
| 算法引擎无制品 | openssl3 系 provider 需要 OHOS 预构建 libcrypto(社区尚无);jdk / apple / webcrypto / cryptokit 是平台绑定。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。 |
| 交付链路复杂 | .so、CMake、N-API、HAP、签名和 hdc 都要单独验收。 |
因此,本项目把边界放在:provider 无关层(SecureRandom / PEM / ASN.1 / BigInt / core)的 ohos 编译、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。算法语义完全由上游原版实现负责,ArkTS 只负责调用 Native、解析 JSON 和展示结果。
1.2 库提供的能力
Demo 提供一个真实的密码学会话,对应桥接的全部入口:
- 版本握手:
ckVersion()返回 fork 版本 / 上游版本 / Kotlin 工具链 / 目标 / 模块清单,来自真实 Native 调用而非静态文字; - 安全随机:
CryptographyRandom.Default.nextBytes(32)经 ohos actual 直读/dev/urandom(O_CLOEXEC、逐块填充),hex + base64 双输出,ArkTS 侧用Base64Helper做解码再编码的交叉校验; - PEM 编码:
PemDocument(PemLabel(label), bytes).encodeToString()产出 RFC 7468 格式的-----BEGIN/END-----文本; - PEM 解码:
PemDocument.decode(pem)回读 label、字节数与 base64,可打印内容附 text 预览; - ASN.1 DER 编解码:
RsaAlgorithmIdentifier/Ed25519AlgorithmIdentifier/EcAlgorithmIdentifier(EcParameters(secp256r1 / secp384r1))经Der.encodeToByteArray<AlgorithmIdentifier>(...)编码为 DER,再decodeFromByteArray回读并做 roundtrip 位一致校验——这是 RFC 5280AlgorithmIdentifier结构在真机上的完整往返; - BigInt:随机 2048bit 幅度经
BigInt.fromMagnitude变成大整数,输出十进制 / 十六进制 / 符号 / 字节数;两个 hex 大整数做compareTo比较。
响应统一为:
| 字段 | 作用 |
|---|---|
ok | 操作是否成功,失败时 error 带原文。 |
op | 回显本次操作。 |
hex / base64 | random 的双格式输出。 |
pem / label / text | PEM 编解码的主载荷与回读字段。 |
oid / derHex / roundtrip | ASN.1 的算法 OID、DER 十六进制与往返校验位。 |
decimal / sign / result | BigInt 的十进制串、符号与比较结果。 |
引擎侧完整能力(provider 注册机制、算法查找 Registry、全部序列化结构)都随上游 commonMain 原样编译进 .so,JVM 侧保留上游测试套件做交叉验证,证明 ohos .so 里执行的逻辑没有漂移。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | 6 个库模块的全部 commonMain 文件原样编译,不提取、不重写;仅新增 1 个 ohos random actual(全仓唯一新增库源码文件)。 |
| 平台目标 | 根构建直接声明 ohosArm64 + ohosX64,保留 jvm 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。 |
| 构建隔离 | 根构建替换为极简 ohos 构建(cmp_qrose 模式);providers / bom / gradle-plugin / build-logic 等留盘不进构建。 |
| 桥接稳定 | 使用 2 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。 |
| 依赖纪律 | 版本主体与上游同一小版本线,只把仓库换到 ohos 制品线(io 0.9.0→0.9.1、serialization 1.10.0→1.11.0,均为制品线起点),上游测试零改动全绿。 |
| UI 完整 | 单页覆盖全部桥接入口:版本握手、随机数、PEM 编解码、ASN.1 编解码、大整数随机与比较、操作日志。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。
cryptography-kotlin本身不碰 UI,适配面天然落在计算与序列化层;ArkTS Demo 只是把桥暴露的密码学会话画出来。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点模块与依赖边界、确认 provider 层的去留、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.4.20-0.1.0-07 钉版、Gradle 9.6.1 对齐
第 3 阶段:桥协议建模 ── 8 个 op 的 JSON 契约、MiniJson、错误通道
第 4 阶段:原生桥接 ── @CName C ABI、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主 ── 版本握手面板、按钮矩阵、操作日志
第 6 阶段:示例与验证 ── HAP、签名、设备安装、真机运行和效果图
三、逐步实现过程
第 1 阶段:项目初始化
1.1 工程边界
cryptography-bigint/ 上游大整数(进 ohos 构建,jvm + ohosArm64 + ohosX64)
cryptography-random/ 上游安全随机(进 ohos 构建)
└── src/ohosMain/ 新增:/dev/urandom actual(全仓唯一新增库源码)
cryptography-serialization/pem|asn1|modules/ 上游序列化三件(100% commonMain,进 ohos 构建)
cryptography-core/ 上游高层 API + provider 注册机制(进 ohos 构建)
cryptography-providers/* jdk / apple / webcrypto / openssl3 / cryptokit / optimal
算法引擎层,留盘不进构建(ohos 无预构建 libcrypto)
build-logic/ build-settings/ 上游约定插件体系,留盘不进构建
cryptography-bom / version-catalog / gradle-plugin / testtool / swiftinterop / tests-publication
发布与测试设施,留盘不进构建
example/nativeApp/ Kotlin/Native 动态库(Bridge.kt + MiniJson)
example/harmonyApp/ DevEco Stage、CMake N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md 鸿蒙适配说明(中英双份)
README.upstream.md 上游原文保存
构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include 6 个库模块与 :example:nativeApp,rootProject.name = "ohos_cryptography_kotlin"。上游的 kover / dokka / vanniktech 发布插件、AGP classpath、OpenSSL ivy 预构建仓库、build-logic 约定插件全部随根构建替换剥离——本 fork 不发 Maven Central。
1.2 工具链矩阵
| 项目 | 配置 |
|---|---|
| Kotlin Multiplatform | HarmonyOS Kotlin 2.4.20-0.1.0-07(eazytec Maven) |
| JDK | 21 Temurin(org.gradle.java.home 钉进 gradle.properties) |
| OpenHarmony 目标 | ohosArm64 + ohosX64(jvm 保留作交叉验证) |
| Gradle | 9.6.1(上游 9.4.1 主动升级,与 KSafe / KStore 审核矩阵一致,原因见 2.2) |
| kotlinx | kotlinx-io-core 0.9.1-0.1.0-07、kotlinx-serialization-core 1.11.0-0.1.0-07(ohos 制品线) |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.terminator.ohos.cryptography |
export JAVA_HOME="C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot"
java -version
1.3 源码依赖度盘点
| 模块 | commonMain 形态 | 依赖 | 处置 |
|---|---|---|---|
cryptography-bigint | 纯 Kotlin(jvm 用 BigInteger,native 自实现幅度算术) | serialization-core(compileOnly) | 整体编译 |
cryptography-random | expect defaultCryptographyRandom() + 7 平台 actual | 零外部依赖 | 整体编译 + 新增 ohos actual |
cryptography-serialization-pem | 100% commonMain | kotlinx-io-core | 整体编译(零平台源码集) |
cryptography-serialization-asn1 | 100% commonMain | serialization-core + bigint | 整体编译 |
...asn1-modules | 100% commonMain(RFC 5280 等结构定义) | asn1 | 整体编译 |
cryptography-core | commonMain + jvmMain(ServiceLoader) + nonJvmMain(空) | bigint + random + kotlinx-io | 整体编译 |
cryptography-providers/* | jvm / apple / web / openssl3 cinterop | 平台密码库 | 留盘不进构建 |
expect 声明里与 ohos 相关的只有一处硬缺口:cryptography-random 的 internal expect fun defaultCryptographyRandom(): CryptographyRandom。上游的 actual 分布在 appleMain(CCRandomGenerateBytes)、linuxAndAndroidNativeMain(getrandom syscall 优先、/dev/urandom 回退)、mingwMain(BCryptGenRandom)、webMain / wasmWasiMain——ohos 不属于任何分组。而上游 linux 实现里的 SYS_getrandom 硬编码了 x86_64 的系统调用号 318(arm64 Linux 上是 278),照抄到 ohosArm64 会调用错误的系统调用。ohos 补的第一个、也是整个 fork 唯一新增的库源码文件,直读 /dev/urandom:
// cryptography-random/src/ohosMain/kotlin/CryptographyRandom.ohos.kt
// The OHOS kernel is Linux, so /dev/urandom has the same never-blocking semantics
// as the linuxAndAndroidNative actuals. The upstream getrandom(2) path hardcodes
// the x86_64 syscall number, which is wrong on arm64, so this port reads
// /dev/urandom directly through musl.
internal actual fun defaultCryptographyRandom(): CryptographyRandom = OhosRandom
private object OhosRandom : AbstractRandom() {
@OptIn(ExperimentalForeignApi::class)
override fun fillBytes(array: ByteArray) {
val fd = open("/dev/urandom", O_RDONLY or O_CLOEXEC)
check(fd >= 0) { "open(/dev/urandom) failed with errno $errno" }
try {
array.usePinned { pinned ->
var filled = 0
while (filled < array.size) {
val chunk = read(fd, pinned.addressOf(filled),
(array.size - filled).convert()).convert<Int>()
check(chunk > 0) { "read(/dev/urandom) failed with errno $errno" }
filled += chunk
}
}
} finally {
close(fd)
}
}
}
其余两个 expect 由上游 hierarchy 天然覆盖:bigint 的 nativeAndWasmWasiMain actual、core 的 nonJvmMain 空 loadProviders()(非 JVM 平台 provider 靠模块顶层属性副作用自注册,纯运行时可选,不影响编译)。
pem / asn1 / asn1-modules 三个模块值得单独说明:全部实现站在 kotlinx-io 的 ByteString 与 kotlinx-serialization 的 BinaryFormat 上,零平台源码集——过去挡住这类库适配的是"ohos 上没有 kotlinx 制品",本次适配首先验证这个障碍已经消失。
1.4 页面边界
标题区 CRYPTOGRAPHY-KOTLIN · OPENHARMONY / 版本握手行(KMP 密码学库 · Kotlin/Native ohosArm64/ohosX64)
版本握手面板 ckVersion() 解析出的 fork / upstream / Kotlin / targets / modules 五行
输入区 文本框(默认填 atomgit 仓库链接,PEM 编码的输入)+ PEM 多行框(编码结果回填,也可粘贴外部 PEM)
操作区 随机数 32B / PEM 编码 / PEM 解码 / ASN.1 rsa / ASN.1 ec-p256 / 大整数 2048bit / 大整数比较
状态行 最近一次操作结果
日志区 带时间戳的操作日志(→ 请求摘要 / ← 响应,最新在前,上限 100 条)
说明区 架构分层说明与仓库 / 社区链接
页面启动时先调 ckVersion() 做握手,ASN.1 按钮编码后立即回读解码并显示 roundtrip 校验位。
第 2 阶段:目标与依赖打通
2.1 制品线验证(本适配的成败手)
先查社区 Maven(https://maven.eazytec-cloud.com)上有没有 ohos 的 kotlinx 制品。用 .module 元数据逐个验证:
org.jetbrains.kotlinx:kotlinx-io-core:0.9.1-0.1.0-07
variants: ohosArm64Api-published / ohosX64Api-published / jvmApi-published / jvmRuntime-published
org.jetbrains.kotlinx:kotlinx-serialization-core:1.11.0-0.1.0-07
variants: 同上(jvm + ohos 双线齐全)
org.jetbrains.kotlin:kotlin-gradle-plugin:2.4.20-0.1.0-07
二进制含 ohosArm64 / ohosX64 DSL(官方 2.3.20 / 2.4.20 均为 0 命中)
三个关键结论:其一,ohos 制品线起始于 io 0.9.1 / serialization 1.11.0,上游钉的是 0.9.0 / 1.10.0,本仓把版本主体上浮到制品线起点(同为向后兼容的小版本升级),commonMain 里一行依赖坐标都不用改;其二,ohos 制品同时发布 jvm 变体,jvm 交叉验证可以用同一个版本号,不需要双线隔离;其三,Maven Central 上的同名版本没有 ohos 变体,版本必须带 -0.1.0-07 后缀才解析得到。另探明:eazytec 上没有 kotlin-native-compiler-embeddable、也没有任何 OHOS 预构建 OpenSSL——这两个事实分别决定了第 2.2 节的工具链钉法和第 1.1 节的 provider 边界。
2.2 工具链钉版(为什么是 2.4.20-0.1.0-07 + Gradle 9.6.1)
社区此前批准的钉法分两条线:上游 ≤2.2 用 HarmonyOS Kotlin 2.2.21-1.0.0(qrose 先例);上游 2.3.x / 2.4.x 用 2.4.20-0.1.0-07(KSafe 先例:上游 2.3.21 → 2.4.20-0.1.0-07)。本仓上游 Kotlin 2.3.20,按 KSafe 先例钉 2.4.20-0.1.0-07,且 kotlinx 制品线正是针对 stdlib 2.4.20-0.1.0-07 构建的——工具链版本跟着依赖走。
Gradle 是本次适配踩的第一个深坑:沿用上游 wrapper 9.4.1 时,KGP 在 compileKotlinOhosArm64 里走 in-process 编译器路径,而 eazytec 没有发布 kotlin-native-compiler-embeddable(HTTP 404),直接报:
java.lang.ClassNotFoundException: org.jetbrains.kotlin.cli.utilities.MainKt
at ...KotlinNativeToolRunner.runInProcess(KotlinNativeToolRunner.kt:165)
KSafe / KStore / FlowRedux 等已审核仓库的公共矩阵是 Gradle 9.6.1 + Kotlin 2.4.20-0.1.0-07(该组合下 KGP 走 Kotlin daemon 路径,编译器类来自 konan 分发目录)。把 wrapper 升到 9.6.1 后同一条命令立即通过——两条工具链线的 Gradle 经验不能互相套用,这次是"矩阵对齐"而非"版本玄学"。
settings.gradle.kts 把 eazytec 双仓写在最前,并按 KSafe / Result 先例加 content filter(eazytec 对外来 group 只代理元数据不代理 jar,不过滤会让 Gradle 绑定错模块):
pluginManagement {
repositories {
// eazytec must stay first here: the HarmonyOS Kotlin plugin (with the
// ohosArm64/ohosX64 DSL) resolves from the community line only.
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
maven("https://maven.eazytec-cloud.com/nexus/content/groups/harmonyos")
gradlePluginPortal(); google(); mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
repositories {
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/") {
content { includeGroupByRegex("org[.]jetbrains.*") }
}
maven("https://maven.eazytec-cloud.com/nexus/content/groups/harmonyos") {
content { includeGroupByRegex("org[.]jetbrains.*") }
}
google(); mavenCentral()
}
}
rootProject.name = "ohos_cryptography_kotlin"
include(":cryptography-bigint")
include(":cryptography-random")
include(":cryptography-serialization-pem")
include(":cryptography-serialization-asn1")
include(":cryptography-serialization-asn1-modules")
include(":cryptography-core")
include(":example:nativeApp")
2.3 编译真实库模块
6 个模块的 build.gradle.kts 重写为纯 KMP DSL(脱离 build-logic 约定插件),以 core 为例:
plugins {
alias(libs.plugins.kotlin.multiplatform)
}
kotlin {
// 上游 build-logic 开启的 progressive 模式与 expect/actual 类,
// 上游源码依赖这两项语言设置(pem 的 UnnamedLocalVariables 还需显式 -XXLanguage)
compilerOptions {
freeCompilerArgs.addAll(
"-progressive", "-Xexpect-actual-classes",
"-XXLanguage:+UnnamedLocalVariables"
)
}
jvm { // 交叉验证用,对齐上游 JVM_1_8 / jdk-release 8
compilerOptions {
jvmTarget = JvmTarget.JVM_1_8
freeCompilerArgs.add("-Xjdk-release=8")
}
}
ohosArm64()
ohosX64()
@OptIn(ExperimentalKotlinGradlePluginApi::class)
applyDefaultHierarchyTemplate { // 上游自定义 hierarchy 原样保留
common {
group("nonJvm") {
withJs(); withWasmJs(); withWasmWasi()
group("native")
}
}
}
sourceSets {
commonMain.dependencies {
api(project(":cryptography-bigint"))
api(project(":cryptography-random"))
api(libs.kotlinx.io.core)
}
commonTest.dependencies { implementation(libs.kotlin.test) }
}
explicitApi() // 上游 ckbuild.kotlin-library 的显式 API 纪律
}
bigint 保留 nonJvm → nativeAndWasmWasi → native 分组(ohosMain 经 native 链接上游 nativeAndWasmWasiMain 的 actual);random 保留空的 linuxAndAndroidNative 分组;pem / asn1 / modules 无自定义分组。serialization 插件在 bigint / asn1 / modules 模块级补回(上游在约定插件里统一 apply)。实测:
./gradlew :cryptography-bigint:compileKotlinOhosArm64 :cryptography-random:compileKotlinOhosArm64
./gradlew :cryptography-core:compileKotlinOhosArm64 :cryptography-serialization-asn1-modules:compileKotlinOhosArm64
# 全部 BUILD SUCCESSFUL
6 个模块的 commonMain 一个没动,只有那一页 ohos random actual 是新的。
2.4 测试依赖的处置:ohos 无测试运行器
ohos 侧没有 K/N 测试运行器,测试统一在 JVM 跑(jvmTest),根构建全局禁用 ohos 测试任务:
subprojects {
// Precedent: cmp_qrose / KStore / KSafe ports.
tasks.matching { it.name.contains("Ohos", ignoreCase = true) && it.name.contains("Test") }
.configureEach { enabled = false }
}
这样上游测试文件零改动保留,ohos 侧只承担编译与链接。
第 3 阶段:桥协议建模
3.1 JSON 契约
dev.whyoleg.cryptography.**(上游 6 模块,原样编译)
↓ CryptographyRandom / PemDocument / Der / BigInt
Bridge.kt(@CName × 2,扁平 JSON 进出)
↓ UTF-8 JSON
N-API ckVersion() / ckDispatch(request)
↓
ArkUI Index.ets(单页滚动)
请求与响应:
请求 : {"op":"random","size":32}
{"op":"pemEncode","label":"OPENHARMONY DEMO","text":"..."} // 或 base64 字段
{"op":"pemDecode","pem":"-----BEGIN ..."}
{"op":"asn1Encode","algorithm":"rsa|ed25519|ec-p256|ec-p384"}
{"op":"asn1Decode","derHex":"300906..."}
{"op":"bigintRandom","size":256}
{"op":"bigintCompare","aHex":"...","bHex":"..."}
响应 : {"ok":true,"op":"...","hex":"...","base64":"...","pem":"...","oid":"...",
"derHex":"...","roundtrip":true,"decimal":"...","result":-1|0|1, ...}
{"ok":false,"error":"..."}
两个不对称设计:入口载荷走字符串通道(text / base64 / derHex / aHex,ArkTS 侧 JSON.stringify 一行可得);二进制出参统一 hex + base64 双格式(hex 用于展示与 roundtrip 校验,base64 供 ArkTS Base64Helper 解码复核字节数),读写两条路都不做双重转义。
3.2 载荷为什么是扁平 JSON 而不是对象句柄
密码学操作天然是"一次调用一次结果",没有跨调用的会话状态(对比 KStore 的 store 句柄),所以桥不需要对象注册表——8 个 op 全部是无状态函数,ArkTS 每次组一个扁平请求对象即可。这个选择把 C ABI 的生命周期管理压缩到只剩字符串:返回的 const char* 用 DisposeString 归还,无泄漏面。
MiniJson 手写解析器(继承自 cmp_qrose 审核模式)做完整转义(\" \\ \n \r \t \b \f \uXXXX)——PEM 文本含换行,ArkTS JSON.stringify 会转义成 \n,桥侧必须还原,这是 qrose 简化版解析器覆盖不到的场景。
3.3 错误通道
每个导出整体 try/catch,Kotlin 异常永远不跨 FFI:
@CName("CkDispatch")
fun ckDispatch(request: String): String {
return try {
val json = MiniJson.parse(request)
when (val op = json.str("op")) {
"random" -> opRandom(json)
"pemEncode" -> opPemEncode(json)
/* ... 其余 6 个 op ... */
else -> """{"ok":false,"op":${jsonString(op)},"error":"unknown op"}"""
}
} catch (t: Throwable) {
"""{"ok":false,"error":${jsonString(t.message ?: t.toString())}}"""
}
}
非法 JSON、缺字段、不支持的 op、奇数长度 hex、不支持的平台输入,全部走同一个 ok:false 通道,ArkTS 侧统一渲染。
第 4 阶段:原生桥接(技术难点)
4.1 三层桥接
ArkTS
│ JSON string
▼
C++ N-API entry(libcryptography_napi.so)
│ const char*
▼
Kotlin/Native C ABI(libohoscryptography.so)
│ CkDispatch / CkVersion
▼
UTF-8 JSON + DisposeString
直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;只导出整数又会让页面复制密码学语义,所以选择 C ABI + JSON。
4.2 Kotlin/Native 导出函数
@CName("CkVersion")
fun ckVersion(): String =
"""{"ok":true,"op":"version","version":"0.5.0-ohos.1","upstream":"0.5.0",
"kotlin":"2.4.20-0.1.0-07","targets":["ohosArm64","ohosX64"],
"modules":["cryptography-bigint","cryptography-random",
"cryptography-serialization-pem","cryptography-serialization-asn1",
"cryptography-serialization-asn1-modules","cryptography-core"]}"""
@CName("CkDispatch")
fun ckDispatch(request: String): String // 8 个 op 一口进
| C ABI 符号 | ArkTS 方法 | 覆盖内核 |
|---|---|---|
CkDispatch | ckDispatch(req) | random / PEM 编解码 / ASN.1 编解码 / BigInt |
CkVersion | ckVersion() | fork 版本握手(真机版本面板的数据来源) |
example/nativeApp/build.gradle.kts 双 ABI binaries.sharedLib { baseName = "ohoscryptography" },copy 任务把 libohoscryptography.so + konan 的 libc++_shared.so 拷进 harmonyApp/entry/libs/{arm64-v8a,x86_64},并把生成的 libohoscryptography_api.h 拷进 NAPI cpp/include。
4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
{"ckDispatch", nullptr, NapiCkDispatch, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
{"ckVersion", nullptr, NapiCkVersion, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};
每个回调统一三步:napi_typeof 校验必须是 string、OH_LOG_INFO 打字节数、调 KN 函数并包装返回。KN 返回的字符串必须经 libohoscryptography_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。
4.4 CMake 和类型声明
add_library(ohoscryptography SHARED IMPORTED)
set_target_properties(ohoscryptography PROPERTIES
IMPORTED_LOCATION ${KN_SO}
IMPORTED_NO_SONAME TRUE)
add_library(cryptography_napi SHARED napi_init.cpp)
target_link_libraries(cryptography_napi PUBLIC
libace_napi.z.so
libhilog_ndk.z.so
ohoscryptography)
OHOS_ARCH 为空或 libohoscryptography.so 缺失直接 FATAL_ERROR(提示先跑 link 任务);$ORIGIN rpath 加 POST_BUILD 拷贝保证 NAPI so、KN so、libc++ 三件套同目录,dlopen 时互相可见。
export const ckVersion: () => string;
export const ckDispatch: (request: string) => string;
第 5 阶段:ArkUI 宿主
5.1 版本握手和触发操作
aboutToAppear(): void {
this.refreshVersion(); // ckVersion() → 版本面板五行
}
private dispatch(req: DispatchRequest): DispatchResp | undefined {
this.log(`→ ${req.op} ${this.requestSummary(req)}`);
try {
const rawOut: string = ckDispatch(JSON.stringify(req));
const resp = JSON.parse(rawOut) as DispatchResp;
if (!resp.ok) {
this.log(`← ${req.op} 失败: ${resp.error ?? '未知错误'}`);
return undefined;
}
return resp;
} catch (e) {
/* Native 调用异常同样收进日志区 */
return undefined;
}
}
PEM 编码前不强制校验(任意字节都可编码);ASN.1 与大整数按钮自带合法预设值,用户直接点按即可完成验收。
5.2 页面交互
单页自上而下:版本握手面板(fork / upstream / Kotlin / targets / modules 五行)、文本输入框(默认填 atomgit 仓库链接)、PEM 多行框(编码结果自动回填,也可粘贴外部 PEM)、操作按钮矩阵、状态行与操作日志。每条日志带时间戳,→ 行显示请求摘要、← 行显示响应(如 ← asn1Decode oid=1.2.840.113549.1.1.1 roundtrip=true),最新在前、上限 100 条。底部是 ArkTS → NAPI → C ABI → Kotlin 引擎 → /dev/urandom · PEM · ASN.1 · BigInt 分层说明与仓库 / 社区链接。
5.3 交叉校验与错误边界
随机数与 PEM 解码的 base64 出参由 ArkTS 侧 util.Base64Helper 做 decode → reencode 交叉校验(字节数与字符串双重比对),校验位直接显示在状态行;ok:false 时错误原文进状态行与日志。Native 调用本身抛错(理论不该发生)同样被 try/catch 收进日志区。
第 6 阶段:示例与验证
6.1 工程结构
example/
├── nativeApp/
│ ├── build.gradle.kts 双 ABI sharedLib(baseName=ohoscryptography)
│ │ + cxxShared + copyKnSo*(含头文件拷贝)
│ └── src/ohosMain/kotlin/example/nativeapp/
│ └── Bridge.kt @CName × 2 + 8 op + MiniJson(cmp_qrose 审核模式)
└── harmonyApp/
├── build-profile.json5 signingConfigs 留空(签名由 DevEco 自动生成回填)
├── AppScope/app.json5 bundleName org.terminator.ohos.cryptography
│ versionName 0.5.0-ohos.1
└── entry/
├── build-profile.json5 externalNativeOptions + abiFilters [arm64-v8a, x86_64]
├── libs/{arm64-v8a,x86_64}/ linkDebugShared* 经 copyKnSo* 回填的三件套(gitignore)
├── src/main/cpp/CMakeLists.txt IMPORTED 引入 KN so、$ORIGIN rpath
├── src/main/cpp/napi_init.cpp N-API 导出、参数检查、DisposeString
├── src/main/cpp/include/libohoscryptography_api.h
├── src/main/cpp/types/libcryptography_napi/{index.d.ts, oh-package.json5}
└── src/main/ets/pages/Index.ets ArkUI 验收页面
6.2 构建、签名和安装
./gradlew :cryptography-bigint:jvmTest :cryptography-random:jvmTest # 交叉验证
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
cd example/harmonyApp
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon
签名在 DevEco Studio 的 File → Project Structure → Signing Configs 中勾选自动生成,证书写入本机 ~/.ohos/config,不进入仓库(仓库内 build-profile.json5 的 signingConfigs 保持空数组)。
hdc list targets -v
hdc -t <设备序列号> install -r \
entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start \
-a EntryAbility -b org.terminator.ohos.cryptography -m entry
四、完整代码对照
4.1 整体架构
dev.whyoleg.cryptography.bigint/**(上游原样编译)
│ BigInt / magnitude / hex / compareTo
dev.whyoleg.cryptography.random/**(上游原样编译 + 1 个 ohos actual)
│ CryptographyRandom.Default → /dev/urandom
dev.whyoleg.cryptography.serialization.pem|asn1|modules/**(上游原样编译)
│ PemDocument / PemLabel / Der / ObjectIdentifier / RFC 5280 结构
dev.whyoleg.cryptography/**(core,上游原样编译)
│ CryptographyProvider 注册机制 / 算法 API 门面(provider 运行时可选)
Bridge.kt + MiniJson
│ CkDispatch / CkVersion
libcryptography_napi.so
│ ckDispatch / ckVersion
Index.ets
│ JSON parse + @State 更新
▼
ArkUI 真机页面(单页滚动)
4.2 文件清单
| 文件 | 职责 |
|---|---|
settings.gradle.kts | 极简 ohos 构建、eazytec Maven 置前 + content filter、ohos_cryptography_kotlin、providers 移出 |
gradle/libs.versions.toml | Kotlin 2.4.20-0.1.0-07 + ohos kotlinx 制品线钉版(io 0.9.1-0.1.0-07 / serialization 1.11.0-0.1.0-07) |
gradle.properties | JDK 21 钉死、version=0.5.0-ohos.1、ignoreDisabledTargets / downloadFromMaven |
gradle/wrapper/gradle-wrapper.properties | 9.4.1 → 9.6.1(审核矩阵对齐,见 2.2) |
build.gradle.kts(根) | 版本消费、全局禁用 Ohos*Test* 任务 |
6 个模块 build.gradle.kts | 纯 KMP:jvm + ohosArm64 + ohosX64,上游 hierarchy / explicitApi / 语言开关保留 |
cryptography-random/src/ohosMain/.../CryptographyRandom.ohos.kt | 唯一新增库源文件(/dev/urandom) |
example/nativeApp/build.gradle.kts | 双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填(含 api 头) |
nativeApp/.../Bridge.kt | 2 个 C ABI、8 op、MiniJson、错误通道 |
harmonyApp/.../cpp/napi_init.cpp | N-API 导出、参数检查、DisposeString |
harmonyApp/.../cpp/CMakeLists.txt | IMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套 |
harmonyApp/.../ets/pages/Index.ets | ArkUI 验收页面 |
README.OpenHarmony_CN.md | 鸿蒙适配主文档(12 节) |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| 引擎 | CryptographyRandom.Default.nextBytes(size) | 安全随机(ohos → /dev/urandom actual) |
| 引擎 | PemDocument(PemLabel(l), bytes) / .encodeToString() | RFC 7468 PEM 编码 |
| 引擎 | PemDocument.decode(text) | PEM 解码(label + content) |
| 引擎 | Der.encodeToByteArray<T>(value) / decodeFromByteArray<T>(bytes) | ASN.1 DER 编解码(kotlinx-serialization BinaryFormat) |
| 引擎 | RsaAlgorithmIdentifier / EcAlgorithmIdentifier(EcParameters(oid)) 等 | RFC 5280 算法标识结构 |
| 引擎 | BigInt.fromMagnitude(sign, magnitude) / String.hexToBigInt() | 大整数构造与解析 |
| Native | CkDispatch / CkVersion | C ABI JSON 导出 |
| N-API | ckDispatch(req) / ckVersion() | 向 ArkTS 暴露能力 |
| ArkTS | dispatch(req) | 触发操作并刷新 UI |
4.4 ArkTS 与 Kotlin 的边界
const res = JSON.parse(ckDispatch(JSON.stringify({
op: 'asn1Encode', algorithm: 'rsa'
})));
// res.ok === true; res.oid === '1.2.840.113549.1.1.1';
// res.derHex === '300d06092a864886f70d0101010500' (SEQUENCE{OID rsaEncryption, NULL})
const back = JSON.parse(ckDispatch(JSON.stringify({
op: 'asn1Decode', derHex: res.derHex
})));
// back.oid === res.oid && back.roundtrip === true
val value: AlgorithmIdentifier = when (json.str("algorithm")) {
"rsa" -> RsaAlgorithmIdentifier
"ed25519" -> Ed25519AlgorithmIdentifier
"ec-p256" -> EcAlgorithmIdentifier(EcParameters(ObjectIdentifier.secp256r1))
"ec-p384" -> EcAlgorithmIdentifier(EcParameters(ObjectIdentifier.secp384r1))
else -> throw IllegalArgumentException("unsupported algorithm")
}
val der = Der.encodeToByteArray<AlgorithmIdentifier>(value)
两者之间只传输扁平 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构。
五、关键决策说明
决策 1:边界放在 provider 无关层,而不是硬啃算法引擎
上游 9 个 provider 模块全部依赖平台密码设施:openssl3 系要 cinterop 链接预构建 libcrypto(ohos 社区尚无制品)、jdk 绑定 JCA、apple 绑定 CoreCrypto / CryptoKit、webcrypto 绑定 JS。硬把它们拉进构建等于先做一轮 OHOS OpenSSL 交叉编译工程,评审面和风险面完全失控。而 bigint / random / pem / asn1 / core 五层是 100% 纯 Kotlin,且是"任何算法引擎落地前都必须先在"的底座——序列化层与随机源先行,算法层等制品线补齐(见 8.3),这是密码学库特有的分层适配路线。
决策 2:工具链升到 Kotlin 2.4.20-0.1.0-07,Gradle 主动对齐 9.6.1
上游 Kotlin 2.3.20 按社区规则选 ≥ 上游的最近鸿蒙线(KSafe 先例:上游 2.3.21 → 2.4.20-0.1.0-07),kotlinx 制品线也按这个 stdlib 构建。Gradle 没有沿用上游 9.4.1:fork 未发布 kotlin-native-compiler-embeddable,9.4.1 上 KGP 走 in-process 编译路径必炸 ClassNotFoundException;9.6.1 是已审核仓库的公共矩阵(该组合走 Kotlin daemon 路径)。鸿蒙适配的 Gradle 版本是工具链矩阵的一部分,不是"上游是什么就留什么"。
决策 3:根构建替换(cmp_qrose 模式),providers 与发布设施留盘
上游 build-logic / build-settings 双 includeBuild 体系(kover / dokka / vanniktech 发布 / xcframework / AGP / OpenSSL ivy)对鸿蒙没有保留价值,整个替换为极简 ohos 构建。providers 等模块留盘不进 settings.gradle.kts——上游源码树完整保留,制品线补齐后加回 include 即可,不存在信息丢失。
决策 4:无状态 JSON 桥 + 二进制双格式出参
密码学操作无会话状态,桥不需要对象注册表,C ABI 生命周期只剩字符串 Dispose。二进制出参统一 hex + base64 双格式:hex 面向展示与 roundtrip 校验,base64 面向 ArkTS Base64Helper 交叉验证字节数——每条二进制链路都有两种独立观察方式。
决策 5:jvmTest 交叉验证 + 测试零改动
逻辑在 commonMain,JVM 与 ohos 跑同一份代码;6 个模块的 jvmTest 跑的就是上游原测试文件(bigint 幅度算术、ASN.1 编解码矩阵、PEM 往返、random 统计等),是"内核没漂"的闸门。处置不是改测试,而是"测试统一 JVM + 根构建禁用 Ohos*Test*"——测试文件一个字节不动,交叉验证的有效性才成立。
六、测试与验证
6.1 测试环境
本次验证使用 Windows(x86_64)、JDK 21(Temurin)、Gradle 9.6.1、HarmonyOS Kotlin 2.4.20-0.1.0-07、DevEco Studio 5.1.0 Release、OpenHarmony 命令行工具(hvigor / hdc)与鸿蒙真机(arm64)。
6.2 JVM 交叉验证
./gradlew :cryptography-bigint:jvmTest :cryptography-random:jvmTest \
:cryptography-serialization-pem:jvmTest \
:cryptography-serialization-asn1:jvmTest \
:cryptography-serialization-asn1-modules:jvmTest :cryptography-core:jvmTest
6 个模块的上游测试套件全部通过(测试文件零改动)。依赖换线没有 API 漂移:kotlinx-io 0.9.0→0.9.1、serialization 1.10.0→1.11.0 换到 -0.1.0-07 线后,上游测试照跑全绿。
6.3 HAP 验证
:6×compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
llvm-nm -D: CkDispatch / CkVersion 均为全局 T 符号
libohoscryptography.so: arm64 6,118,792 B(约 5.8 MiB)/ x86_64 5,655,552 B(约 5.4 MiB)
BuildNativeWithCmake: finished(双 ABI 编出 libcryptography_napi.so)
CompileArkTS: finished
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully
6.4 功能用例
- 启动后版本握手面板显示
ckVersion()真实返回:fork0.5.0-ohos.1/ upstream0.5.0(main6081777e)/ Kotlin2.4.20-0.1.0-07/ targetsohosArm64 + ohosX64/ modules 六项; - 点【随机数 32B】,返回 64 位 hex 与 base64,ArkTS
Base64Helperdecode→reencode 校验通过、字节数 32; - 点【PEM 编码】,输入框文本变成
-----BEGIN OPENHARMONY DEMO-----开头的完整 PEM 并回填 PEM 框; - 点【PEM 解码】,label 回读
OPENHARMONY DEMO、字节数与 base64 和编码侧一致,可打印内容附 text 预览; - 点【ASN.1 rsa】显示
oid=1.2.840.113549.1.1.1与 DER hex,随即自动asn1Decode回读,roundtrip=true位一致; - 点【ASN.1 ec-p256】显示
oid=1.2.840.10045.2.1(参数secp256r1)同样 roundtrip 通过; - 点【大整数 2048bit】,
/dev/urandom生成 256 字节幅度 →BigInt.fromMagnitude,显示 617 位十进制与 hex; - 点【大整数比较】,预设
2^128-1与1比较,result=1(a > b); - 每步操作的操作日志按时间戳滚动留痕,
→/←成对出现。
6.5 验证结论
安全随机源、PEM / ASN.1 序列化运行时、BigInt 底座、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的密码学会话:版本握手、随机数、PEM 往返、ASN.1 DER 往返校验与大整数运算(见下节效果图)。
七、运行效果
7.1 真机截图

7.2 命令速查
git clone https://atomgit.com/oh-tpc/cryptography-kotlin.git
cd cryptography-kotlin
export JAVA_HOME="/path/to/jdk-21"
./gradlew :cryptography-core:compileKotlinOhosArm64 :cryptography-core:compileKotlinOhosX64 # klib
./gradlew :cryptography-bigint:jvmTest :cryptography-random:jvmTest # 交叉验证
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
cd example/harmonyApp
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b org.terminator.ohos.cryptography -m entry
hdc shell hilog | grep Cryptography
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配,必须让 6 个库模块真正编译成
ohosArm64/ohosX64动态库; settings.gradle.kts里 eazytec 双仓必须排在pluginManagement与dependencyResolutionManagement最前,否则配置期直接Unresolved reference: ohosArm64;同时要按 KSafe 先例加includeGroupByRegex("org[.]jetbrains.*")content filter,否则外来 group 会被 eazytec 的元数据代理劫持导致绑定失败;- Gradle 9.4.1 上 KGP 的 K/N 编译走 in-process 路径,而鸿蒙 fork 未发布
kotlin-native-compiler-embeddable(eazytec 404),报ClassNotFoundException: org.jetbrains.kotlin.cli.utilities.MainKt——解法不是降级 Kotlin,而是把 Gradle 对齐到已审核矩阵 9.6.1(Kotlin daemon 路径); - 残留 Kotlin daemon 会锁死编译器 jar:其他项目留下的编译 daemon 持有
kotlin-native-compiler-embeddable文件句柄,konan 分发重解压时写不进 jar,留下provisioned.ok标记但konan/lib为空的残缺目录——先taskkill掉kotlin-build-tools-compat类 daemon 再删目录重解; - konan 的 OHOS LLVM 工具链可能缺 clang++/lld:
cpf-llvm-19-x86_64-windows-dev-21若缺编译器主程序,CAdapter 阶段报CreateProcess error=2;DevEco SDK 自带 LLVM 15.0.4 顶替会报Unknown command line argument '-enable-kotlin-stub-generator'(无 Kotlin 补丁)——正解是从 eazytecfile-storage/llvm/cpf-llvm-19-x86_64-windows-dev-21.zip重新下载完整包恢复; - 上游 2.3.20 的 progressive 模式在 2.4.20 fork 上不含
UnnamedLocalVariables(pem 源码使用),需要显式-XXLanguage:+UnnamedLocalVariables;-Xexpect-actual-classes与explicitApi()也要在模块级补回; - 上游
PemContent已DeprecationLevel.ERROR,鸿蒙侧直接用PemDocument;UnknownKeyAlgorithmIdentifier同为 ERROR 级废弃,自定义 OID 演示改用EcAlgorithmIdentifier(EcParameters(secp384r1)); - ArkTS
util.Base64Helper的encodeToString是异步版(返回Promise<string>),同步版叫encodeToStringSync——赋值给string时编译器才报错; - 上游 linux random 的
SYS_getrandom硬编码 318(x86_64),arm64 是 278,不能把 ohos 塞进linuxAndAndroidNative分组照抄;ohos actual 直读/dev/urandom(musl 底座语义一致且永阻塞风险相同); - HAP 缺当前 ABI 的 so 会报安装失败
9568347,abiFilters必须双 ABI,每个 ABI 三件套(libcryptography_napi.so/libohoscryptography.so/libc++_shared.so)缺一不可; - KN 返回字符串必须
DisposeString归还运行时,N-API 层不能free(); - DevEco 打开工程后生成的
entry/oh_modules/、.clangd、.clang-tidy会混进提交,gitignore 要按嵌套路径单独追加;签名材料由 IDE 自动写入build-profile.json5,提交前用git update-index --skip-worktree保护,证书绝不进入 AtomGit。
8.2 已知问题
- 算法引擎层(AES / RSA / ECDSA / SHA 等全部 provider)未编译到 ohos:openssl3 系需要 OHOS 预构建 libcrypto(社区尚无),jdk / apple / webcrypto / cryptokit 是平台绑定;本 fork 的 include 列表里没有它们;
- ohos 上
CryptographyProvider.Default无注册 provider,调用会抛No providers registered(core 的 provider 机制是运行时可选的,不影响其余 API 编译与使用); optimalprovider 无法路由(它是按平台挑 provider 的聚合层,ohos 无可选对象);BigInt是序列化级大整数(ASN.1 INTEGER 的底座),公开 API 无plus/times算术运算符,Demo 的大整数操作相应设计为构造、解析与compareTo;- android / js / wasmJs / apple / linux / windows / androidNative 目标剥离(源文件留在树里),发布、dokka、kover 一并剥离,本 fork 不发 Maven Central;
- HAP 安装必须使用与 bundle name
org.terminator.ohos.cryptography匹配的签名。
8.3 未来优化方向
- 制品线补齐后引入 OHOS 预构建 OpenSSL 3.x,把
openssl3-api/openssl3-prebuiltprovider 拉进 ohos 构建,算法引擎全量落地(SHA / HMAC / AES-GCM / RSA / ECDSA / 密钥生成); - 评估 HUKS(鸿蒙通用密钥库系统)作为原生 provider 的可行性——密码学库的 provider 架构恰好为这类平台引擎预留了接缝;
- 桥补
asn1Decode对更多 RFC 5280 结构(SubjectPublicKeyInfo、Certificate)的演示入口; - 评估
.har形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套; - 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
- 补充模拟器(x86_64)与其他设备 ABI 的验收矩阵和自动化截图脚本。
适配过程 FAQ
以下问题来自
ohosArm64目标接入、NAPI 桥接、HAP 构建与真机验收的实际流程,按出现频率排序。
Q1:DevEco Studio Sync 失败或 ohpm 依赖下载超时,怎么处理?
- 确认 DevEco Studio 版本与工程
build-profile.json5的compatibleSdkVersion一致; - 在
File > Settings > Build, Execution, Deployment > Ohpm配置可用的 ohpm 仓库镜像或代理; - 删除
oh_modules、oh-package-lock.json5与.hvigor缓存后重新 Sync; - 公司网络需要代理时,同时给 ohpm 与 hvigor 配置
http.proxy。
Q2:CMake 报 Missing .../libxxx.so 或找不到 Kotlin/Native 动态库?
- 先在
example目录执行./gradlew :nativeApp:linkDebugSharedOhosArm64(模拟器用linkDebugSharedOhosX64); - 确认
libxxx.so与libc++_shared.so已复制到ohos/entry/libs/<abi>/; - 检查
entry/build-profile.json5的abiFilters与设备 ABI 是否一致(真机arm64-v8a,模拟器x86_64); - 清理
entry/.cxx与entry/build后重试。
Q3:ArkTS 报 Cannot find module 'libxxx_napi.so' 或调用返回 undefined?
- 确认 CMake 的
add_library(<target> SHARED ...)名称与import ... from 'libxxx_napi.so'完全一致; - 解包 HAP,确认
libs/<abi>/libxxx_napi.so已打包; - 删除
entry/build、entry/.cxx与oh_modules后重新构建; - 检查
module.json5的srcEntry与页面路径是否正确。
Q4:真机安装报 signature verification failed 或 install failed?
- 在 DevEco Studio 的
Signing Configs配置证书、Profile 与密码; bundleName必须与签名 profile 中的 bundle name 一致;- 设备需加入 profile 的
deviceIds,调试证书有效期需覆盖当前时间; - 用
hdc list targets确认设备在线,再执行hdc install -r xxx-signed.hap。
Q5:应用启动白屏、闪退或 NAPI 调用崩溃?
- 用
hdc hilog | grep -i "napi\|kotlin"查看原生日志; - 确认
libc++_shared.so与 Kotlin/Native 工具链版本匹配,且 ABI 与设备一致; - 检查 C ABI 返回的字符串是否由 Native 侧分配、ArkTS 侧复制后是否调用
XxxFree释放; - 在 DevEco 的 Native 调试配置中附加
lldb,定位空指针与越界访问。
Q6:Gradle 报 ohosArm64 目标不存在或 Kotlin/Native 编译失败?
- 使用鸿蒙发行版 Kotlin(如
2.2.21-1.0.0),并在settings.gradle.kts配置 eazytec 仓库; - Gradle 建议 8.x、JDK 21,版本不匹配时先统一工具链;
- 确认
kotlin { ohosArm64() }已应用,且local.properties的 SDK 路径有效; - 清理
~/.gradle/caches中对应版本的 Kotlin/Native 缓存后重试。
Q7:HAP 构建报 SDK 版本不匹配或 compatibleSdkVersion 错误?
- 对齐 DevEco Studio、HarmonyOS SDK 与
build-profile.json5中的compatibleSdkVersion; - 升级 DevEco Studio 后同步升级 hvigor 与 SDK,避免
modelVersion与 IDE 不匹配; - 命令行构建时设置
DEVECO_SDK_HOME指向 DevEco 的sdk目录。
Q8:命令行 hvigor 构建失败,但 DevEco 里能构建?
- 使用 DevEco 自带的 node 与 hvigor:
"$DEVECO/Contents/tools/node/bin/node" "$DEVECO/Contents/tools/hvigor/bin/hvigorw.js" ...; - 设置
JAVA_HOME、NODE_HOME、DEVECO_SDK_HOME; - 加上
--no-daemon排除守护进程缓存影响,并清理entry/build与.hvigor。
九、总结
9.1 核心难点回顾
上游 6 个 provider 无关模块(commonMain 为主)
→ 验证 ohos kotlinx 制品线(io / serialization 双件套)
→ 工具链对齐 Kotlin 2.4.20-0.1.0-07 + Gradle 9.6.1(矩阵对齐,非版本玄学)
→ 根构建替换(cmp_qrose 模式,providers 留盘)
→ ohosArm64 / ohosX64 KLIB + 唯一 /dev/urandom actual
→ C ABI(@CName × 2)+ 扁平 JSON(8 op)
→ C++ N-API(DisposeString 纪律)
→ ArkTS JSON 解析 + Base64 交叉校验
→ ArkUI 真机单页验收(握手 / 随机 / PEM / ASN.1 roundtrip / BigInt)
9.2 封装层次
- 引擎层:上游原版
CryptographyRandom、PemDocument、Der编解码器与 RFC 5280 结构、BigInt; - core 层:上游原版 provider 注册机制与类型安全算法门面(provider 运行时可选);
- Native 层:生成双 ABI 动态库并通过 2 个 C ABI 输出 JSON;
- N-API 层:完成参数检查、字符串转换和 Native 内存释放;
- ArkTS 层:管理页面状态、JSON 组包、base64 交叉校验、日志与错误文本;
- DevEco 层:完成 CMake、HAP、签名、安装和真机运行。
9.3 三条经验
- 密码学库的适配边界由"制品可达性"决定:算法引擎依赖平台密码制品(ohos 暂无 libcrypto 预构建),而 provider 无关层是纯 Kotlin——把边界画在制品断点处,序列化与随机先行、算法引擎后补,比硬啃交叉编译 OpenSSL 稳一个数量级;
- 鸿蒙工具链是一个矩阵,不是一个版本号:Kotlin 钉版由 kotlinx 制品线的 stdlib 反推(2.4.20-0.1.0-07),Gradle 由已审核组合对齐(9.6.1),konan 分发、LLVM 工具链、daemon 进程模型环环相扣——任何一环错位(如 in-process 编译器缺失、LLVM 缺 clang++)都会以毫不相干的报错面貌出现,排查时先对矩阵再查代码;
- 交叉验证是"内核没漂"的唯一可信证明:6 个模块的
jvmTest跑的是上游原测试文件,测试零改动才让"换线不换版"有说服力;密码学库没有渲染面,Demo 的验收重点天然落在 roundtrip 位一致(ASN.1 编解码往返)与字节级复核(base64 双侧校验)这类可机械判定的不变量上。
更多推荐




所有评论(0)