基于密钥库(huks)的加解密常见问题指导
场景描述
- 密钥库中的私钥可以用于保存密钥,在加解密过程中,开发者会因为没有校验密钥是否存在造成密钥出现变更或被删除,使得密钥失效,导致加解密过程出现问题。
- SM2比较特殊,在交互过程会出现密钥和密文是否是ASN.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()) }
- 密钥库公钥可导出,私钥不可见,如何与服务端交互使用同一套密钥
- 服务端使用客户端传入的公钥做加密,客户端使用对应私钥做解密;客户端解密失败。
- 服务端生成密钥对,导出公钥给客户端,客户端加密后密文传递给服务端,服务端解密失败。
- 客户端使用本端生成的公私钥做加解密;解密失败
- 服务端使用客户端传入的公钥做加密,客户端使用对应私钥做解密;客户端解密失败。
解决方案
密钥是有时效性的,如果应用卸载之后密钥是会失效的。
- 不管是在客户端还是服务端,在生成密钥之前,首先需要校验密钥是否存在,如果密钥本身就存在,就不需要再次创建导致密钥被覆盖。
- 再导入密钥的时候要根据对应的算法去使用对应的格式去导入公钥,可以参考支持的导入的算法密钥以及对应的导入格式。
- 在涉及两端交互的过程中,需要判断客户端传出和服务器端接收的公钥别名和数据是否一致,避免因为request请求导致base64字符串特殊字符发生改变。
处理方法:
- 在交互过程中使用十六进制字符串;
- 使用base64传递的时候,使用baseurl传递,即在转成字符串的时候把类型设置为BASIC_URL_SAFE。
new util.Base64Helper().encodeToStringSync(data,util.Type.BASIC_URL_SAFE)
- 因为密钥不可见,所以使用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);
}
- 密钥库中加解密常见问题
- SM2比较特殊,如何在交互过程将两端的密钥和密文统一为序列化(ASN.1)和未序列化(裸密文/钥)的数据格式
解决方案
- 将huks的公钥通过huks.exportKeyItem()接口导出后,因为密钥库没有提供格式转换的接口,需要使用到加解密算法框架中的 getAsyKeySpec 接口去获取公钥中的x和y的值,进而解析出公钥ASN.1格式为裸密钥。
- 如果服务端传入的密文是裸密文,需要通过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);
- 在交互过程中,对于加解密的明文是有部分要求的。如何根据加密方式处理明文
在使用密钥库加解密的时候,因为使用的填充方式或者分组模式,会对可加密的明文有一定的限制条件,如果不满足,就会导致加密失败。即使使用分段加解密要求也是一致
解决方案
具体的限制请参考下面的表格数据,其中:
原始数据长度 => 指的是明文的字节长度。
密钥长度 => 指的是密钥的位数除以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及以上的密钥。
更多推荐





所有评论(0)