HarmonyOS ArkTS集成SM2签名验签:从密钥生成到实战应用
1. 项目概述与核心价值
最近在HarmonyOS应用开发社区里,看到不少开发者对国密算法的集成感到头疼,尤其是SM2签名验签这块。很多朋友反馈,官方文档虽然全面,但想快速上手、跑通一个完整的流程,还是得自己摸索半天,踩不少坑。正好我最近在一个金融类应用项目中,完整地走通了HarmonyOS下使用ArkTS进行SM2密钥对签名验签的全流程,从密钥生成到签名,再到验签和异常处理,积累了一些实战心得。今天我就把这些经验整理出来,目标是让你在5分钟内,能有一个清晰、可运行的代码框架,理解每一步背后的逻辑,而不仅仅是复制粘贴。
SM2作为国家密码管理局发布的椭圆曲线公钥密码算法标准,在金融、政务、物联网等对安全性要求极高的场景中应用越来越广。在HarmonyOS应用开发中,尤其是涉及用户身份认证、数据传输完整性校验、交易指令签名等环节,集成SM2几乎是刚需。ArkTS作为HarmonyOS主推的应用开发语言,其安全API的设计既强大又略显复杂,初次接触容易找不到北。本文将从零开始,手把手带你构建一个完整的SM2签名验签Demo,并深入讲解每个API的用途、参数含义以及实际开发中那些文档里没写的“坑”。
2. 环境准备与项目初始化
2.1 开发环境确认
首先,确保你的开发环境已经就绪。你需要安装最新版本的DevEco Studio。我写这篇文章时使用的是4.1 Release版本,配套的HarmonyOS SDK API版本是10。这一点很重要,因为安全相关的API在不同API版本上可能有细微差别。你可以在DevEco Studio的 File > Settings > SDKs 中查看和管理已安装的SDK版本。
创建一个新的Empty Ability工程,选择ArkTS作为开发语言,Compile SDK版本选择API 10或更高。项目创建好后,我们重点关注两个目录: entry/src/main/ets 下的业务逻辑代码,以及 entry/src/main/resources/base/profile 下的权限配置文件。
2.2 权限声明与模块导入
HarmonyOS的密钥管理服务(Universal Keystore Kit)需要声明相应的权限。打开 module.json5 文件,在 module 字段下的 requestPermissions 数组中添加以下权限:
{
"name": "ohos.permission.ACCESS_UKS"
}
这个权限是应用访问底层密钥系统服务所必需的。没有它,后续所有关于密钥的操作都会失败。
接下来,在需要用到签名验签功能的ArkTS文件(例如 Index.ets )顶部,导入相关的模块:
import { huks } from '@kit.UniversalKeystoreKit'; // 密钥管理核心模块
import { buffer } from '@kit.ArkTS'; // 用于处理二进制数据
import { BusinessError } from '@kit.BasicServicesKit'; // 错误处理
huks 模块是主角,它提供了密钥生成、导入、使用(加解密、签名验签)等全套接口。 buffer 模块用于在ArkTS中处理ArrayBuffer和Uint8Array,因为密钥和签名数据通常是二进制的。
3. SM2密钥对生成与管理
3.1 密钥属性定义
在HarmonyOS的UKS体系中,一切操作都围绕 HuksParam 这个属性参数来展开。生成一个SM2密钥对,首先需要定义它的属性集。这就像你去定制一把锁,需要告诉锁匠锁的类型(SM2)、用途(签名验签)、密钥长度等信息。
// 定义SM2密钥对的属性参数
let keyAlias = 'my_sm2_key'; // 密钥别名,用于后续查找和管理
let properties: Array<huks.HuksParam> = [
{
tag: huks.HuksTag.HUKS_TAG_ALGORITHM,
value: huks.HuksKeyAlg.HUKS_ALG_SM2
},
{
tag: huks.HuksTag.HUKS_TAG_PURPOSE,
value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_SIGN | huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_VERIFY
},
{
tag: huks.HuksTag.HUKS_TAG_KEY_SIZE,
value: huks.HuksKeySize.HUKS_SM2_KEY_SIZE_256
},
{
tag: huks.HuksTag.HUKS_TAG_DIGEST,
value: huks.HuksKeyDigest.HUKS_DIGEST_SM3
},
{
tag: huks.HuksTag.HUKS_TAG_PADDING,
value: huks.HuksKeyPadding.HUKS_PADDING_NONE
},
{
tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE,
value: huks.HuksCipherMode.HUKS_MODE_ECB
}
];
let options: huks.HuksOptions = {
properties: properties
};
我们来逐一拆解这些属性:
- HUKS_TAG_ALGORITHM : 算法类型,设为
HUKS_ALG_SM2。 - HUKS_TAG_PURPOSE : 密钥用途。这里用按位或操作符
|同时指定了SIGN和VERIFY,表示这个密钥对既可用于签名,也可用于验签。如果你只需要签名,可以只设SIGN。 - HUKS_TAG_KEY_SIZE : 密钥长度。SM2标准曲线是256位,所以选择
HUKS_SM2_KEY_SIZE_256。 - HUKS_TAG_DIGEST : 摘要算法。SM2签名通常与SM3摘要算法配对使用,这是国密标准组合。
- HUKS_TAG_PADDING : 填充模式。对于SM2签名,通常不需要额外填充,设为
HUKS_PADDING_NONE。 - HUKS_TAG_BLOCK_MODE : 分组模式。虽然ECB模式通常用于对称加密,但在这里对于非对称算法的密钥属性定义中,它作为一个必填参数,按规范设置为
HUKS_MODE_ECB即可。
注意:属性集的完整性 。这些属性是一个整体,缺少任何一个关键属性(如DIGEST),密钥生成可能会失败,或者生成的密钥无法用于预期的操作。务必根据官方文档核对所需属性。
3.2 执行密钥生成
定义好属性后,就可以调用API生成密钥了。这是一个异步操作。
async function generateKeyPair(): Promise<void> {
try {
await huks.generateKeyItem(keyAlias, options);
console.info('SM2密钥对生成成功,别名:' + keyAlias);
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`密钥生成失败,错误码:${err.code}, 错误信息:${err.message}`);
// 这里可以根据不同的错误码进行更精细化的处理,例如密钥已存在等。
}
}
调用 huks.generateKeyItem(keyAlias, options) ,系统会在安全的硬件环境(如果设备支持)或软件密钥库中,生成一对SM2密钥。公钥和私钥都与我们指定的 keyAlias 绑定。私钥受到系统级保护,应用无法直接读取其原始字节,只能通过UKS服务在授权下使用它进行签名操作。
3.3 密钥的导出与存储
生成的密钥对存储在系统密钥库中。有时我们需要将公钥导出,分发给其他方用于验签。导出公钥也是一个异步过程。
async function exportPublicKey(): Promise<Uint8Array | null> {
let exportOptions: huks.HuksOptions = {
properties: [
{
tag: huks.HuksTag.HUKS_TAG_ALGORITHM,
value: huks.HuksKeyAlg.HUKS_ALG_SM2
}
]
};
try {
let exportResult = await huks.exportKeyItem(keyAlias, exportOptions);
// exportResult.outData 是 ArrayBuffer 类型,包含公钥数据
let publicKeyData = new Uint8Array(exportResult.outData as ArrayBuffer);
console.info('公钥导出成功,长度:' + publicKeyData.length);
// 在实际应用中,这里可以将 publicKeyData 转换为Base64或Hex字符串进行传输或存储
// let publicKeyBase64 = buffer.from(publicKeyData).toString('base64');
return publicKeyData;
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`公钥导出失败,错误码:${err.code}, 错误信息:${err.message}`);
return null;
}
}
导出的公钥数据是二进制格式(通常是X.509 SubjectPublicKeyInfo结构的DER编码)。在实际网络传输或存储时,我们通常会将其转换为Base64或十六进制字符串。这里用 buffer.from(publicKeyData).toString('base64') 可以方便地转换。
实操心得:密钥别名管理 。
keyAlias是你的应用内访问该密钥的唯一标识。建议设计一套清晰的命名规则,例如功能_算法_版本(如user_sign_sm2_v1),并考虑在应用卸载时是否要删除密钥(通过huks.deleteKeyItem)。对于长期使用的根密钥,妥善管理其别名和生命周期至关重要。
4. 使用SM2密钥进行数据签名
4.1 准备待签名数据
签名是针对数据的摘要(哈希值)进行的。首先,我们需要计算原始数据的SM3摘要。虽然UKS的签名接口内部也可以指定摘要算法并计算,但为了更清晰地展示流程,我们这里先显式地计算摘要。
假设我们有一条重要的交易指令字符串需要签名:
let sourceString: string = '支付订单:123456,金额:100.00元,时间:2024-05-27 10:30:00';
let sourceData: Uint8Array = new TextEncoder().encode(sourceString); // 将字符串转为Uint8Array
接下来,我们需要使用SM3算法计算这个数据的摘要。HarmonyOS的 cryptoFramework 提供了摘要计算功能。首先在文件顶部导入:
import { cryptoFramework } from '@kit.CryptoArchitectureKit';
然后计算SM3摘要:
async function sm3Digest(data: Uint8Array): Promise<Uint8Array> {
let md = cryptoFramework.createMd('SM3'); // 创建SM3摘要实例
await md.update({ data: data.buffer }); // 更新数据
let mdResult = await md.digest(); // 计算摘要
return new Uint8Array(mdResult.data); // 返回摘要的Uint8Array
}
// 调用
let digestData = await sm3Digest(sourceData);
console.info('SM3摘要计算完成,长度:' + digestData.length); // SM3摘要长度为32字节
4.2 构建签名参数与执行签名
有了摘要数据,我们就可以使用之前生成的私钥进行签名了。签名操作同样需要一套属性参数,用于告知UKS服务如何使用密钥。
async function signData(digest: Uint8Array): Promise<Uint8Array | null> {
// 1. 构建签名操作的参数
let signProperties: Array<huks.HuksParam> = [
{
tag: huks.HuksTag.HUKS_TAG_ALGORITHM,
value: huks.HuksKeyAlg.HUKS_ALG_SM2
},
{
tag: huks.HuksTag.HUKS_TAG_PURPOSE,
value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_SIGN
},
{
tag: huks.HuksTag.HUKS_TAG_DIGEST,
value: huks.HuksKeyDigest.HUKS_DIGEST_SM3
},
{
tag: huks.HuksTag.HUKS_TAG_PADDING,
value: huks.HuksKeyPadding.HUKS_PADDING_NONE
},
// 关键参数:指定签名时使用的用户ID(Z值)。SM2签名需要此参数。
{
tag: huks.HuksTag.HUKS_TAG_SM2_SIGN_USER_ID,
value: stringToUint8Array('1234567812345678') // 通常使用默认的16字节用户ID
}
];
let signOptions: huks.HuksOptions = {
properties: signProperties,
inData: digest.buffer // 传入待签名的摘要数据
};
// 2. 执行签名
try {
let signResult = await huks.sign(keyAlias, signOptions);
let signature = new Uint8Array(signResult.outData as ArrayBuffer);
console.info('签名成功,签名长度:' + signature.length); // SM2签名结果通常为64字节(R+S各32字节)
return signature;
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`签名失败,错误码:${err.code}, 错误信息:${err.message}`);
return null;
}
}
// 辅助函数:字符串转Uint8Array
function stringToUint8Array(str: string): Uint8Array {
return new TextEncoder().encode(str);
}
这里有几个关键点:
-
HUKS_TAG_PURPOSE:必须设置为HUKS_KEY_PURPOSE_SIGN。 -
HUKS_TAG_SM2_SIGN_USER_ID:这是SM2签名特有的一个参数,称为“用户标识符”或Z值。它参与SM2签名算法中公钥的哈希计算,用于增强安全性。通常使用一个默认的、双方约定的值,比如1234567812345678。 非常重要的一点是,签名和验签时必须使用相同的用户ID,否则验签会失败。 这是新手最容易踩的坑之一。 -
inData:这里我们传入的是计算好的SM3摘要。实际上,huks.sign接口也支持传入原始数据,并通过属性指定摘要算法,由UKS内部完成摘要计算。但显式地传入摘要可以让流程更清晰,也方便调试。
签名成功后,会得到一个二进制签名数据。对于SM2-with-SM3,签名结果通常是64字节(R和S分量各32字节)。这个签名值可以随原始数据一起发送给验证方。
5. 使用SM2公钥进行签名验证
5.1 验签方准备工作
验签方需要持有两样东西: 原始数据的摘要 (或原始数据本身,并约定好使用SM3摘要)和 签名值 。当然,最重要的是需要拥有对应的 SM2公钥 。
公钥的来源有两种:
- 从密钥库导出 :如果你是签名方同时也是验签方(例如本地验证),可以直接使用
exportPublicKey导出的公钥数据。 - 从外部接收 :更常见的场景是,服务端或另一台设备收到了签名数据,并同时收到了发送者的公钥(通常是Base64或Hex格式)。你需要将其解码为
Uint8Array。
假设我们以第二种场景为例,验签方收到了Base64编码的公钥、原始数据(或摘要)和签名。
// 假设从网络或存储中获取到以下Base64字符串
let receivedPublicKeyBase64: string = 'MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE...'; // 示例,实际很长
let receivedSignatureBase64: string = 'MEUCIQD...'; // 示例
let receivedOriginalData: string = '支付订单:123456,金额:100.00元,时间:2024-05-27 10:30:00';
// 1. 解码Base64数据
import { buffer } from '@kit.ArkTS';
let publicKeyData: Uint8Array = buffer.from(receivedPublicKeyBase64, 'base64').bufferToUint8Array();
let signatureData: Uint8Array = buffer.from(receivedSignatureBase64, 'base64').bufferToUint8Array();
// 2. 计算原始数据的SM3摘要(必须与签名时使用的摘要一致)
let dataToVerify: Uint8Array = new TextEncoder().encode(receivedOriginalData);
let digestToVerify = await sm3Digest(dataToVerify); // 复用之前的sm3Digest函数
5.2 导入公钥并执行验签
HarmonyOS UKS要求使用公钥进行验签前,需要先将公钥数据以“外部密钥”的形式导入到密钥库中,并分配一个临时的别名。
async function verifySignature(publicKey: Uint8Array, digest: Uint8Array, signature: Uint8Array): Promise<boolean> {
let verifyKeyAlias = 'temp_sm2_pub_key_for_verify'; // 临时公钥别名
// 1. 构建公钥导入属性
let importProperties: Array<huks.HuksParam> = [
{
tag: huks.HuksTag.HUKS_TAG_ALGORITHM,
value: huks.HuksKeyAlg.HUKS_ALG_SM2
},
{
tag: huks.HuksTag.HUKS_TAG_PURPOSE,
value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_VERIFY // 注意用途是VERIFY
},
{
tag: huks.HuksTag.HUKS_TAG_KEY_SIZE,
value: huks.HuksKeySize.HUKS_SM2_KEY_SIZE_256
},
{
tag: huks.HuksTag.HUKS_TAG_DIGEST,
value: huks.HuksKeyDigest.HUKS_DIGEST_SM3
},
{
tag: huks.HuksTag.HUKS_TAG_PADDING,
value: huks.HuksKeyPadding.HUKS_PADDING_NONE
},
{
tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE,
value: huks.HuksCipherMode.HUKS_MODE_ECB
},
// 关键属性:声明这是一个外部导入的公钥
{
tag: huks.HuksTag.HUKS_TAG_IS_KEY_ALIAS,
value: true
},
{
tag: huks.HuksTag.HUKS_TAG_IMPORT_KEY_TYPE,
value: huks.HuksImportKeyType.HUKS_KEY_TYPE_PUBLIC_KEY
}
];
let importOptions: huks.HuksOptions = {
properties: importProperties,
inData: publicKey.buffer // 传入公钥二进制数据
};
// 2. 导入公钥
try {
await huks.importKeyItem(verifyKeyAlias, importOptions);
console.info('验签公钥导入成功');
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`公钥导入失败,错误码:${err.code}, 错误信息:${err.message}`);
// 导入失败可能原因:公钥格式错误、属性不匹配等
return false;
}
// 3. 构建验签参数并执行验签
let verifyProperties: Array<huks.HuksParam> = [
{
tag: huks.HuksTag.HUKS_TAG_ALGORITHM,
value: huks.HuksKeyAlg.HUKS_ALG_SM2
},
{
tag: huks.HuksTag.HUKS_TAG_PURPOSE,
value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_VERIFY
},
{
tag: huks.HuksTag.HUKS_TAG_DIGEST,
value: huks.HuksKeyDigest.HUKS_DIGEST_SM3
},
{
tag: huks.HuksTag.HUKS_TAG_PADDING,
value: huks.HuksKeyPadding.HUKS_PADDING_NONE
},
// 关键:必须与签名时使用的用户ID完全一致!
{
tag: huks.HuksTag.HUKS_TAG_SM2_SIGN_USER_ID,
value: stringToUint8Array('1234567812345678')
}
];
let verifyOptions: huks.HuksOptions = {
properties: verifyProperties,
inData: digest.buffer, // 传入待验证的摘要
outData: signature.buffer // 传入待验证的签名
};
try {
await huks.verify(verifyKeyAlias, verifyOptions);
console.info('验签成功!数据完整且来源可信。');
// 验签成功后,删除临时导入的公钥
await huks.deleteKeyItem(verifyKeyAlias);
return true;
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`验签失败,错误码:${err.code}, 错误信息:${err.message}`);
// 验签失败可能原因:签名无效、数据被篡改、用户ID不匹配、公钥不匹配等
// 同样,删除临时密钥
await huks.deleteKeyItem(verifyKeyAlias).catch(() => {});
return false;
}
}
验签流程比签名多了一个“导入公钥”的步骤。注意导入时的属性:
-
HUKS_TAG_IS_KEY_ALIAS和HUKS_TAG_IMPORT_KEY_TYPE:明确告诉UKS,这是一个从外部导入的公钥。 -
HUKS_TAG_PURPOSE:设置为HUKS_KEY_PURPOSE_VERIFY,表明这个导入的密钥只用于验签。
最关键的一步是验签参数中的 HUKS_TAG_SM2_SIGN_USER_ID 。它的值 必须 与签名时使用的用户ID字节对字节完全相同。哪怕是一个字符的差异,也会导致验签失败。这是SM2算法实现中的一个特定要求,务必在双方系统中约定好这个值。
验签成功后, huks.verify 调用正常返回。如果签名无效或数据被篡改,则会抛出异常,错误码可以帮助定位问题。
6. 常见问题排查与实战技巧
6.1 错误码分析与处理
UKS操作失败时会抛出 BusinessError ,其中 code 属性是错误码。掌握常见错误码能快速定位问题。
-
401(HUKS_ERR_CODE_PERMISSION_DENIED) :权限不足。检查module.json5是否已声明ohos.permission.ACCESS_UKS权限。 -
801(HUKS_ERR_CODE_ILLEGAL_ARGUMENT) :参数非法。这是最常见的问题。仔细检查HuksOptions中的properties数组:- 属性标签
tag的值是否正确? - 属性值
value的类型和范围是否正确?(例如,PURPOSE是否用了正确的HuksKeyPurpose枚举值) - 属性集合是否完整?对比官方文档的“密钥生成属性”或“密码操作属性”表格。
- 属性标签
-
802(HUKS_ERR_CODE_NOT_EXIST) :密钥不存在。检查keyAlias是否拼写正确,密钥是否已成功生成或导入。 -
803(HUKS_ERR_CODE_INVALID_KEY_FILE) :密钥文件无效。可能发生在导入外部密钥时,公钥数据格式不正确(不是有效的X.509 DER编码)。 -
13000001(HUKS_ERR_CODE_VERIFICATION_FAILED) :验签失败。签名无效、数据被篡改、或者 用户ID不匹配 。首先排查用户ID。 -
-1:通用错误或系统内部错误。可以查看message获取更多信息,或者检查操作逻辑。
建议在捕获异常后,不仅打印错误码,还将当时操作的 keyAlias 和 options.properties 也打印出来,便于调试。
6.2 性能优化与最佳实践
- 密钥别名复用 :对于需要频繁使用的密钥(如应用内固定的签名密钥),应该在应用初始化时生成或导入一次,并持久化保存其别名。避免在每次签名/验签时都重新生成或导入密钥。
- 异步操作与UI :所有UKS API都是异步的。在UI线程中调用时,务必使用
async/await或.then/.catch,避免阻塞主线程。对于耗时操作(如密钥生成),可以给用户一个加载提示。 - 临时密钥清理 :像验签场景中导入的临时公钥,在验签操作完成后(无论成功与否),都应及时调用
huks.deleteKeyItem进行清理,避免密钥别名冲突和资源浪费。 - 用户ID标准化 :将SM2用户ID作为一个配置项或常量,在应用的所有签名和验签模块中统一使用。例如,可以定义一个常量文件:
// constants.ts export const SM2_DEFAULT_USER_ID = '1234567812345678'; export const SM2_DEFAULT_USER_ID_BYTES = new TextEncoder().encode(SM2_DEFAULT_USER_ID); - 数据摘要的考量 :在上面的示例中,我们显式地先计算SM3摘要再签名。你也可以利用UKS的“一站式”签名,将原始数据直接传给
huks.sign,并在属性中指定HUKS_TAG_DIGEST。UKS内部会先计算摘要再签名。两种方式都可以,显式计算摘要更灵活(例如,你可以先缓存摘要值),而一站式操作更简洁。根据你的业务场景选择。
6.3 跨平台兼容性考虑
如果你的HarmonyOS应用需要与其他系统(如后端Java服务、Web前端)进行签名验签交互,需要特别注意:
- 公钥格式 :HarmonyOS UKS导出和导入的公钥是X.509 DER格式。确保其他平台也能正确处理这种格式。常见的Java
PublicKey对象、OpenSSL命令行工具通常都支持导入DER格式的公钥。 - 签名格式 :UKS生成的SM2签名是简单的R||S拼接(各32字节)。有些平台或库可能使用ASN.1 DER编码的签名格式。如果遇到验签不通过,检查签名格式是否一致,可能需要进行格式转换。
- 用户ID :这是SM2特有的。必须确保通信双方使用完全相同的用户ID字节序列。
6.4 完整流程串联示例
最后,我们把上面的所有片段串联成一个完整的、可执行的函数流程,模拟一个简单的“客户端签名-服务端验签”场景:
async function demoFullProcess() {
console.info('=== 开始SM2签名验签完整演示 ===');
// 客户端:生成密钥对并导出公钥
await generateKeyPair();
let publicKeyData = await exportPublicKey();
if (!publicKeyData) {
console.error('公钥导出失败,流程终止');
return;
}
let publicKeyBase64 = buffer.from(publicKeyData).toString('base64');
console.info('客户端公钥(Base64):', publicKeyBase64.substring(0, 64) + '...');
// 客户端:准备数据并签名
let originalData = '这是一条需要签名的关键指令';
let dataBytes = new TextEncoder().encode(originalData);
let digest = await sm3Digest(dataBytes);
let signature = await signData(digest);
if (!signature) {
console.error('签名失败,流程终止');
return;
}
let signatureBase64 = buffer.from(signature).toString('base64');
console.info('客户端签名(Base64):', signatureBase64.substring(0, 64) + '...');
// 模拟网络传输:客户端将 [原始数据, 签名, 公钥] 发送给服务端
console.info('--- 模拟数据传输 ---');
// 服务端:接收数据并验签
let receivedData = originalData;
let receivedSignature = buffer.from(signatureBase64, 'base64').bufferToUint8Array();
let receivedPublicKey = buffer.from(publicKeyBase64, 'base64').bufferToUint8Array();
// 服务端:计算摘要
let serverDigest = await sm3Digest(new TextEncoder().encode(receivedData));
// 服务端:执行验签
let isVerified = await verifySignature(receivedPublicKey, serverDigest, receivedSignature);
if (isVerified) {
console.info('=== 验签成功!指令来源可信,可以执行。===');
} else {
console.error('=== 验签失败!指令可能被篡改或来源不可信,拒绝执行。===');
}
}
// 在合适的时机调用,例如按钮点击事件中
// demoFullProcess();
运行这个演示,你将在Log中看到完整的流程输出。通过这个实战演练,你应该对如何在HarmonyOS ArkTS应用中使用SM2密钥对有了清晰的理解。记住,安全无小事,尤其是在处理密钥和签名时,仔细检查每一个参数和步骤,才能构建出可靠的应用。
更多推荐



所有评论(0)