场景描述

  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. 密钥库公钥可导出,私钥不可见,如何与服务端交互使用同一套密钥
    1. 服务端使用客户端传入的公钥做加密,客户端使用对应私钥做解密;客户端解密失败。

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

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

解决方案

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

  1. 不管是在客户端还是服务端,在生成密钥之前,首先需要校验密钥是否存在,如果密钥本身就存在,就不需要再次创建导致密钥被覆盖。
  2. 再导入密钥的时候要根据对应的算法去使用对应的格式去导入公钥,可以参考支持的导入的算法密钥以及对应的导入格式
  3. 在涉及两端交互的过程中,需要判断客户端传出和服务器端接收的公钥别名和数据是否一致,避免因为request请求导致base64字符串特殊字符发生改变。

    处理方法:

    1. 在交互过程中使用十六进制字符串;
    2. 使用base64传递的时候,使用baseurl传递,即在转成字符串的时候把类型设置为BASIC_URL_SAFE。
      new util.Base64Helper().encodeToStringSync(data,util.Type.BASIC_URL_SAFE)
  4. 因为密钥不可见,所以使用huks.generateKeyItem()没有捕获到异常,既代表密钥生成成功,也可以通过 huks.hasKeyItem() 接口查询是否生成成功。

效果图

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

请求是否使用baseurl对比:

核心代码

/* 创建密钥对 */
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)和未序列化(裸密文/钥)的数据格式

    解决方案

    1. 将huks的公钥通过huks.exportKeyItem()接口导出后,因为密钥库没有提供格式转换的接口,需要使用到加解密算法框架中的 getAsyKeySpec 接口去获取公钥中的x和y的值,进而解析出公钥ASN.1格式为裸密钥。
    2. 如果服务端传入的密文是裸密文,需要通过SM2_Ciphertext工具类中的i2d_SM2_Ciphertext方法(附件)转成ASN.1格式的数据,如果已经是ASN.1格式,直接传入解密方法

    效果图

    核心代码

    /* 导出密钥对中的公钥并转成裸密文 */
    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及以上的密钥。

推荐内容
点击阅读全文
Logo

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

更多推荐