本文记录 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 5280 AlgorithmIdentifier 结构在真机上的完整往返;
  • BigInt:随机 2048bit 幅度经 BigInt.fromMagnitude 变成大整数,输出十进制 / 十六进制 / 符号 / 字节数;两个 hex 大整数做 compareTo 比较。

响应统一为:

字段作用
ok操作是否成功,失败时 error 带原文。
op回显本次操作。
hex / base64random 的双格式输出。
pem / label / textPEM 编解码的主载荷与回读字段。
oid / derHex / roundtripASN.1 的算法 OID、DER 十六进制与往返校验位。
decimal / sign / resultBigInt 的十进制串、符号与比较结果。

引擎侧完整能力(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 MultiplatformHarmonyOS Kotlin 2.4.20-0.1.0-07(eazytec Maven)
JDK21 Temurin(org.gradle.java.home 钉进 gradle.properties)
OpenHarmony 目标ohosArm64 + ohosX64(jvm 保留作交叉验证)
Gradle9.6.1(上游 9.4.1 主动升级,与 KSafe / KStore 审核矩阵一致,原因见 2.2)
kotlinxkotlinx-io-core 0.9.1-0.1.0-07、kotlinx-serialization-core 1.11.0-0.1.0-07(ohos 制品线)
ABIarm64-v8a + x86_64
Bundleorg.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-randomexpect defaultCryptographyRandom() + 7 平台 actual零外部依赖整体编译 + 新增 ohos actual
cryptography-serialization-pem100% commonMainkotlinx-io-core整体编译(零平台源码集)
cryptography-serialization-asn1100% commonMainserialization-core + bigint整体编译
...asn1-modules100% commonMain(RFC 5280 等结构定义)asn1整体编译
cryptography-corecommonMain + 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 方法覆盖内核
CkDispatchckDispatch(req)random / PEM 编解码 / ASN.1 编解码 / BigInt
CkVersionckVersion()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.tomlKotlin 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.propertiesJDK 21 钉死、version=0.5.0-ohos.1、ignoreDisabledTargets / downloadFromMaven
gradle/wrapper/gradle-wrapper.properties9.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.kt2 个 C ABI、8 op、MiniJson、错误通道
harmonyApp/.../cpp/napi_init.cppN-API 导出、参数检查、DisposeString
harmonyApp/.../cpp/CMakeLists.txtIMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
harmonyApp/.../ets/pages/Index.etsArkUI 验收页面
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()大整数构造与解析
NativeCkDispatch / CkVersionC ABI JSON 导出
N-APIckDispatch(req) / ckVersion()向 ArkTS 暴露能力
ArkTSdispatch(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 功能用例

  1. 启动后版本握手面板显示 ckVersion() 真实返回:fork 0.5.0-ohos.1 / upstream 0.5.0(main 6081777e)/ Kotlin 2.4.20-0.1.0-07 / targets ohosArm64 + ohosX64 / modules 六项;
  2. 点【随机数 32B】,返回 64 位 hex 与 base64,ArkTS Base64Helper decode→reencode 校验通过、字节数 32;
  3. 点【PEM 编码】,输入框文本变成 -----BEGIN OPENHARMONY DEMO----- 开头的完整 PEM 并回填 PEM 框;
  4. 点【PEM 解码】,label 回读 OPENHARMONY DEMO、字节数与 base64 和编码侧一致,可打印内容附 text 预览;
  5. 点【ASN.1 rsa】显示 oid=1.2.840.113549.1.1.1 与 DER hex,随即自动 asn1Decode 回读,roundtrip=true 位一致;
  6. 点【ASN.1 ec-p256】显示 oid=1.2.840.10045.2.1(参数 secp256r1)同样 roundtrip 通过;
  7. 点【大整数 2048bit】,/dev/urandom 生成 256 字节幅度 → BigInt.fromMagnitude,显示 617 位十进制与 hex;
  8. 点【大整数比较】,预设 2^128-1 与 1 比较,result=1(a > b);
  9. 每步操作的操作日志按时间戳滚动留痕,→/← 成对出现。

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 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配,必须让 6 个库模块真正编译成 ohosArm64 / ohosX64 动态库;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;同时要按 KSafe 先例加 includeGroupByRegex("org[.]jetbrains.*") content filter,否则外来 group 会被 eazytec 的元数据代理劫持导致绑定失败;
  3. 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 路径);
  4. 残留 Kotlin daemon 会锁死编译器 jar:其他项目留下的编译 daemon 持有 kotlin-native-compiler-embeddable 文件句柄,konan 分发重解压时写不进 jar,留下 provisioned.ok 标记但 konan/lib 为空的残缺目录——先 taskkill 掉 kotlin-build-tools-compat 类 daemon 再删目录重解;
  5. 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 补丁)——正解是从 eazytec file-storage/llvm/cpf-llvm-19-x86_64-windows-dev-21.zip 重新下载完整包恢复;
  6. 上游 2.3.20 的 progressive 模式在 2.4.20 fork 上不含 UnnamedLocalVariables(pem 源码使用),需要显式 -XXLanguage:+UnnamedLocalVariables;-Xexpect-actual-classes 与 explicitApi() 也要在模块级补回;
  7. 上游 PemContent 已 DeprecationLevel.ERROR,鸿蒙侧直接用 PemDocument;UnknownKeyAlgorithmIdentifier 同为 ERROR 级废弃,自定义 OID 演示改用 EcAlgorithmIdentifier(EcParameters(secp384r1));
  8. ArkTS util.Base64Helper 的 encodeToString 是异步版(返回 Promise<string>),同步版叫 encodeToStringSync——赋值给 string 时编译器才报错;
  9. 上游 linux random 的 SYS_getrandom 硬编码 318(x86_64),arm64 是 278,不能把 ohos 塞进 linuxAndAndroidNative 分组照抄;ohos actual 直读 /dev/urandom(musl 底座语义一致且永阻塞风险相同);
  10. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI,每个 ABI 三件套(libcryptography_napi.so / libohoscryptography.so / libc++_shared.so)缺一不可;
  11. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();
  12. 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 编译与使用);
  • optimal provider 无法路由(它是按平台挑 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-prebuilt provider 拉进 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 三条经验

  1. 密码学库的适配边界由"制品可达性"决定:算法引擎依赖平台密码制品(ohos 暂无 libcrypto 预构建),而 provider 无关层是纯 Kotlin——把边界画在制品断点处,序列化与随机先行、算法引擎后补,比硬啃交叉编译 OpenSSL 稳一个数量级;
  2. 鸿蒙工具链是一个矩阵,不是一个版本号:Kotlin 钉版由 kotlinx 制品线的 stdlib 反推(2.4.20-0.1.0-07),Gradle 由已审核组合对齐(9.6.1),konan 分发、LLVM 工具链、daemon 进程模型环环相扣——任何一环错位(如 in-process 编译器缺失、LLVM 缺 clang++)都会以毫不相干的报错面貌出现,排查时先对矩阵再查代码;
  3. 交叉验证是"内核没漂"的唯一可信证明:6 个模块的 jvmTest 跑的是上游原测试文件,测试零改动才让"换线不换版"有说服力;密码学库没有渲染面,Demo 的验收重点天然落在 roundtrip 位一致(ASN.1 编解码往返)与字节级复核(base64 双侧校验)这类可机械判定的不变量上。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐