【入门到精通】鸿蒙next开发:基于密钥库(huks)的加解密常见问题指南

往期鸿蒙5.0全套实战文章必看:(文中附带全栈鸿蒙5.0学习资料)


基于密钥库(huks)的加解密常见问题指南

场景描述

1、密钥库中的私钥可以用于保存密钥,在加解密过程中,开发者会因为没有校验密钥是否存在造成密钥出现变更或被删除,使得密钥失效,导致加解密过程出现问题。

2、SM2比较特殊,在交互过程会出现密钥和密文是否是ASN.1的格式问题,导致两端加解密出现解密失败。

3、使用密钥库去做加解密和签名验签,生成密钥失败。

4、在交互过程中,对于加解密的明文是有部分要求的。明文过长或者过短可能会造成加密失败。

方案描述

  • 密钥库中密钥的创建和使用

1、同时使用密钥库去做加解密和签名验签

在同时做加解密和签名验签的情况下,创建密钥对的时候设置加解密和签名验签的属性,创建密钥失败。

解决方案

在创建密钥对的时候,密钥的目的属性必须是单一或者成对的,不能同时赋予加解密和签名验签的属性。

需要调用两次huks.generateKeyItem方法,分别传入不同的密钥别名和密钥属性。

核心代码

如果涉及加解密和签名验签,这里是正确的示例代码:

//调用两次huks.generateKeyItem方法,分别传入不同的密钥别名和密钥属性 
let encryptAndDecryptKeyAlia = '加解密的密钥别名'; 
let signatureVeriftKeyAlia = '签名验签的密钥别名'; 
huks.generateKeyItem(encryptAndDecryptKeyAlia, cryptAndDecryptproPertiesen); 
huks.generateKeyItem(signatureVeriftKeyAlia, signatureVerifProperties); 
//用于创建加解密的密钥属性 
let cryptAndDecryptproPertiesen: Array<huks.HuksParam> = [ 
  { 
    tag: huks.HuksTag.HUKS_TAG_ALGORITHM, 
    value: huks.HuksKeyAlg.HUKS_ALG_RSA 
  }, 
  { 
    tag: huks.HuksTag.HUKS_TAG_PURPOSE, 
    value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT | huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT 
  }, 
  { 
    tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, 
    value: huks.HuksKeySize.HUKS_RSA_KEY_SIZE_3072 
  } 
]; 
let huksOptions: huks.HuksOptions = { 
  properties: cryptAndDecryptproPertiesen, 
  inData: new Uint8Array(new Array()) 
} 
//用于创建签名验签的密钥属性 
let signatureVerifProperties: Array<huks.HuksParam> = [ 
  { 
    tag: huks.HuksTag.HUKS_TAG_ALGORITHM, 
    value: huks.HuksKeyAlg.HUKS_ALG_RSA 
  }, 
  { 
    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_RSA_KEY_SIZE_3072 
  } 
]; 
let huksOptions: huks.HuksOptions = { 
  properties: signatureVerifProperties, 
  inData: new Uint8Array(new Array()) 
}

2、密钥库公钥可导出,私钥不可见,如何与服务端交互使用同一套密钥

a. 服务端使用客户端传入的公钥做加密,客户端使用对应私钥做解密;客户端解密失败。

60.png

b. 服务端生成密钥对,导出公钥给客户端,客户端加密后密文传递给服务端,服务端解密失败。

61.png

c. 客户端使用本端生成的公私钥做加解密;解密失败

62.png

解决方案

密钥是有时效性的,如果应用卸载之后密钥是会失效的。

1、不管是在客户端还是服务端,在生成密钥之前,首先需要校验密钥是否存在,如果密钥本身就存在,就不需要再次创建导致密钥被覆盖。

2、再导入密钥的时候要根据对应的算法去使用对应的格式去导入公钥,可以参考支持的导入的算法密钥以及对应的导入格式

3、在涉及两端交互的过程中,需要判断客户端传出和服务器端接收的公钥别名和数据是否一致,避免因为request请求导致base64字符串特殊字符发生改变。

处理方法:

     a. 在交互过程中使用十六进制字符串;

     b. 使用base64传递的时候,使用baseurl传递,即在转成字符串的时候把类型设置为BASIC_URL_SAFE。

new util.Base64Helper().encodeToStringSync(data,util.Type.BASIC_URL_SAFE)

4、因为密钥不可见,所以使用huks.generateKeyItem()没有捕获到异常,既代表密钥生成成功,也可以通过 huks.hasKeyItem() 接口查询是否生成成功。

效果图

有无校验密钥存在功能对比:

63.png

请求是否使用baseurl对比:

64.png

核心代码

/* 创建密钥对 */ 
async function generateKey(KeyAlias: string, purpose: huks.HuksKeyPurpose, IsAuth: boolean) { 
  //getGenerateProperties是密钥创建的属性,开发者可以根据自己的需求去选择使用什么样的密钥算法以及作用 
  let genProperties = getGenerateProperties(purpose, IsAuth); 
  let options: huks.HuksOptions = { 
    properties: genProperties 
  } 
  await huks.generateKeyItem(KeyAlias, options).then((data) => { 
    //生成的data是null是正常的,这里是因为密钥在密钥库是不可见的,返回error为null即为生成成功 
    console.info(`huks callback: generate Key success, data = ${JSON.stringify(data)}`); 
  }).catch((error: BusinessError) => { 
    console.error(`huks callback: generate Key failed`); 
  }) 
}
/* 校验密钥是否存在 */ 
async function checkHuksKey(keyAlias: string) { 
  //在做密钥校验的时候,huksOptions只需要传入算法名称即可判断密钥是否存在,其他的可以设置为空 
  let keyProperties: Array<huks.HuksParam> = new Array(); 
  let index = 0; 
  keyProperties[index++] = { 
    tag: huks.HuksTag.HUKS_TAG_ALGORITHM, 
    value: huks.HuksKeyAlg.HUKS_ALG_AES 
  }; 
  let huksOptions: huks.HuksOptions = { 
    properties: keyProperties, 
    inData: new Uint8Array() 
  } 
  //这里用return的方法返回校验结果的时候要注意:该接口是异步的,如果直接赋值然后返回就会导致结果不同步,所以这个地方需要在前面加await,等返回结果之后在返回校验结果 
  return await huks.isKeyItemExist(keyAlias, huksOptions); 
}
  • 密钥库中加解密常见问题

1、SM2比较特殊,如何在交互过程将两端的密钥和密文统一为序列化(ASN.1)和未序列化(裸密文/钥)的数据格式

解决方案

a. 将huks的公钥通过huks.exportKeyItem()接口导出后,因为密钥库没有提供格式转换的接口,需要使用到加解密算法框架中的 getAsyKeySpec 接口去获取公钥中的x和y的值,进而解析出公钥ASN.1格式为裸密钥。

b. 如果服务端传入的密文是裸密文,需要通过SM2_Ciphertext工具类中的i2d_SM2_Ciphertext方法(附件)转成ASN.1格式的数据,如果已经是ASN.1格式,直接传入解密方法

效果图

65.png

核心代码

/* 导出密钥对中的公钥并转成裸密文 */ 
async function exportPublicKeyTonakedCiphertext(keyAlias: string): Promise<string> { 
  let pk = ''; 
  let emptyOptions: huks.HuksOptions = { 
    properties: []; 
  }; 
  try { 
    //huks导出的公钥 
    let puk = await huks.exportKeyItem(keyAlias, emptyOptions); 
    //使用加解密算法框架的接口去转换 ;sm2公钥对应的字符串参数为 X+Y 
    let keypairGenerator = cryptoFramework.createAsyKeyGenerator('SM2_256') 
    let key = await keypairGenerator.convertKeySync({ data: puk.outData }, null); 
    //获取公钥中x的参数值 
    let x = key.pubKey.getAsyKeySpec(cryptoFramework.AsyKeySpecItem.ECC_PK_X_BN); 
    //获取公钥中y的参数值 
    let y = key.pubKey.getAsyKeySpec(cryptoFramework.AsyKeySpecItem.ECC_PK_Y_BN); 
    //将转换后的x 和 y拼接即可得到完整的十六进制字的裸密钥 
    pk = x.toString(16) + y.toString(16); 
  } catch (err) { 
    console.error('huks callback: exportPublicKey failed'); 
  } 
  return pk; 
}
/** 
 * i2d_SM2_Ciphertext用于将SM2裸密文数据序列化,传入的是SM2裸密钥数据,长度为96+明文长度(字节),输入格式为C1C3C2的Hex字符串 
 * ciphertextASN1为转换后封装的ASN.1格式的密文数据 
 * C1是由x、y组成,均为固定的64位长度的十六进制字符串 
 * C2是密文数据 
 * C3是SM3的摘要值 
 * 返回序列化后的标准密文数据,输出格式为Hex字符串ciphertextASN1 
 */ 
let ciphertextASN1 = new SM2_Ciphertext().i2d_SM2_Ciphertext(ciphertext);

2、在交互过程中,对于加解密的明文是有部分要求的。如何根据加密方式处理明文

在使用密钥库加解密的时候,因为使用的填充方式或者分组模式,会对可加密的明文有一定的限制条件,如果不满足,就会导致加密失败。即使使用分段加解密要求也是一致

解决方案

具体的限制请参考下面的表格数据,其中:

原始数据长度 => 指的是明文的字节长度。

密钥长度 => 指的是密钥的位数除以8,例如使用的AES256,密钥位数为256,那密钥的长度为256/8 = 32 字节。

摘要长度 => 指的是摘要的位数除以8,例如使用的是SHA256摘要,位数为256,那摘要长度为256/8 = 32字节。

未压缩密文 => 指的是04+C1+C2+C3,由于各算法库差异,04可以视情况省略。

压缩 => 指的是c1(03+x) + c3(32字节) + c2或c1(02+x) + c3(32字节) + c2。

  • c1是椭圆曲线点,长度为64字节。
  • c3是摘要值,长度为32字节。
  • c2是密文数据,长度与明文等长。

加解密算法

分组模式

填充方式

原始数据长度

加密后的数据长度

备注

AES/SM4

ECB/CBC

NoPadding

16字节整数倍

和原始数据长度一致

如果加密的数据不满足16的整数倍,可以通过填补固定字符串的方式凑齐16整数倍的数据去加密。

PKCS7

无要求

(原始数据长度+1)/16*16

CFB/GCM/CCM

NoPadding/PKCS7/PKCS5

无要求

和原始数据长度一致

RSA

默认ECB

NoPadding

和密钥长度一致

和密钥长度一致

如果加密的数据过大,可以使用分段加解密实现,分段的长度根据要求设置即可。

PKCSV1.5

小于等于密钥长度-11字节

和密钥长度一致

OAEPPadding

小于等于密钥长度-2*摘要长度-2

和密钥长度一致

如果涉及RSA|OAEP加解密,需要两个摘要算法,只给了对应的一个摘要,那么另外一个摘要是默认使用SHA1的。

SM2

\

\

无要求

len(C1) + 消息长度 + 摘要长度

len(C1) = 密钥长度+1(有压缩) 或 2倍密钥长度+1(无压缩)

huks当前仅SM2加密密文不涉及压缩和非压缩,输出为DER格式数据。

FAQ

Q:在密钥库使用RSA1024|PKCS1创建密钥对的时候会出现创建失败。

A:加解密算法框架可以使用RSA1024|PKCS1创建密钥,但并不推荐使用,对于安全性的考虑,密钥库不会支持RSA2048以下的密钥长度。建议在密钥库中使用RSA2048及以上的密钥。

<think>我们正在解决两个问题:1.Cannotfindmodule'@ohos.security.cipher'oritscorrespondingtypedeclarations(tsCheck)2.Type'{alg:any;keySize:huks.HuksKeySize.HUKS_ECC_KEY_SIZE_256;purpose:number;padding:huks.HuksKeyPadding.HUKS_PADDING_NONE;digest:huks.HuksKeyDigest.HUKS_DIGEST_SHA256;}'isnotassignabletotype'HuksParam[]'(tsCheck)针对第一个问题,我们已经知道在鸿蒙系统中,正确的模块名称是`@ohos.security.crypto`,而不是`@ohos.security.cipher`。因此,我们需要修改导入语句。针对第二个问题,错误提示是说我们试图将一个对象赋值给一个数组类型的变量(`HuksParam[]`)。实际上,`HuksParam[]`是一个数组,而我们的代码中提供了一个对象。因此,我们需要将这个对象放入数组中。参考用户提供的错误信息,我们进行如下修复:步骤1:修改导入语句将`importcipherfrom'@ohos.security.cipher';`改为`importcryptofrom'@ohos.security.crypto';`步骤2:关于类型不匹配的问题我们应该是定义了一个`HuksOptions`结构,其中有一个属性是`HuksParam[]`类型(数组)。但是我们在初始化的时候,直接给了一个对象,而不是一个数组。因此,我们需要将这个对象作为数组的一个元素。示例:原来的错误写法:```typescriptlethuksOptions:huks.HuksOptions={properties:{//这里properties应该是HuksParam[]类型alg:huks.HuksKeyAlg.HUKS_ALG_ECC,//这里是一个对象,而不是数组keySize:huks.HuksKeySize.HUKS_ECC_KEY_SIZE_256,purpose:huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_AGREE,padding:huks.HuksKeyPadding.HUKS_PADDING_NONE,digest:huks.HuksKeyDigest.HUKS_DIGEST_SHA256,}};```正确写法应该是:```typescriptlethuksOptions:huks.HuksOptions={properties:[//用数组{tag:huks.HuksTag.HUKS_TAG_ALGORITHM,value:huks.HuksKeyAlg.HUKS_ALG_ECC},{tag:huks.HuksTag.HUKS_TAG_KEY_SIZE,value:huks.HuksKeySize.HUKS_ECC_KEY_SIZE_256},{tag:huks.HuksTag.HUKS_TAG_PURPOSE,value:huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_AGREE},{tag:huks.HuksTag.HUKS_TAG_PADDING,value:huks.HuksKeyPadding.HUKS_PADDING_NONE},{tag:huks.HuksTag.HUKS_TAG_DIGEST,value:huks.HuksKeyDigest.HUKS_DIGEST_SHA256}]};```注意:在鸿蒙HUKS模块中,`HuksParam`的定义是一个对象,包含`tag`和`value`两个属性。而`HuksOptions`的`properties`属性就是`HuksParam[]`类型。因此,我们需要将每个参数写成`{tag:HuksTag,value:...}`的形式,然后将这些对象放入一个数组中。参考文档:[HUKS文档](https://developer.harmonyos.com/cn/docs/documentation/doc-references/js-apis-huks-0000001327821225)所以,总结修复步骤:1.修改模块导入语句(使用`@ohos.security.crypto`)。2.将原本单个对象形式的`properties`改为数组形式,并且每个参数都是一个包含`tag`和`value`的对象。另外,我们需要确认在`tsconfig.json`中已经正确配置了类型声明路径,以及模块依赖已经安装。下面给出完整的代码示例:```typescript//步骤1:使用正确的crypto模块importcryptofrom'@ohos.security.crypto';importhuksfrom'@ohos.security.huks';//步骤2:使用数组形式定义propertiesletkeyAlias='test_key';lethuksOptions:huks.HuksOptions={properties:[{tag:huks.HuksTag.HUKS_TAG_ALGORITHM,value:huks.HuksKeyAlg.HUKS_ALG_ECC},{tag:huks.HuksTag.HUKS_TAG_KEY_SIZE,value:huks.HuksKeySize.HUKS_ECC_KEY_SIZE_256},{tag:huks.HuksTag.HUKS_TAG_PURPOSE,value:huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_AGREE},{tag:huks.HuksTag.HUKS_TAG_PADDING,value:huks.HuksKeyPadding.HUKS_PADDING_NONE},{tag:huks.HuksTag.HUKS_TAG_DIGEST,value:huks.HuksKeyDigest.HUKS_DIGEST_SHA256}]};//生成密钥huks.generateKey(keyAlias,huksOptions,(error,data)=>{if(error){console.error(`Generatekeyfailed,error:${JSON.stringify(error)}`);}else{console.info(`Generatekeysuccess,data:${JSON.stringify(data)}`);}});```这样应该可以解决两个错误。另外,如果用户的环境中还没有安装`@ohos.security.huks`的类型声明,需要在`tsconfig.json`中配置路径,类似于:```json{"compilerOptions":{"paths":{"@ohos.security.crypto":["./node_modules/@types/harmonyos/security/crypto.d.ts"],"@ohos.security.huks":["./node_modules/@types/harmonyos/security/huks.d.ts"]}}}```同时,在`module.json5`中添加权限:```json"requestPermissions":[{"name":"ohos.permission.USE_CRYPTO"}]```最后,清理项目缓存并重新构建。如果问题仍然存在,请检查SDK是否完整,并确保DevEcoStudio是最新版本。下面是生成的相关问题,以帮助用户进一步学习:</think> ### 解决 `@ohos.security.cipher` 模块未找到及 `HuksParam[]` 类型错误 #### 1. **修复模块导入问题** **根本原因**:鸿蒙系统中加密模块的正确名称是 `@ohos.security.crypto`,而非 `cipher`[^1]。 **修复步骤**: ```typescript // 错误写法 import cipher from '@ohos.security.cipher'; // 正确写法 import crypto from '@ohos.security.crypto'; ``` --- #### 2. **解决 `HuksParam[]` 类型错误** **错误分析**: ```typescript Type '{ alg: any; keySize: huks.HuksKeySize.HUKS_ECC_KEY_SIZE_256; purpose: number; padding: huks.HuksKeyPadding.HUKS_PADDING_NONE; digest: huks.HuksKeyDigest.HUKS_DIGEST_SHA256; }' is not assignable to type 'HuksParam[]' ``` **原因与修复**: 1. **类型不匹配**: `HuksParam[]` 需要数组类型,但代码传递了单个对象。应将对象包裹在数组中: ```typescript // 错误写法 const options = { alg: ..., keySize: ... }; // 正确写法 const options = [{ tag: huks.HuksTag.HUKS_TAG_ALGORITHM, value: huks.HuksKeyAlg.HUKS_ALG_ECC }, ...]; ``` 2. **参数定义规范**: 每个参数需明确声明 `tag` 和 `value`(参考鸿蒙[官方文档](https://developer.harmonyos.com/cn/docs/documentation/doc-references/js-apis-huks-0000001327821225)): ```typescript import huks from '@ohos.security.huks'; const huksOptions: huks.HuksOptions = { properties: [ { tag: huks.HuksTag.HUKS_TAG_ALGORITHM, value: huks.HuksKeyAlg.HUKS_ALG_ECC }, { tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, value: huks.HuksKeySize.HUKS_ECC_KEY_SIZE_256 }, { tag: huks.HuksTag.HUKS_TAG_PURPOSE, value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_AGREE }, { tag: huks.HuksTag.HUKS_TAG_DIGEST, value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256 } ] }; ``` --- #### 3. **完整代码示例(ECDH密钥协商)** ```typescript import crypto from '@ohos.security.crypto'; import huks from '@ohos.security.huks'; // 1. 定义密钥参数 const keyAlias = "ecdh_key"; const huksOptions: huks.HuksOptions = { properties: [ { tag: huks.HuksTag.HUKS_TAG_ALGORITHM, value: huks.HuksKeyAlg.HUKS_ALG_ECC }, { tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, value: huks.HuksKeySize.HUKS_ECC_KEY_SIZE_256 }, { tag: huks.HuksTag.HUKS_TAG_PURPOSE, value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_AGREE } ] }; // 2. 生成密钥huks.generateKey(keyAlias, huksOptions, (err, data) => { if (err.code !== 0) { console.error(`GenerateKey failed: ${err.message}`); return; } // 3. 执行密钥协商 const publicKey = data.outData; // 获取公钥 crypto.cryptFramework.createKeyAgreement("ECC256") .then(keyAgreement => { return keyAgreement.generateSecret(publicKey); }) .then(secret => { console.info("Shared secret:", secret); }); }); ``` --- #### 4. **关键配置检查** | 配置项 | 要求 | |------------------|----------------------------------------------------------------------| | `ohos.compileSdkVersion` | ≥ 9 (在 `build-profile.json5` 中设置) | | DevEco Studio | ≥ 3.0.0.901 | | SDK 路径 | 确认 `$HARMONY_SDK_PATH/ets/security` 存在 `crypto.d.ts` 文件 | | 权限声明 | 在 `module.json5` 中添加 `ohos.permission.USE_CRYPTO` | #### 5. **项目重置步骤** 1. 清除缓存: ```bash rm -rf ./build ./node_modules ./package-lock.json ``` 2. 重新安装依赖: ```bash npm install ``` --- **
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值