头图

HarmonyOS 6.1 全栈实战录 - 82 实战 Crypto Kit 私钥剥离公钥的非对称衍生中控舱

1、引言

在传统的端云协同架构中,如果涉及极高安全级别的非对称加密(如 RSA 或 ECC),我们通常会在服务端生成一套密钥对(KeyPair),然后分别将私钥(Private Key)和公钥(Public Key)下发给终端设备。或者终端自己在安全区生成密钥对,再把公钥上传给服务器。
image.png
这种“成双成对”强绑定的操作模式,在某些脱网的物理环境或极端的嵌入式通讯场景中,显得极为笨重。如果我的应用只接收到了一串受信任的裸私钥二进制流(例如从智能卡或加密狗中读取),我能不能不依赖网络,直接在本地设备上“算”出它对应的公钥?

在密码学的数学模型中,私钥实际上包含了公钥的全部数学特征(比如 RSA 私钥包含了模数 $n$ 和私钥指数 $d$,同时也隐藏了计算公钥指数 $e$ 的因子)。但在 API 23 之前,系统的加密框架并没有暴露这种底层的逆向衍生能力,开发者只能被迫引入庞大的第三方 C++ 加密库(如 OpenSSL)来进行剥离,这严重拖慢了应用体积并带来了内存安全风险。
image.png
HarmonyOS NEXT 6.1 (API 23) 的 Crypto Architecture Kit 终于在架构层开放了这项权力:PriKey 对象新增了 getPubKey()getPubKeySync()。仅凭一把私钥,即可在端侧安全无损地剥离重构出配套公钥。

2、效果展示与项目结构
2.1 运行效果展示

本节我们将构建一个「非对称密钥衍生控制舱」:

  1. 裸流加载:系统会在内存中初始化一段脱水的 RSA1024 裸私钥二进制流。
  2. 双引擎萃取:提供异步(Promise)和同步(Sync)两种剥离引擎。点击执行后,底层硬件加密库将瞬间从私钥对象中萃取出配套的公钥数据。
  3. 安全审计比对:萃取完成后,系统会将生成的公钥流与服务端预先存储的基准公钥(expectPkdata)进行严格的字节级数学比对。如果不一致,将触发系统级红灯告警。
2.2 示例项目物理结构
CryptoArchitectureKitDemo
├── AppScope
│   └── app.json5
├── entry
│   ├── oh-package.json5
│   └── src
│       └── main
│           ├── ets
│           │   ├── entryability
│           │   │   └── EntryAbility.ets
│           │   └── pages
│           │       ├── Index.ets                  <-- 衍生舱入口与亮点解析
│           │       └── CryptoDemo.ets             <-- 实战舱:装载私钥,剥离公钥,模数比对
│           ├── resources
│           │   └── base
│           │       └── profile
│           │           └── main_pages.json
│           └── module.json5
└── build-profile.json5
3、核心机制解析:剥离公钥的底层链路

在实战前,我们需要理清使用这个新 API 的底层转换链路。我们并不是直接对着一串二进制 Uint8Array 去调用 getPubKey,而是需要通过系统的 Crypto 引擎建立对象关系。

image.png

4、API 23 新增特性实战:非对称衍生中控舱

接下来,我们在 CryptoDemo.ets 中将这一整套流程进行工程化落地。

4.1 注入私钥样本与基准公钥

在真实企业级场景中,这串数据可能来源于网络响应(Base64解码后)或者 NFC 刷卡获取的缓冲流。

import { cryptoFramework } from '@kit.CryptoArchitectureKit';
import { buffer } from '@kit.ArkTS';
import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct CryptoDemo {
  @State log: string = '等待中控台指令...\n';
  @State isExtracting: boolean = false;
  @State extractedPubKeyData: string = '';

  // 模拟从外部(如网络或本地安全区)加载的裸私钥二进制流 (RSA1024)
  private readonly skData = new Uint8Array([48, 130, 2, 119, 2, 1, 0, 48, /* ...此处省略大量字节... */ 248, 148, 151]);

  // 预期的裸公钥二进制流 (用于比对验证剥离是否无损)
  private readonly expectPkdata = new Uint8Array([48, 129, 159, 48, /* ...此处省略大量字节... */ 3, 1, 0, 1]);
4.2 核心派生引擎:双路并发剥离架构

这里的重点在于 rsaGenerator.convertKeySync 时,对于公钥的装载参数我们传了 null。这意味着我们主动放弃了向引擎输入现成的公钥。

  // 核心剥离方法
  private async extractPubKey(useSync: boolean) {
    this.isExtracting = true;
    this.extractedPubKeyData = '';
    this.appendLog('⚡ 初始化:正在装载裸私钥流...');

    try {
      // 1. 初始化 RSA1024 架构生成器
      let rsaGenerator = cryptoFramework.createAsyKeyGenerator('RSA1024');
      
      // 2. 将二进制流装载至 ArkTS 内存数据块
      let skDataBlob: cryptoFramework.DataBlob = { data: this.skData };
      
      // 3. 关键动作:将裸流转换为引擎可识别的 PriKey 对象
      // 注意:第一个参数为 null,因为我们此刻并没有公钥
      let keyPair = rsaGenerator.convertKeySync(null, skDataBlob);
      let priKey = keyPair.priKey;

      this.appendLog('✅ 裸私钥装载成功,已持有有效 PriKey 对象。');
      this.appendLog(`⚙️ 引擎开始执行 ${useSync ? '同步(Sync)' : '异步(Async)'} 算法剥离公钥...`);

      let pubkey: cryptoFramework.PubKey;
      
      // 4. 重磅特性:从私钥中反向提取公钥 (API 23 新增能力)
      if (useSync) {
        // 同步提取,适用于主线程负载较轻时的快速推断
        pubkey = priKey.getPubKeySync();
      } else {
        // 异步提取,如果是非对称密钥长度极大(如 RSA4096),强烈推荐异步防阻塞
        pubkey = await priKey.getPubKey();
      }

      // 5. 编码导出公钥裸流,用于网络下发或签名存储
      let pkBlob = pubkey.getEncoded();
      
      this.appendLog('✅ 公钥剥离成功!正在比对数学模型...');
      
      // 6. 安全验证:与服务端预留的公钥模数进行字节级对比
      let isValid: boolean = this.compareUint8Array(pkBlob.data, this.expectPkdata);
      if (isValid) {
        this.appendLog('🔒 校验通过:萃取出的公钥与预期物理模数完全一致。');
        // 将 Uint8Array 转换为直观的 Hex 字符串并在 UI 显示片段
        this.extractedPubKeyData = Array.from(pkBlob.data)
                                    .map(b => b.toString(16).padStart(2, '0'))
                                    .join('')
                                    .substring(0, 48) + '...';
        promptAction.showToast({ message: '公钥提取成功' });
      } else {
        this.appendLog('❌ 校验失败:产生的公钥数据异常!');
      }

    } catch (e) {
      this.appendLog(`🚨 致命错误:代码 ${e.code}, 信息: ${e.message}`);
    } finally {
      this.isExtracting = false;
    }
  }

  // 工具方法:安全的 Uint8Array 内存比对
  private compareUint8Array(a: Uint8Array, b: Uint8Array): boolean {
    let buf1 = buffer.from(a);
    let buf2 = buffer.from(b);
    // 使用底层的 buffer.compare 确保比对性能
    if (buf1.compare(buf2, 0, b.length, 0, a.length) == 0) {
      return true;
    } else {
      return false;
    }
  }

运行效果如下:
image.png

image.png

5、避坑指南

在使用这套密码学新特性时,务必遵循以下规范:

  1. 类型强校验:ArkTS 的强类型检查非常严格。在进行 Array.from 转换或调用 DataBlob 接口时,请确保数据源是合法的 Uint8Array。如果服务端发来的是 Base64,必须先使用 util.Base64Helper 进行严谨解码,绝不能用 any 糊弄编译器。
  2. 同步与异步的选择:对于 RSA1024 等短密匙,getPubKeySync 可以瞬间完成计算,代码结构也更为清爽。但在高防安全业务中如果涉及 RSA4096 或更为复杂的国密(SM2)衍生,强行在主线程调用 Sync 方法可能会引发 UI 卡顿甚至触发底层的 Watchdog 超时截杀。因此,首选永远是基于 Promise 的 getPubKey()
  3. 算法强隔离:你用 RSA1024 的生成器(createAsyKeyGenerator),就只能剥离该规格的私钥。如果你将 RSA2048 的数据流塞进去,底层在执行 convertKeySync 时会直接抛出 17620002 (failed to convert parameters between arkts and c) 的内存重映射惨案。不要抱有任何侥幸心理。
6、总结

Crypto Architecture Kit 在 API 23 推出的私钥剥离公钥特性,看似是一个极小的函数新增,实则是整个端侧架构对密码学边界的一次大扩权。

从此,我们不再需要被繁琐的“全量密钥对分发协议”绑架。端侧设备只需要最核心的 Private Key 种子,就能在本地沙箱内随时衍生出配套验证网络,在保证绝对安全的同时,大幅度降低了带宽与存储开销。

在无网、离线、近场认证等严苛战场,这个特性,就是我们最锋利的瑞士军刀。


轻口味
39.5k 声望5.9k 粉丝

移动端十年老人,主要做IM、音视频、AI方向,目前在做鸿蒙化适配,欢迎这些方向的同学交流:wodekouwei