基于密钥库签名验签的用户认证
场景描述
密钥库使用过程中除了用于加解密外,还用于程序访问控制认证和签名验签,常见的两个场景问题如下:
1、对于密钥库中用户认证以及签名验签的属性配置参数的问题。
2、密钥库中细粒度访问控制和普通访问控制的使用问题,以及在访问控制中进行签名验签使用问题。
场景一:使用创建的密钥去做用户认证、加签验签以及加解密时,密钥参数如何配置
方案:
1、对于密钥属性的使用,创建的每个密钥对的目的不同,也决定了他的使用方式,例如:加签验签的密钥,不可以用于加解密。
2、对于存在密钥失效的情况,在设置授权访问类型的时候选择密钥总是有效,也可以设置别的类型,请参考:HuksAuthAccessType使用也可以通过校验密钥是否存在来判断设置类型是否生效,以及检查密钥是在什么过程导致的失效。
3、使用ECC加签验签,不可以用于加解密,ECC不支持加解密。
算法 | 签名验签 | 支持的规格 | 加解密 | 支持的规格 |
RSA | 支持签名验签 | RSA/SHA256/PKCS1_V1_5 RSA/SHA384/PKCS1_V1_5 RSA/SHA512/PKCS1_V1_5 RSA/SHA256/PSS RSA/SHA384/PSS RSA/SHA512/PSS | 支持加解密 | RSA/ECB/NoPadding RSA/ECB/PKCS1_V1_5 RSA/ECB/OAEP | OAEP (备注:ECB为默认模式,可以不写) |
SM2 | 支持签名验签 | SM2/SM3 | 支持加解密 | SM2/NoPadding SM2/SM3 |
AES | 不支持签名验签 | \ | 支持加解密 | AES/CBC/NoPadding AES/CBC/PKCS7 AES/CTR/NoPadding AES/GCM/NoPadding |
SM4 | 不支持签名验签 | \ | 支持加解密 | SM4/CTR/NoPadding SM4/CBC/NoPadding |
ECC | 支持签名验签 | ECC/SHA256 ECC/SHA384 ECC/SHA512 | 不支持加解密 | \ |
效果图:
核心代码:
用于签名验签和加解密的密钥属性(传入对应的huks.HuksKeyPurpose)
//生成密钥属性信息
function getGenerateProperties(purpose: huks.HuksKeyPurpose) {
let properties: Array<huks.HuksParam> = new Array();
let index = 0;
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_ALGORITHM,
value: huks.HuksKeyAlg.HUKS_ALG_SM2
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_KEY_SIZE,
value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_PURPOSE,
value: purpose
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_DIGEST,
value: huks.HuksKeyDigest.HUKS_DIGEST_SM3
};
return properties;
}
用于用户认证的密钥属性。
//生成密钥属性信息
function getGenerateProperties() {
let properties: Array<huks.HuksParam> = new Array();
let index = 0;
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_ALGORITHM,
value: huks.HuksKeyAlg.HUKS_ALG_SM2
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_KEY_SIZE,
value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_PURPOSE,
value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_SIGN | huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_VERIFY
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_DIGEST,
value: huks.HuksKeyDigest.HUKS_DIGEST_SM3
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_USER_AUTH_TYPE,
value: huks.HuksUserAuthType.HUKS_USER_AUTH_TYPE_FINGERPRINT
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_KEY_AUTH_ACCESS_TYPE,
value: huks.HuksAuthAccessType.HUKS_AUTH_ACCESS_ALWAYS_VALID
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_CHALLENGE_TYPE,
value: huks.HuksChallengeType.HUKS_CHALLENGE_TYPE_NORMAL
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_KEY_AUTH_PURPOSE,
value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_SIGN | huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_VERIFY
};
return properties;
}
调用方法:在创建密钥对的时候传入。
let prop = getGenerateProperties();
let huksOptions: huks.HuksOptions = { properties: prop }
await generateKeyItem(keyAlias, huksOptions)
.then((data) => {
console.info(`huks callback promise: generateKeyItem success, data = ${JSON.stringify(data)}`);
})
.catch((error: BusinessError) => {
console.error(`huks callback promise: generateKeyItem failed` + JSON.stringify(error));
});
场景二:使用细粒度访问控制和普通访问控制做签名验签注意事项以及方案
方案:
1、在使用细粒度访问控制签名验签时和使用普通的访问控制签名验签时要注意需要在创建密钥的时候设置HUKS_TAG_USER_AUTH_TYPE、HUKS_TAG_KEY_AUTH_ACCESS_TYPE、HUKS_TAG_CHALLENGE_TYPE属性,不同处在于细粒度访问控制的签名验签需要设置HUKS_TAG_KEY_AUTH_PURPOSE属性,而在使用普通的访问控制签名验签时不需要设置。HUKS_TAG_KEY_AUTH_PURPOSE属性。
属性 | 用途 |
HUKS_TAG_USER_AUTH_TYPE | 表示用户认证类型 |
HUKS_TAG_KEY_AUTH_ACCESS_TYPE | 表示安全访问控制类型 |
HUKS_TAG_CHALLENGE_TYPE | 表示密钥使用时生成的challenge类型。 |
HUKS_TAG_KEY_AUTH_PURPOSE | 表示密钥认证用途的tag |
2、细粒度访问控制和普通访问控制的异同:
相同:生成/导入密钥后,即使应用进程被攻击也不会导致未经用户授权的密钥访问,一般用于高敏感且高级别安全保护的场景,比如免密登录、免密支付、自动填充密码保护等。
不同:细粒度用户身份认证访问控制是基于已有用户身份认证访问控制的扩展,提供了基于生物特征和锁屏密码二次身份认证的细粒度访问控制能力,允许设置密钥在加密、解密、签名、验签、密钥协商、密钥派生的单个或多个场景时是否需要进行身份验证。如:业务需要使用HUKS密钥加密保存账号密码信息等数据,要求在加密的时候不进行指纹等身份认证,解密的时候需要进行指纹等身份认证,这是就需要依赖HUKS提供细粒度的二次身份认证访问控制机制。
3、在访问控制时,密钥生成失败有以下两种情况:
a. 设置了对应的AccessType,但没有录入对应的生物特征:
HUKS_AUTH_ACCESS_INVALID_CLEAR_PASSWORD ->需预置PIN码
HUKS_AUTH_ACCESS_INVALID_NEW_BIO_ENROLL ->需要预置人脸/指纹
b. 设置的认证类型组合不支持。暂不支持 HUKS_USER_AUTH_TYPE_PIN 与 HUKS_AUTH_ACCESS_INVALID_NEW_BIO_ENROLL的组合
效果图:
核心代码:
第一步:创建细粒度访问控制签名验签的密钥对 创建密钥对对应属性:
/*密钥属性 用于细粒度用户程序访问控制的签名验签创建密钥的属性 */
function getGenerateProperties() {
let properties: Array<huks.HuksParam> = new Array();
let index = properties.length;
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_ALGORITHM,
value: huks.HuksKeyAlg.HUKS_ALG_ECC
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_KEY_SIZE,
value: huks.HuksKeySize.HUKS_ECC_KEY_SIZE_256
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_PURPOSE,
value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_SIGN | huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_VERIFY
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_DIGEST,
value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_USER_AUTH_TYPE,
value: huks.HuksUserAuthType.HUKS_USER_AUTH_TYPE_FINGERPRINT
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_KEY_AUTH_ACCESS_TYPE,
value: huks.HuksAuthAccessType.HUKS_AUTH_ACCESS_ALWAYS_VALID
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_CHALLENGE_TYPE,
value: huks.HuksChallengeType.HUKS_CHALLENGE_TYPE_NORMAL
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_KEY_AUTH_PURPOSE,
value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_SIGN | huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_VERIFY
};
return properties;
}
第二步:进行用户认证后签名。
1、调用 let prop = getKeyPropV(huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_SIGN, isAuthBio);获取到签名功能对应的属性
/*签名验签属性*/
function getKeyPropV(purpose: huks.HuksKeyPurpose) {
let properties: Array<huks.HuksParam> = new Array();
let index = properties.length;
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_ALGORITHM,
value: huks.HuksKeyAlg.HUKS_ALG_ECC
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_KEY_SIZE,
value: huks.HuksKeySize.HUKS_ECC_KEY_SIZE_256
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_PURPOSE,
value: purpose
};
properties[index++] = {
tag: huks.HuksTag.HUKS_TAG_DIGEST,
value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256
};
return properties;
}
2、调用initSession初始化 (传入密钥别名和签名)获取到初始化后的挑战值。
3、将拿到的跳转值传入用户认证的方法authBio (传入挑战值) 注:用户认证方法在最后
4、用户认证后得到一个用户标识token。
5、将用户标识token传入调用的finishSession接口,进行签名。
6、将签名数据(uint8Array数组)转为Hex格式字符串用于保存。
//在用户认证后进行签名
async function signature(keyAlias: string, plaintext: Uint8Array, isAuthBio: Boolean): Promise<Uint8Array> {
let prop = getKeyPropV(huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_SIGN, isAuthBio);
let options: huks.HuksOptions = {
properties: prop,
inData: plaintext
}
let handle: number = 0;
let authChallenge: Uint8Array | undefined = new Uint8Array(0);
let authToken: Uint8Array | undefined = undefined;
let outData: Uint8Array | undefined = new Uint8Array(0);
await huks.initSession(keyAlias, options).then((hukHandle) => {
handle = hukHandle.handle;
if (isAuthBio) {
//拿到挑战值,用于用户认证
authChallenge = hukHandle.challenge;
}
}).catch((err: BusinessError) => {
console.error(JSON.stringify(err));
});
if (isAuthBio) {
//用户认证,传入密钥库初始化拿到的挑战值,认证结束后,取出返回结果,拿到用户标识token
authToken = await authBio(authChallenge);
}
//传入用户标识token,进行加签
await huks.finishSession(handle, options, authToken).then((hukRet) => {
//用户认证加签结果,得到Uint8Array数组
outData = hukRet.outData;
}).catch((err: BusinessError) => {
console.error(JSON.stringify(err));
})
return outData;
}
第三步:调用verify方法进行用户访问控制的验签。
1、调用 let prop = getKeyPropV(huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_VERIFY isAuthBio);获取到验签功能对应的属性。
2、调用initSession初始化 (传入密钥别名和签名)获取到初始化后的挑战值。
3、将拿到的跳转值传入用户认证的方法authBio (传入挑战值) 注:用户认证方法在最后
4、用户认证后得到一个用户标识token。
5、将用户标识token和签名后数据以及待验签数据传入调用的finishSession接口进行验签 注:调用的finishSession接口中传入的options的数据inData值要和initSession初始化的值区分
6、验签成功后,返回的数组为全是0的数组。
//在用户认证后进行验签
async function verify(keyAlias: string, plaintext: Uint8Array, signature: Uint8Array, isAuthBio: Boolean = false): Promise<Boolean> {
let prop = getKeyPropV(huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_VERIFY, isAuthBio);
let options: huks.HuksOptions = {
properties: prop,
inData: plaintext
}
let result: Boolean = false;
let handle: number = 0;
let authChallenge: Uint8Array | undefined = new Uint8Array(0);
await huks.initSession(keyAlias, options).then((hukHandle) => {
handle = hukHandle.handle;
if (isAuthBio) {
//拿到挑战值,用于用户认证
authChallenge = hukHandle.challenge;
}
}).catch((err: BusinessError) => {
console.error(JSON.stringify(err));
result = false;
});
if (isAuthBio) {
console.error(`huks callback authChallenge: ` + authChallenge);
//用户认证,传入密钥库初始化拿到的挑战值,认证结束后,取出返回结果,拿到用户标识token
authToken = await authBio(authChallenge);
console.error(`huks callback authToken: ` + authToken);
}
//这个地方要注意,要传入的是待验签的数据,和上面初始化的inData是不一样的
options.inData = signature;
await huks.finishSession(handle, options, authToken)
.then((hukRet) => {
//用户认证验签结果,得到Uint8Array数组,此时数组数据为全是0的数组,代表验签成功
result = true;
}).catch((err: BusinessError) => {
console.error('finish error: ' + JSON.stringify(err));
result = false;
})
return result;
}
用户认证的方法。
async function authBio(authChallenge: Uint8Array): Promise<Uint8Array> {
return new Promise((resolve, reject) => {
// 设置认证参数
const authParam: userIAM_userAuth.AuthParam = {
challenge: authChallenge,
authType: [userIAM_userAuth.UserAuthType.FINGERPRINT],
authTrustLevel: userIAM_userAuth.AuthTrustLevel.ATL4,
};
// 配置认证界面
const widgetParam: userIAM_userAuth.WidgetParam = {
title: '请进行身份认证',
};
try {
// 获取认证对象
let userAuthInstance = userIAM_userAuth.getUserAuthInstance(authParam, widgetParam);
// 订阅认证结果
userAuthInstance.on('result', {
onResult(result) {
console.error('result:' + JSON.stringify(result));
// 可在认证结束或其他业务需要场景,取消订阅认证结果
userAuthInstance.off('result');
if (result.result == userIAM_userAuth.UserAuthResultCode.SUCCESS) {
resolve(result.token);
} else {
reject(result.result);
}
}
});
userAuthInstance.start();
} catch (error) {
const err: BusinessError = error as BusinessError;
console.log(`auth catch error. Code is ${err?.code}, message is ${err?.message}`);
reject(err?.code);
}
});
}