1. 项目概述:为什么要在ArkTS里折腾SM2?

如果你正在用ArkTS开发鸿蒙应用,并且应用场景涉及金融支付、电子合同、身份认证或者任何需要确保数据完整性与来源可信的环节,那么“签名”和“验签”就是你绕不开的技术坎。SM2,作为国家密码管理局发布的椭圆曲线公钥密码算法标准,在国密合规场景下,其地位就如同国际上的RSA或ECDSA。而ArkTS,作为鸿蒙生态的主力开发语言,其安全能力库 @ohos.security.cryptoFramework 虽然强大,但官方文档在SM2这种具体算法的实战细节上,往往语焉不详,特别是从密钥对生成到签名验签的全链路,新手很容易踩坑。

这个实战指南,就是来解决这个痛点的。它不是一篇泛泛而谈的原理介绍,而是一份从零开始、手把手、带避坑指南的实操手册。我将基于最新的ArkTS API(API 11及以上),拆解SM2密钥对的生成、存储、签名与验签的每一个步骤,解释背后“为什么这么做”,并分享我在实际项目中趟过的雷。无论你是刚接触国密算法,还是已经在ArkTS中集成加密功能但遇到了问题,这篇文章都能给你提供可直接“抄作业”的代码和清晰的思路。

2. 核心概念与ArkTS加密框架扫盲

在直接敲代码之前,我们必须统一几个关键概念,并理解ArkTS提供的加密工具箱是如何组织的。这能避免后续出现“代码能跑,但不知道为啥”的尴尬。

2.1 SM2签名验签到底在做什么?

你可以把SM2签名验签想象成一个精密的数字“封条”和“验钞机”组合。

  • 签名(Sign) : 数据发送方(比如你的App客户端)持有自己的 私钥 。当需要发送一份重要数据(如交易请求)时,他用私钥对这份数据的“数字指纹”(由SM3杂凑算法生成)进行一系列复杂的数学运算,生成一个独一无二的“签名串”。这个签名串就像是用只有发送方才有的特殊印章盖下的封条,封条本身不包含数据内容,但和数据牢牢绑定。
  • 验签(Verify) : 数据接收方(比如你的服务器)持有发送方的 公钥 。收到数据和签名串后,接收方用同样的方法计算收到数据的“数字指纹”,然后用发送方的公钥去“解锁”那个签名串。如果解锁后得到的“指纹”和自己计算出的“指纹”完全一致,就证明了两件事:第一,数据在传输过程中没有被篡改(完整性);第二,这份数据确实来自声称的发送方(身份认证)。

这里的关键是 私钥签名,公钥验签 。私钥必须绝对保密,通常存储在设备的安全芯片(如TEE)或由用户妥善保管;公钥则可以公开发布,任何人都可以用来验证签名的真伪。

2.2 ArkTS CryptoFramework 架构解析

ArkTS通过 @ohos.security.cryptoFramework 提供了统一的密码学操作接口。它的设计是模块化和异步的,理解其核心类的关系至关重要:

  1. CryptoFramework : 工厂类,一切的开端。用于创建 SymKeyGenerator (对称密钥生成器)、 AsyKeyGenerator (非对称密钥生成器,我们用它生成SM2密钥对)、 Cipher (加密解密)、 Sign (签名验签)等对象。
  2. AsyKeyGenerator : 非对称密钥生成器。你需要指定算法(如 SM2_256 )和参数,通过它来生成密钥对。
  3. KeyPair : 生成的密钥对容器,包含 pubKey (公钥)和 priKey (私钥)两个属性。
  4. Sign : 签名验签操作的核心类。你需要先 init 初始化(设置密钥和模式:签名或验签),然后 update 传入要处理的数据,最后 sign 生成签名或 verify 进行验签。
  5. DataBlob : ArkTS中用于表示二进制数据的通用对象,简单理解为一个 { data: Uint8Array } 的结构。密钥、签名、待处理数据通常都封装在 DataBlob 里进行传递。

整个流程是异步Promise驱动的,这意味着你需要熟悉 async/await 的写法。框架的这种设计保证了耗时操作不会阻塞UI主线程。

注意 : 在查阅资料时,你可能会看到一些基于旧版API或Java的SM2示例。请务必以当前鸿蒙官方文档为准,因为API在迭代中可能有较大变动。本文的代码基于主流的稳定版本。

3. 实战第一步:生成SM2密钥对

生成密钥对是后续所有操作的基础。在ArkTS中,我们不仅关心如何生成,更关心如何以安全的格式获取和保存它们。

3.1 密钥生成代码实现

首先,在项目的 entry/src/main/ets 目录下,创建一个用于处理加密的工具类,比如 CryptoUtil.ets

// CryptoUtil.ets
import cryptoFramework from '@ohos.security.cryptoFramework';

export class CryptoUtil {
  /**
   * 生成SM2密钥对
   * @returns 返回生成的KeyPair(密钥对)
   */
  static async generateSM2KeyPair(): Promise<cryptoFramework.KeyPair> {
    try {
      // 1. 创建非对称密钥生成器,指定算法为SM2,密钥长度为256位(固定)
      let keyGenAlg = 'SM2_256';
      let asyKeyGenerator = cryptoFramework.createAsyKeyGenerator(keyGenAlg);
      if (asyKeyGenerator == null) {
        throw new Error(`创建密钥生成器失败,请检查算法名称'${keyGenAlg}'是否正确或系统是否支持`);
      }

      // 2. 生成密钥对
      let keyPair: cryptoFramework.KeyPair = await asyKeyGenerator.generateKeyPair();
      console.info('SM2密钥对生成成功!');
      
      // 3. 这里可以打印或转换密钥,用于调试(生产环境切勿日志输出私钥!)
      // await this.logKeyPair(keyPair);
      
      return keyPair;
    } catch (error) {
      console.error(`生成SM2密钥对失败: ${error.message}`);
      throw error;
    }
  }

  // 一个用于调试的辅助方法,将密钥转换为十六进制字符串查看
  private static async logKeyPair(keyPair: cryptoFramework.KeyPair): Promise<void> {
    let pubKeyBlob = await keyPair.pubKey.getEncoded();
    let priKeyBlob = await keyPair.priKey.getEncoded();
    console.info('公钥Hex:', this.uint8ArrayToHex(pubKeyBlob.data));
    console.info('私钥Hex:', this.uint8ArrayToHex(priKeyBlob.data));
  }

  private static uint8ArrayToHex(uint8Array: Uint8Array): string {
    return Array.from(uint8Array).map(b => b.toString(16).padStart(2, '0')).join('');
  }
}

关键点解析:

  • SM2_256 : 这是ArkTS框架中标识SM2算法的参数。256指曲线参数的长度,对于SM2是固定的。
  • generateKeyPair() : 这是一个异步方法,返回一个 Promise<KeyPair> 。在实际设备上,生成过程可能会利用硬件安全能力,需要一定时间。
  • getEncoded() : 这是 Key 对象的方法,用于获取密钥的二进制编码格式( DataBlob )。这个格式通常是DER或PKCS#8格式的,不是简单的裸密钥字节。

3.2 密钥的格式、保存与安全考量

生成的 KeyPair 对象在内存中,应用重启后就会丢失。因此,持久化保存是必须的,但这里的安全风险极高。

1. 公钥的保存: 公钥可以公开,所以保存方式很灵活。通常我们会将其 getEncoded() 后的二进制数据( DataBlob.data )进行Base64编码或转换为十六进制字符串,然后:

  • 存储在应用的 Preferences (轻量级存储)中。
  • 上传到服务器。
  • 硬编码在代码里(不推荐,不利于更换)。
// 保存公钥示例
import util from '@ohos.util';
let pubKeyBlob = await keyPair.pubKey.getEncoded();
let base64Encoder = new util.Base64Helper();
let pubKeyBase64 = base64Encoder.encodeToStringSync(pubKeyBlob.data);
// 将pubKeyBase64存入Preferences

2. 私钥的保存(重中之重!): 私钥的泄露意味着身份被伪造。绝对禁止以明文形式存储在 Preferences 、文件或任何不安全的介质中。

  • 推荐方案:使用系统密钥库(HUKS) 。鸿蒙的 @ohos.security.huks 模块提供了硬件级的安全密钥存储。你可以将生成的私钥导入到HUKS中,由系统安全芯片保护,应用只持有一个密钥的别名(Alias)句柄。后续签名时,使用这个别名从HUKS中调用私钥进行操作,私钥本身不会暴露给应用内存。这是最安全的方式。
  • 折中方案(仅用于测试或低安全要求) : 如果必须由应用自己管理,可以考虑:
    • 加密后存储 : 使用一个由用户口令派生的密钥(通过PBKDF2)对称加密私钥二进制数据,再将密文存储起来。但这只是增加了攻击难度,并非绝对安全。
    • 使用 cryptoFramework SymKeyGenerator 生成一个临时密钥进行加密 ,但这个临时密钥又面临同样存储问题。

实操心得: 在真实的商业应用中,尤其是涉及金融、政务的App, 必须使用HUKS来管理私钥 。虽然集成HUKS会增加一些代码复杂度(需要处理密钥属性、访问控制等),但这是通过安全审计和合规性检查的必经之路。开发初期为了快速验证流程,可以暂时将密钥对保存在内存或临时文件中,但务必在发布前替换为HUKS方案。

4. 核心环节:使用SM2进行数据签名

有了密钥对,我们就可以开始签名了。签名过程需要用到私钥和待签名的原始数据。

4.1 签名流程代码拆解

我们在 CryptoUtil 类中增加签名方法。

/**
 * 使用SM2私钥对数据进行签名
 * @param priKey 私钥对象
 * @param data 待签名的原始数据 (Uint8Array 或 string)
 * @returns 签名结果 (DataBlob)
 */
static async signData(priKey: cryptoFramework.PriKey, data: Uint8Array | string): Promise<cryptoFramework.DataBlob> {
  try {
    // 1. 统一输入数据为Uint8Array
    let inputData: Uint8Array;
    if (typeof data === 'string') {
      // 将字符串转换为UTF-8编码的字节数组
      let textEncoder = new util.TextEncoder();
      inputData = textEncoder.encodeInto(data);
    } else {
      inputData = data;
    }

    // 2. 创建Sign实例,指定算法为SM2(带SM3杂凑)
    let signAlg = 'SM2|SM3';
    let signer = cryptoFramework.createSign(signAlg);
    if (signer == null) {
      throw new Error(`创建Sign实例失败,算法'${signAlg}'可能不支持`);
    }

    // 3. 初始化Signer,设置为签名模式,并传入私钥
    await signer.init(priKey);

    // 4. 更新(传入)待签名的数据
    await signer.update({ data: inputData });

    // 5. 执行签名操作
    let signDataBlob = await signer.sign(null); // 参数为预留,通常传null
    console.info('数据签名成功!签名长度:', signDataBlob.data.length);
    return signDataBlob;
  } catch (error) {
    console.error(`SM2签名失败: ${error.message}`);
    throw error;
  }
}

关键点解析:

  • SM2|SM3 : 这是ArkTS中指定SM2签名算法并携带SM3作为摘要算法的标准写法。SM2签名标准规定使用SM3生成数据的杂凑值。
  • init(priKey) : 明确使用私钥进行初始化,意味着接下来是签名操作。
  • update() : 可以多次调用,用于处理大数据流。这里我们一次性传入所有数据。
  • sign(null) : 执行签名计算。参数预留,通常为 null 或空对象。返回的 signDataBlob.data 就是DER编码的签名值。

4.2 签名结果的编码与传输

签名结果 signDataBlob.data 是一个二进制的 Uint8Array 。为了在网络传输或存储中方便使用,我们需要将其编码为文本格式。

  • Base64编码 : 最常用,长度适中,适合放在JSON、URL参数(需URL Safe)或文本文件中。
    let base64Encoder = new util.Base64Helper();
    let signatureBase64 = base64Encoder.encodeToStringSync(signDataBlob.data);
    // 现在可以将 signatureBase64 和原始数据一起发送给验签方
    
  • 十六进制(Hex)编码 : 人类可读,但长度会增加一倍。
    let signatureHex = Array.from(signDataBlob.data).map(b => b.toString(16).padStart(2, '0')).join('');
    

一个完整的签名数据包 通常包含:

  1. 原始数据(或数据的标识)。
  2. 签名值(Base64或Hex格式)。
  3. 签名使用的公钥(或公钥标识),以便验签方找到对应的公钥。

5. 核心环节:使用SM2进行签名验证

验签是签名的逆过程,发生在接收方。接收方持有原始数据、签名串和发送方的公钥。

5.1 验签流程代码实现

CryptoUtil 类中增加验签方法。

/**
 * 使用SM2公钥验证签名
 * @param pubKey 公钥对象
 * @param data 原始数据 (Uint8Array 或 string)
 * @param signatureToVerify 待验证的签名 (DataBlob 或 Uint8Array)
 * @returns 验签是否通过 (boolean)
 */
static async verifySignature(pubKey: cryptoFramework.PubKey, 
                             data: Uint8Array | string, 
                             signatureToVerify: cryptoFramework.DataBlob | Uint8Array): Promise<boolean> {
  try {
    // 1. 统一输入数据
    let inputData: Uint8Array;
    if (typeof data === 'string') {
      let textEncoder = new util.TextEncoder();
      inputData = textEncoder.encodeInto(data);
    } else {
      inputData = data;
    }

    // 2. 统一签名数据
    let signatureBlob: cryptoFramework.DataBlob;
    if ((signatureToVerify as cryptoFramework.DataBlob).data !== undefined) {
      signatureBlob = signatureToVerify as cryptoFramework.DataBlob;
    } else {
      signatureBlob = { data: signatureToVerify as Uint8Array };
    }

    // 3. 创建Sign实例,同样指定算法为 SM2|SM3
    let verifyAlg = 'SM2|SM3';
    let verifier = cryptoFramework.createVerify(verifyAlg);
    if (verifier == null) {
      throw new Error(`创建Verify实例失败,算法'${verifyAlg}'可能不支持`);
    }

    // 4. 初始化Verifier,设置为验签模式,并传入公钥
    await verifier.init(pubKey);

    // 5. 更新(传入)原始数据
    await verifier.update({ data: inputData });

    // 6. 执行验签操作
    let verifyResult = await verifier.verify(signatureBlob);
    console.info(`签名验证${verifyResult ? '通过' : '失败'}!`);
    return verifyResult;
  } catch (error) {
    // 注意:验签失败可能抛出异常(如格式错误),也可能返回false。
    // 这里捕获的是初始化、更新等过程的异常,verify()本身的失败会返回false。
    console.error(`SM2验签过程发生错误: ${error.message}`);
    return false;
  }
}

关键点解析:

  • init(pubKey) : 使用公钥初始化,表明接下来进行验签。
  • verify(signatureBlob) : 传入待验证的签名。该方法返回一个 Promise<boolean> true 表示验签成功, false 表示失败。
  • 异常处理 verify() 方法本身在签名不匹配时返回 false ,而不是抛出异常。但如果签名数据格式错误、公钥不匹配算法等,之前的步骤(如 init )可能会抛出异常。因此,我们需要在 catch 块中也返回 false ,表示验签未通过。

5.2 从编码格式还原密钥与签名

在实际场景中,你收到的公钥和签名往往是Base64或Hex字符串,而不是直接的 KeyPair DataBlob 对象。因此,我们需要还原操作。

1. 从Base64字符串还原公钥进行验签: 这通常需要用到 cryptoFramework.createAsyKeyGenerator().convertKey 方法。但请注意, convertKey 通常需要密钥的格式参数(如PKCS#1, PKCS#8)。公钥的常见格式是X.509 SubjectPublicKeyInfo (SPKI)。ArkTS的 getEncoded() 默认输出的可能就是这种格式。假设你存储的是这个原始二进制数据的Base64。

static async getPubKeyFromBase64(base64Str: string): Promise<cryptoFramework.PubKey> {
  let base64Decoder = new util.Base64Helper();
  let pubKeyData = base64Decoder.decodeSync(base64Str); // 得到 Uint8Array

  let keyGenAlg = 'SM2_256';
  let asyKeyGenerator = cryptoFramework.createAsyKeyGenerator(keyGenAlg);
  
  // 关键:将二进制数据转换回公钥对象。
  // 这里假设数据是框架默认getEncoded()输出的格式。
  let pubKey = await asyKeyGenerator.convertKey(pubKeyData, null);
  return pubKey;
}

注意 convertKey 的第二个参数是私钥转换时的密码,公钥转换时传 null 。如果转换失败,很可能是因为二进制数据的格式不对。你需要确认存储的公钥格式是否与 getEncoded() 输出的格式一致。

2. 从Base64字符串还原签名数据: 这个比较简单,解码后包装成 DataBlob 即可。

let base64Decoder = new util.Base64Helper();
let signatureData = base64Decoder.decodeSync(signatureBase64Str);
let signatureBlob: cryptoFramework.DataBlob = { data: signatureData };
// 然后将 signatureBlob 传入 verifySignature 方法

6. 完整流程串联与示例

让我们把上面的所有步骤串联起来,看一个从生成密钥对到签名再到验签的完整示例。假设在一个简单的用户登录场景,客户端对登录信息进行签名,服务端(这里模拟)进行验签。

// Example.ets
import { CryptoUtil } from './CryptoUtil';
import util from '@ohos.util';

async function demoSM2FullProcess() {
  console.info('=== SM2 完整签名验签流程演示 ===');

  // 步骤1:客户端生成密钥对(实际应用中,私钥应安全存储,公钥上传服务器)
  console.info('\n1. 生成SM2密钥对...');
  let keyPair;
  try {
    keyPair = await CryptoUtil.generateSM2KeyPair();
  } catch (error) {
    console.error('密钥对生成失败,流程终止。');
    return;
  }

  // 步骤2:客户端准备待签名的数据(例如:`用户名:时间戳`)
  let userId = 'user123';
  let timestamp = Date.now().toString();
  let rawData = `${userId}:${timestamp}`;
  console.info(`\n2. 原始数据: "${rawData}"`);

  // 步骤3:客户端使用私钥对数据进行签名
  console.info('\n3. 客户端使用私钥进行签名...');
  let signatureBlob;
  try {
    signatureBlob = await CryptoUtil.signData(keyPair.priKey, rawData);
    let signatureBase64 = new util.Base64Helper().encodeToStringSync(signatureBlob.data);
    console.info(`   生成签名(Base64): ${signatureBase64.substring(0, 50)}...`);
  } catch (error) {
    console.error('签名失败!');
    return;
  }

  // 步骤4:模拟网络传输。客户端将 rawData, signatureBase64 和公钥(Base64格式)发送给服务器
  let pubKeyBlob = await keyPair.pubKey.getEncoded();
  let pubKeyBase64 = new util.Base64Helper().encodeToStringSync(pubKeyBlob.data);
  console.info(`\n4. 客户端发送给服务器:`);
  console.info(`   数据: ${rawData}`);
  console.info(`   签名: ${signatureBase64.substring(0, 50)}...`);
  console.info(`   公钥: ${pubKeyBase64.substring(0, 50)}...`);

  // 步骤5:服务器端验签
  console.info('\n5. 服务器端进行验签...');
  
  // 5.1 服务器还原公钥对象(模拟)
  let serverPubKey;
  try {
    // 注意:这里需要CryptoUtil中实现的getPubKeyFromBase64方法
    serverPubKey = await CryptoUtil.getPubKeyFromBase64(pubKeyBase64);
  } catch (error) {
    console.error('服务器:公钥还原失败!');
    return;
  }

  // 5.2 服务器还原签名数据
  let serverSignatureData = new util.Base64Helper().decodeSync(signatureBase64);
  let serverSignatureBlob: cryptoFramework.DataBlob = { data: serverSignatureData };

  // 5.3 服务器进行验签
  let isVerified;
  try {
    isVerified = await CryptoUtil.verifySignature(serverPubKey, rawData, serverSignatureBlob);
  } catch (error) {
    console.error('服务器:验签过程异常!');
    return;
  }

  // 步骤6:验签结果
  if (isVerified) {
    console.info('\n✅ 验签成功!数据完整且来源可信。');
    // 服务器可以放心处理 rawData 了
  } else {
    console.error('\n❌ 验签失败!数据可能被篡改或来源不可信。');
    // 服务器应拒绝此请求
  }
}

// 调用演示函数
demoSM2FullProcess();

7. 常见问题、踩坑实录与排查技巧

在实际开发中,你几乎一定会遇到下面这些问题。我把它们和解决方案整理出来,希望能帮你节省大量调试时间。

7.1 错误:“创建Sign/Verify实例失败”或“算法不支持”

  • 问题现象 cryptoFramework.createSign('SM2|SM3') createAsyKeyGenerator('SM2_256') 返回 null
  • 排查步骤
    1. 检查API版本 : 确认你的 compileSdkVersion targetSdkVersion 是否支持该算法。某些较老的SDK版本可能对国密算法支持不全。建议使用API 9及以上版本。
    2. 检查算法字符串 : 确保字符串完全正确,没有拼写错误或多余空格。 'SM2_256' 'SM2|SM3' 是大小写敏感的。
    3. 检查导入模块 : 确认文件头部正确导入了 import cryptoFramework from '@ohos.security.cryptoFramework';
    4. 查阅官方文档 : 前往 ArkTS API文档 ,确认当前版本文档中列出的支持算法。

7.2 错误:签名或验签时抛出异常,提示“初始化错误”或“操作错误”

  • 问题现象 : 在 signer.init(priKey) verifier.init(pubKey) 时报错。
  • 可能原因与解决
    1. 密钥与算法不匹配 : 你用来初始化的密钥不是由 SM2_256 算法生成的。例如,尝试用RSA的密钥进行SM2操作。确保密钥对是由正确的 AsyKeyGenerator 生成的。
    2. 密钥对象已损坏或无效 : 如果你尝试从存储的字符串还原密钥,但还原过程出错,得到的密钥对象是无效的。检查你的编解码过程,确保二进制数据没有丢失或损坏。 一个实用的调试方法 :在生成密钥对后,立即将其 getEncoded() 并转换成Base64打印出来。在还原时,对比这个Base64字符串是否完全一致。
    3. 私钥用于验签或公钥用于签名 : 这是逻辑错误。 init 时传入的密钥类型必须与操作匹配:签名用 PriKey ,验签用 PubKey

7.3 错误:验签始终返回false,但步骤看似都正确

这是最令人头疼的问题。请按照以下清单逐一核对:

  1. 数据一致性(99%的问题出在这里)

    • 绝对保证 : 验签时使用的 原始数据 ,必须与签名时使用的 原始数据 逐字节完全相同 。一个额外的空格、不同的编码(如UTF-8 vs GBK)、甚至不可见的换行符( \n vs \r\n )都会导致杂凑值不同,从而使验签失败。
    • 检查点
      • 如果数据是字符串,签名和验签两端的字符串转换 Uint8Array 的方式是否一致?(推荐都使用 TextEncoder )。
      • 如果数据包含时间戳,确保两端的时间戳字符串格式一致。
      • 在网络传输中,是否对数据进行了不必要的URL解码或HTML实体解码?
    • 调试建议 : 在签名后和验签前,分别将数据的十六进制表示打印出来进行比对。
  2. 签名数据一致性

    • 确保传输的签名字符串(Base64/Hex)在接收端被正确解码回原始的二进制格式。Base64解码时注意URL Safe和Padding问题。
  3. 公钥一致性

    • 验签使用的公钥,必须是对应签名私钥的配对公钥。确保服务器端使用的公钥确实是客户端生成的那个,没有弄混。
  4. 算法标识一致性

    • 极少数情况下,不同系统或库对SM2签名的编码格式(如ASN.1 DER编码的序列结构)有细微差异。ArkTS使用的是标准格式。如果你需要与其他系统(如OpenSSL、GmSSL、Java BouncyCastle)交互,需要确认双方的签名输出/输入格式是否兼容。有时需要手动处理DER序列的编解码。

7.4 性能与内存优化提示

  • 大文件签名 : 对于非常大的数据,不要一次性读取到内存再调用 update() 。可以利用 update() 支持多次调用的特性,流式地读取和更新数据块。
    await signer.init(priKey);
    for (let chunk of largeDataChunks) {
      await signer.update({ data: chunk });
    }
    let signature = await signer.sign(null);
    
  • 密钥对象复用 : 生成或转换密钥对象( KeyPair , PubKey , PriKey )是相对耗时的操作。在应用生命周期内,如果密钥不变,应将其缓存起来,避免重复生成或转换。
  • 异步操作 : 所有 cryptoFramework 的主要操作都是异步的,避免在UI线程进行大量同步加密运算。

7.5 关于HUKS集成的一点补充

虽然本文示例为了清晰使用了内存中的密钥,但我必须再次强调生产环境使用HUKS的重要性。集成HUKS的基本思路是:

  1. 生成或导入密钥 : 使用 huks.generateKeyItem() huks.importKeyItem() 将密钥材料存入安全区,得到一个 keyAlias
  2. 签名时 : 不再直接使用 priKey 对象,而是通过 huks.init() huks.update() huks.finish() 这一套接口,指定 keyAlias 来完成签名操作。私钥始终不出安全芯片。
  3. 验签时 : 公钥可以导出,因此验签流程和本文描述基本一致。

这部分的代码会更复杂,涉及大量的异步回调和参数配置,建议直接参考鸿蒙官方关于HUKS的专项文档和示例代码。

最后,SM2在ArkTS中的集成,核心在于理解框架的异步API设计、密钥的生命周期管理以及数据在各个环节的格式一致性。多写测试用例,对每个环节的输入输出进行十六进制打印比对,是快速定位问题的法宝。希望这份实战指南能让你在鸿蒙应用开发中,稳稳地跨过国密签名验签这道技术门槛。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐