1. 项目概述与背景
最近在做一个跨平台应用,核心需求是在鸿蒙(OpenHarmony)和安卓上共享一套业务逻辑,同时要确保用户敏感数据(比如登录凭证、本地缓存)的安全。Kotlin Multiplatform(KMP)自然成了首选,它允许我们用Kotlin写一次核心逻辑,然后分别编译到JVM(Android)和JavaScript(HarmonyOS)。但问题来了,数据加密这块,两边平台的原生API差异不小,直接调用平台特定代码就失去了KMP“共享逻辑”的意义。所以,我的目标很明确:在KMP的共享模块(commonMain)里,实现一套统一、安全、可复用的数据加密方案,让鸿蒙的ArkTS和安卓的Kotlin/Java都能无缝调用。
这不仅仅是调用一个加密库那么简单。它涉及到在KMP的约束下,如何选择加密算法、如何管理密钥、如何处理平台差异,以及最终如何将加密后的数据安全地存储或传输。网上关于KMP结合加密的资料比较零散,特别是针对鸿蒙端(ArkTS调用Kotlin编译的JS)的实践更少。这次我就把踩过的坑和最终跑通的方案详细拆解一下,如果你也在做跨平台的安全功能,这篇内容应该能帮你省下不少折腾的时间。
简单说,这个方案的核心价值在于:用Kotlin在中心写好加密解密、哈希验证等安全逻辑,一套代码同时保障安卓App和鸿蒙应用的数据安全,避免了两边各自实现可能带来的不一致性和安全漏洞。
2. 技术选型与架构设计
2.1 为什么选KMP+自研加密模块?
首先得回答为什么不用现成的跨平台加密库,或者分别在两端实现。现成的跨平台C库(如OpenSSL绑定)对KMP的支持尚不成熟,集成复杂度高,且可能增加包体积。分别在Android(用Security或AndroidX Security)和HarmonyOS(用@ohos.security.cryptoFramework)实现,则会导致业务逻辑重复,且一旦加密策略需要调整(比如密钥轮换、算法升级),需要改两个地方,维护成本高。
KMP的共享模块(commonMain)是纯Kotlin代码,可以编译成JVM字节码和JS代码。我们的加密核心逻辑放在这里,就能同时服务于两个平台。架构上,我采用了分层设计:
- 接口层(commonMain):定义加密、解密、哈希等操作的通用接口(如
interface CryptoService)。 - 实现层(commonMain):使用纯Kotlin/JVM可用的加密库实现上述接口。这里的关键是,选择的库必须在Kotlin/JVM和Kotlin/JS两个目标平台都能工作。
- 平台适配层(androidMain/harmonyMain):原则上,核心逻辑都在commonMain,平台层应该很薄。但有些平台特定的优化或密钥存储(如Android的KeyStore、HarmonyOS的
huks)需要在这里做桥接。在本方案中,为了最大化代码共享,我选择在commonMain中实现一个基于密码的密钥派生方案,而将最敏感的根密钥种子(或加密后的密钥)的存储,委托给平台特定的安全存储(这部分的调用会通过expect/actual机制实现)。
2.2 加密库的选择:Bouncy Castle还是Kotlin自带的?
在commonMain里,我们不能直接用java.security,因为JS目标平台不认识它。所以需要找一个纯Kotlin实现或支持多平台的加密库。
- 选项A:
Bouncy Castle(BC):功能极其强大且久经考验,但它本质上是一个Java库。虽然可以通过bcprov-jdk18on在Android端使用,但无法直接编译到JS。有人尝试通过Kotlin/JS的IR编译器或wasm来移植,但过程复杂且不稳定,对新手不友好。 - 选项B:
kotlinx.crypto:这是JetBrains官方维护的一个实验性库,旨在提供跨平台的加密原语。但目前(截至我实践时)其功能较为基础,且仍处于实验阶段,用于生产环境有风险。 - 选项C:纯Kotlin实现的轻量级库:例如
Tink的Kotlin版本或者一些专注于特定算法(如AES-GCM)的轻量库。Tink是谷歌出品的安全库,设计精良,但其KMP支持也还在演进中。
我的选择与折中:考虑到项目进度和稳定性,我决定采用一个务实的策略:对于最核心的对称加密(AES),使用一个经过审计的、纯Kotlin实现的AES库(例如aes-kt)。对于哈希(SHA256)和密码学安全随机数生成,Kotlin/JS环境可以通过crypto.subtle(Web Crypto API)的互操作性来间接实现,但这需要写一些JS胶水代码。为了让commonMain代码更统一,我最终选择了一个名为krypto的多平台实验性库的简化分支,它用纯Kotlin实现了AES和SHA256,虽然功能不及其它大型库全面,但对于常见的“加密一个字符串或一段JSON”的需求完全够用,且依赖简单。
关键决策点:如果你的应用加密需求非常复杂(需要多种算法、数字签名、证书操作),那么花精力集成
Tink或寻找成熟的KMP加密方案是值得的。但如果主要是对本地存储数据进行AES加密,以及对密码进行哈希,那么一个轻量的、纯Kotlin的AES+SHA256实现是快速启动项目的最佳选择。切记,不要自己实现加密算法,使用经过验证的库。
2.3 整体架构图(概念层面)
[Android App (Kotlin/JVM)] [HarmonyOS App (ArkTS)] | | | (调用共享模块编译后的产物) | (调用共享模块编译后的JS模块) | | ▼ ▼ [KMP Shared Module - commonMain] <-- 核心加密逻辑在此 | |--- CryptoManager (单例入口) |--- AESGCMEncryptor (实现AES-GCM加密/解密) |--- SHA256Hasher (实现哈希计算) |--- SecureRandomGenerator (安全随机数生成) |--- KeyDerivation (基于密码的密钥派生,PBKDF2) | |--- expect/actual | |--- SecureKeyStorage (平台安全存储接口) | |--- actual Android: 委托给 Android Keystore | |--- actual HarmonyOS: 委托给 @ohos.security.huks这个架构确保了加密的核心流程(如何加密、如何解密、如何哈希)在commonMain中只有一份代码。平台特定的部分被隔离在expect/actual声明的SecureKeyStorage中,用于安全地保存那个最关键的“主密钥”或加密密钥的密文。
3. 核心加密模块实现详解
3.1 密钥管理:安全的核心
所有加密系统的弱点往往在密钥管理。我们的目标是:应用密钥不能以明文形式硬编码在代码中,也不能轻易从存储中提取。
方案:基于用户/设备密码的密钥派生 + 平台安全存储
- 根密钥种子:我们不在代码里写死密钥。而是会在应用首次启动时,生成一个高熵的随机字节数组作为“根密钥种子”。这个种子本身不能直接用作加密密钥。
- 密钥派生:当需要加密数据时,我们使用PBKDF2 (Password-Based Key Derivation Function 2)算法,结合以下要素派生出实际的加密密钥:
- 密码 (Password):可以是一个由应用生成的、对用户透明的复杂字符串(存储在平台安全存储中),或者在某些场景下,是用户输入的生物特征或PIN码的派生值。为了简化,本例使用一个应用内固定的“服务密码”。
- 盐 (Salt):一个随机生成的、与加密数据一起存储的盐值。防止彩虹表攻击,确保即使相同的密码,每次派生出的密钥也不同。
- 迭代次数 (Iterations):增加计算成本,抵御暴力破解(建议10万次以上)。
- 种子存储:派生密钥的“根密钥种子”或“服务密码”,必须存储在平台提供的最高安全级别的存储中。
- Android端:使用
AndroidKeyStore。它可以将密钥材料保存在硬件安全模块(HSM)或可信执行环境(TEE)中,操作系统外的应用无法直接读取。 - HarmonyOS端:使用
@ohos.security.huks(HUKS)。它提供了类似的硬件级密钥保护能力。
- Android端:使用
在commonMain中,我们这样设计:
// commonMain/kotlin/security/KeyManager.kt expect class SecureKeyStorage { fun saveSeed(seed: ByteArray): Boolean fun getSeed(): ByteArray? fun clearSeed(): Boolean } // commonMain/kotlin/security/KeyDerivation.kt object KeyDerivation { private const val PBKDF2_ITERATIONS = 100_000 private const val KEY_LENGTH_BITS = 256 // AES-256 fun deriveKeyFromPassword(password: String, salt: ByteArray): ByteArray { // 这里使用一个纯Kotlin的PBKDF2实现,或调用平台API(通过expect/actual)。 // 为简化,此处展示逻辑。实际应使用如`CommonCrypto`的KMP绑定或纯Kotlin实现。 // 伪代码:return PBKDF2(password, salt, ITERATIONS, KEY_LENGTH_BITS) // 假设我们有一个跨平台的PBKDF2函数 return pbkdf2WithHmacSHA256( password = password, salt = salt, iterationCount = PBKDF2_ITERATIONS, keyLengthBytes = KEY_LENGTH_BITS / 8 ) } }3.2 AES-GCM 加密/解密实现
AES-GCM(Galois/Counter Mode)是目前推荐的对称加密模式,因为它同时提供了保密性(加密)和完整性(认证)。它不需要单独的填充方案,且效率较高。
在commonMain中,我们实现一个AESGCMEncryptor:
// commonMain/kotlin/security/AESGCMEncryptor.kt class AESGCMEncryptor(private val key: ByteArray) { companion object { private const val GCM_TAG_LENGTH = 16 // 128位认证标签 private const val GCM_IV_LENGTH = 12 // 推荐96位IV } /** * 加密明文 * @param plaintext 明文字节数组 * @return 拼接了 IV + 密文 + Tag 的字节数组,方便存储 */ fun encrypt(plaintext: ByteArray): ByteArray { // 1. 生成随机的初始化向量 (IV)。每次加密都必须使用新的IV。 val iv = generateSecureRandomBytes(GCM_IV_LENGTH) // 2. 初始化加密器(这里调用我们选择的纯Kotlin AES-GCM库) // 伪代码,实际调用库的API val cipher = AesGcmCipher(key, iv, GCM_TAG_LENGTH) val ciphertextWithTag = cipher.encrypt(plaintext) // 3. 将 IV 和 (密文+Tag) 拼接在一起。IV不需要保密,但必须唯一。 return iv + ciphertextWithTag } /** * 解密密文 * @param encryptedData encrypt方法返回的字节数组 (IV + 密文 + Tag) * @return 解密后的明文字节数组 * @throws SecurityException 如果认证失败(数据被篡改) */ fun decrypt(encryptedData: ByteArray): ByteArray { if (encryptedData.size <= GCM_IV_LENGTH + GCM_TAG_LENGTH) { throw IllegalArgumentException("加密数据太短") } // 1. 分离 IV 和 (密文+Tag) val iv = encryptedData.copyOfRange(0, GCM_IV_LENGTH) val ciphertextWithTag = encryptedData.copyOfRange(GCM_IV_LENGTH, encryptedData.size) // 2. 初始化解密器并解密 // 伪代码 val cipher = AesGcmCipher(key, iv, GCM_TAG_LENGTH) return cipher.decrypt(ciphertextWithTag) // 内部会验证Tag } } // 安全随机数生成 expect fun generateSecureRandomBytes(size: Int): ByteArray关键点说明:
- IV管理:GCM模式要求IV必须是唯一的(不重复)。我们每次加密都生成一个密码学安全的随机IV,并将其与密文一起存储。IV不需要保密。
- 认证标签:GCM会自动生成一个认证标签(Tag),解密时会验证它。任何对密文或IV的篡改都会导致解密失败并抛出异常。这是我们实现数据完整性验证的关键。
- 密钥:传入的
key必须是固定长度(如32字节对应AES-256)。这个密钥由前面的KeyDerivation模块派生而来。
3.3 哈希计算(SHA256)实现
哈希用于验证数据完整性或安全地存储密码(需要加盐)。我们在commonMain中实现一个简单的工具类。
// commonMain/kotlin/security/HashUtil.kt object HashUtil { /** * 计算字节数组的SHA256哈希值 */ expect fun sha256(bytes: ByteArray): ByteArray /** * 计算字符串的SHA256哈希值,返回十六进制字符串 */ fun sha256Hex(input: String): String { val bytes = input.toByteArray(Charsets.UTF_8) val hashBytes = sha256(bytes) return bytesToHex(hashBytes) } /** * 为密码哈希加盐(使用PBKDF2或简单的加盐哈希) * 更安全的方式是使用专门的密码哈希函数如Argon2或bcrypt,但在KMP中实现较复杂。 * 这里演示加盐SHA256(仅适用于非关键场景,关键密码存储应用Argon2)。 */ fun hashPasswordWithSalt(password: String, salt: ByteArray): String { val combined = salt + password.toByteArray(Charsets.UTF_8) return sha256Hex(combined.toString(Charsets.UTF_8)) } private fun bytesToHex(bytes: ByteArray): String { val hexChars = CharArray(bytes.size * 2) for (i in bytes.indices) { val v = bytes[i].toInt() and 0xFF hexChars[i * 2] = "0123456789ABCDEF"[v ushr 4] hexChars[i * 2 + 1] = "0123456789ABCDEF"[v and 0x0F] } return String(hexChars).lowercase() } }对于expect fun sha256,我们需要在平台端实现:
- androidMain:可以使用
java.security.MessageDigest.getInstance("SHA-256”)。 - harmonyMain:由于编译为JS,我们可以通过Kotlin/JS的
external声明来调用浏览器的crypto.subtle.digestAPI,或者使用一个Kotlin/JS的SHA256 polyfill库。
4. 跨平台集成与调用
4.1 在Android端集成与调用
在androidMain源集中,我们需要实现SecureKeyStorage和generateSecureRandomBytes、sha256。
// androidMain/kotlin/security/PlatformCryptoImpl.kt actual class SecureKeyStorage { private val sharedPrefs = // ... 获取SharedPreferences实例 private val keyAlias = "my_app_master_key_seed" actual fun saveSeed(seed: ByteArray): Boolean { // 警告:将种子简单编码后存SharedPreferences是不安全的! // 此处仅为演示。生产环境应使用AndroidKeyStore加密种子后再存储。 val encoded = Base64.encodeToString(seed, Base64.NO_WRAP) return sharedPrefs.edit().putString(keyAlias, encoded).commit() } actual fun getSeed(): ByteArray? { val encoded = sharedPrefs.getString(keyAlias, null) return encoded?.let { Base64.decode(it, Base64.NO_WRAP) } } actual fun clearSeed(): Boolean { return sharedPrefs.edit().remove(keyAlias).commit() } } actual fun generateSecureRandomBytes(size: Int): ByteArray { val random = SecureRandom() val bytes = ByteArray(size) random.nextBytes(bytes) return bytes } actual fun sha256(bytes: ByteArray): ByteArray { val digest = MessageDigest.getInstance("SHA-256") return digest.digest(bytes) }在Android的Activity或ViewModel中,可以这样使用:
// Android App Code class MainViewModel : ViewModel() { private val cryptoManager = CryptoManager.getInstance() fun saveSecretData(secret: String) { viewModelScope.launch(Dispatchers.IO) { val encrypted = cryptoManager.encryptData(secret.toByteArray()) // 将encrypted (ByteArray) 保存到文件或数据库 saveToLocalStorage(encrypted) } } fun loadSecretData(): String? { val encryptedData = loadFromLocalStorage() ?: return null return try { val decryptedBytes = cryptoManager.decryptData(encryptedData) String(decryptedBytes, Charsets.UTF_8) } catch (e: SecurityException) { // 数据被篡改,解密失败 null } } }4.2 在HarmonyOS (ArkTS) 端集成与调用
这是最具挑战性的一步。KMP的JS目标平台会将我们的commonMain Kotlin代码编译成一个JavaScript模块。鸿蒙的ArkTS(基于TypeScript)需要调用这个JS模块。
第一步:配置KMP生成JS模块在build.gradle.kts的kotlin块中,确保配置了JS目标:
kotlin { // ... js(IR) { browser() // 或者 nodejs(), 但为了鸿蒙,我们通常编译为适用于标准JS环境的模块 binaries.executable() } sourceSets { val commonMain by getting { dependencies { // 你的commonMain依赖 implementation("com.mycompany:krypto:1.0.0") // 假设的纯Kotlin加密库 } } val jsMain by getting { dependencies { // JS平台特定的实现 } } } }编译后,会在build/js/packages/<project-name>/kotlin下生成.js和.meta.js等文件。
第二步:实现HarmonyOS端的actual声明在jsMain源集中,我们需要用JavaScript/TypeScript的方式实现那些expect的函数。由于Kotlin/JS可以直接互操作JS代码,我们可以这样写:
// jsMain/kotlin/security/PlatformCryptoImpl.kt import kotlinx.browser.window actual class SecureKeyStorage { // 注意:浏览器/标准JS环境没有像Android KeyStore那样的硬件级安全存储。 // 这是一个重大安全限制。生产级HarmonyOS应用应使用鸿蒙的HUKS。 // 此处为演示,使用localStorage(不安全!)。 private val storage = window.localStorage private val key = "my_app_master_key_seed" actual fun saveSeed(seed: ByteArray): Boolean { return try { // 将ByteArray转为Base64字符串存储 val encoded = seed.toBase64() storage.setItem(key, encoded) true } catch (e: dynamic) { false } } actual fun getSeed(): ByteArray? { val encoded = storage.getItem(key) as? String return encoded?.fromBase64() } actual fun clearSeed(): Boolean { storage.removeItem(key) return true } } // 扩展函数,将ByteArray与Base64字符串互转(需要JS的btoa/atob) fun ByteArray.toBase64(): String { // 实现略,可用js("...")包装JS代码 return js("btoa(String.fromCharCode.apply(null, this))") as String } fun String.fromBase64(): ByteArray { // 实现略 val binaryString = js("atob(this)") as String return binaryString.toByteArray(Charsets.ISO_8859_1) } actual fun generateSecureRandomBytes(size: Int): ByteArray { // 使用Web Crypto API生成安全随机数 val array = ByteArray(size) js("window.crypto.getRandomValues(new Uint8Array(array))") return array } actual fun sha256(bytes: ByteArray): ByteArray { // 调用Web Crypto API的subtle.digest // 这是一个异步操作,但我们的expect函数是同步的。 // 因此,我们需要一个同步的SHA256实现,或者将expect改为suspend。 // 为简化,这里使用一个同步的JS polyfill(例如,引入一个SHA256的JS库)。 // 假设我们通过NPM引入了 `js-sha256` 库,并在gradle中配置了依赖。 // 伪代码: val hashHex = js("sha256.create().update(new Uint8Array(bytes)).hex()") as String return hexStringToByteArray(hashHex) }第三步:在鸿蒙ArkTS项目中引入并使用Kotlin/JS模块
- 将KMP编译生成的JS文件(如
yourproject.js、yourproject.meta.js)复制到鸿蒙工程的js目录下(例如entry/src/main/js/modules)。 - 在鸿蒙的
package.json中声明对这个模块的依赖(如果它被发布为NPM包,则更好)。或者,直接使用<script src="modules/yourproject.js">在HTML中引入(对于Web类鸿蒙应用)。 - 在ArkTS中,通过
import语句调用。
// entry/src/main/ets/pages/Index.ets // 假设Kotlin模块暴露了一个名为 `Crypto` 的全局对象 // 实际导出方式取决于Kotlin/JS的编译配置(如使用 @JsExport) import { CryptoManager } from '../js/modules/yourproject' // 路径根据实际调整 @Entry @Component struct Index { @State message: string = 'Encrypted Data Here' build() { Column() { Text(this.message) .fontSize(20) .margin(10) Button('Encrypt & Decrypt') .onClick(() => { this.executeDemo() }) } .width('100%') .height('100%') } private executeDemo() { try { const cryptoManager = CryptoManager.getInstance(); const plaintext = "MySecretData@123"; const encrypted = cryptoManager.encryptData(plaintext); // 假设这个方法返回Base64字符串 console.log('Encrypted:', encrypted); const decrypted = cryptoManager.decryptData(encrypted); console.log('Decrypted:', decrypted); this.message = `Decrypted: ${decrypted}`; } catch (error) { console.error('Crypto failed:', error); this.message = 'Error: ' + error.message; } } }重要提示:在真实的鸿蒙(特别是非Web类应用,即FA模型)中,JS的运行环境可能不是完整的浏览器环境,
window.crypto或localStorage可能不可用。你需要使用鸿蒙提供的@ohos.security.cryptoFramework和@ohos.data.preferences或@ohos.security.huks来实现SecureKeyStorage和generateSecureRandomBytes。这通常意味着你需要写一个更复杂的“胶水层”,或者将平台相关逻辑完全移到ArkTS侧,通过更精细的expect/actual设计,让Kotlin代码调用由ArkTS实现的接口。这涉及到Kotlin/JS与ArkTS的互操作,是集成中最复杂的部分。
5. 常见问题、调试技巧与安全考量
5.1 开发中遇到的典型问题
Kotlin/JS 加密库缺失或功能不全:
- 现象:在commonMain中引用的加密库,在JS编译时找不到类或函数。
- 排查:检查该库是否支持
kotlin-js目标。在库的GitHub或文档中查看多平台支持说明。很多Java库是不支持JS的。 - 解决:寻找替代的纯Kotlin实现库,或者自己用Kotlin实现核心算法(仅限教育或非关键场景,生产环境用成熟库)。也可以考虑将加密这部分作为“平台相关”实现,但这会牺牲代码共享性。
鸿蒙端调用JS模块失败:
- 现象:ArkTS代码中
import的模块为undefined,或调用函数时报错。 - 排查:
- 检查JS文件是否成功复制到鸿蒙项目目录,且路径正确。
- 检查Kotlin/JS的导出设置。需要在commonMain中,对需要暴露给JS的类或函数使用
@JsExport注解,并在gradle中启用IR编译器及moduleKind设置为commonjs或umd。 - 在浏览器开发者工具(如果鸿蒙应用是Web类)或HiLog中查看具体的JS错误。
- 解决:仔细配置Kotlin/JS的编译输出,确保生成的JS模块格式能被鸿蒙的JS运行时识别。参考鸿蒙官方文档中关于引入第三方JS库的部分。
- 现象:ArkTS代码中
加解密结果在Android和HarmonyOS上不一致:
- 现象:在Android上加密的数据,在鸿蒙上解密失败,反之亦然。
- 排查:这是跨平台加密最常见的问题。逐步检查以下环节是否一致:
- 密钥:派生密钥的密码、盐、迭代次数是否完全相同?
- 算法参数:AES密钥长度(128/256)、GCM的IV长度、认证标签长度是否一致?
- 数据编码:在将加密后的字节数组(
ByteArray)进行存储或传输时,是否使用了相同的编码(如Base64或十六进制)?两端编解码逻辑是否一致? - 数据拼接:
encrypt函数返回的IV + Ciphertext + Tag的拼接顺序,在解密时拆分逻辑是否完全一致?
- 解决:编写单元测试,在commonTest中模拟完整的加密解密流程,确保逻辑正确。然后分别在Android和HarmonyOS的测试中运行,定位平台差异。
性能问题:
- 现象:在鸿蒙端(特别是通过JS解释执行)加密大量数据时感觉缓慢。
- 排查:PBKDF2密钥派生过程(数万次哈希迭代)是计算密集型操作,在JS环境中可能阻塞UI。
- 解决:将密钥派生操作放在Web Worker(对于Web鸿蒙)或异步任务中执行,避免卡顿。对于大量数据的加密,可以考虑分块处理。
5.2 安全强化建议
- 密钥存储是命门:本示例中为了简化,在Android使用了
SharedPreferences,在JS端使用了localStorage,这都是极其不安全的,因为Root或越狱设备可以轻易读取。生产环境必须:- Android:使用
AndroidKeyStore来生成和存储密钥,或至少用它来加密一个存储在别处的密钥。 - HarmonyOS:使用
@ohos.security.huks(HUKS) 来保护密钥。对于JS环境,积极探索如何通过FFI(外部函数接口)或Native API调用HUKS的C API,这是实现真正安全存储的关键。
- Android:使用
- 算法与参数:
- 使用AES-GCM-256而不是CBC模式。
- PBKDF2的迭代次数应足够高(推荐10万次以上),并根据设备性能调整。
- 盐值必须使用密码学安全的随机数生成器生成,并且每个加密数据都应使用不同的盐(或IV)。
- 防御侧信道攻击:确保代码执行时间不随密钥或数据内容变化(时间侧信道)。对于KMP共享的Kotlin代码,这点需要依赖底层加密库的实现质量。
- 定期密钥轮换:设计密钥轮换机制。当主密钥可能泄露时,可以用新密钥重新加密所有数据。
5.3 调试与日志
在KMP跨平台项目中,调试加密逻辑需要一些技巧:
- 在commonMain中打印日志:使用
println或io.github.aakira:napier等多平台日志库。在Android的Logcat和鸿蒙的HiLog中查看输出。 - 单元测试先行:为
CryptoManager、AESGCMEncryptor、KeyDerivation编写详尽的单元测试(放在commonTest中),覆盖正常流程、错误输入、边界情况。确保核心逻辑在脱离平台环境时就是正确的。 - 端到端测试:编写一个简单的测试页面,在Android和鸿蒙应用上分别执行相同的加密-解密操作,并对比结果和性能。
通过以上步骤,我们成功构建了一个基于KMP的、跨Android和HarmonyOS的数据加密方案。它虽然无法做到像原生平台那样深度集成硬件安全特性,但在逻辑统一、代码复用和基础安全防护上提供了极大的价值。对于许多需要保护本地敏感数据(如用户设置、缓存令牌)的应用来说,这是一个切实可行的架构。