当前位置: 首页 > 工具软件 > TENCENT SOTER > 使用案例 >

Android原生生物识别 VS 微信Soter

景辰钊
2023-12-01

Google原生

介绍

在google官方的api中,有三个类作为生物&指纹识别的入口可以供开发者使用

FingerprintManagerCompat:指纹识别,Android M及以上,设备上有指纹模块,不提供UI;
BiometricPrompt:生物识别,Android P及以上,设备上有至少一个生物方式的安全验证模块,系统统一UI(只可以自定义一些文案);
FingerprintManager:指纹识别(已弃用,直接使用FingerprintManagerCompat即可)。
生物识别目前包含指纹识别与人脸识别(一直有传言,但截至Android Q release版仍然没有人脸识别的相关文档),它们与传统的pin验证和密码验证的区别是需要硬件支持。

证书链和根证书

证书链和根证书(密钥对的证书链的根)的作用是给设备的安全硬件(TEE)提供了一种方法来验证非对称密钥对是否存在于安全硬件中。

Android中判断密钥是否来自安全硬件:

KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
PrivateKey key = (PrivateKey) keyStore.getKey(alias, null);
KeyFactory factory = KeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore");
KeyInfo keyInfo = factory.getKeySpec(key, KeyInfo.class);
boolean result = keyInfo.isInsideSecureHardware();

假如Android系统不可靠(比如被root),那么isInsideSecureHardware有可能是不可靠的,此时使用此方法来验证密钥对是否来自安全硬件。

证书链和根证书的示例见“使用示例 ——非对称加密&证书链和根证书示例”

证书链和根证书的验证逻辑:

  1. 验证证书链每个证书都未过期;
  2. 验证证书链每个证书都可以被父级证书的公钥验签;
  3. 通过google提供的api(需翻墙)验证证书链每个证书是否被吊销;
  4. 验证根证书与Google认证根密钥签名的根证书一致;
    在实际测试中Redmi Note 5的安全硬件根证书有问题

Google认证根密钥签名的根证书:

-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIJAOj6GWMU0voYMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTYwNTI2MTYyODUyWhcNMjYwNTI0MTYy
ODUyWjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS
Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7
tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj
nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq
C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ
oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O
JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg
sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi
igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M
RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E
aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um
AGMCAwEAAaOBpjCBozAdBgNVHQ4EFgQUNmHhAHyIBQlRi0RsR/8aTMnqTxIwHwYD
VR0jBBgwFoAUNmHhAHyIBQlRi0RsR/8aTMnqTxIwDwYDVR0TAQH/BAUwAwEB/zAO
BgNVHQ8BAf8EBAMCAYYwQAYDVR0fBDkwNzA1oDOgMYYvaHR0cHM6Ly9hbmRyb2lk
Lmdvb2dsZWFwaXMuY29tL2F0dGVzdGF0aW9uL2NybC8wDQYJKoZIhvcNAQELBQAD
ggIBACDIw41L3KlXG0aMiS//cqrG+EShHUGo8HNsw30W1kJtjn6UBwRM6jnmiwfB
Pb8VA91chb2vssAtX2zbTvqBJ9+LBPGCdw/E53Rbf86qhxKaiAHOjpvAy5Y3m00m
qC0w/Zwvju1twb4vhLaJ5NkUJYsUS7rmJKHHBnETLi8GFqiEsqTWpG/6ibYCv7rY
DBJDcR9W62BW9jfIoBQcxUCUJouMPH25lLNcDc1ssqvC2v7iUgI9LeoM1sNovqPm
QUiG9rHli1vXxzCyaMTjwftkJLkf6724DFhuKug2jITV0QkXvaJWF4nUaHOTNA4u
JU9WDvZLI1j83A+/xnAJUucIv/zGJ1AMH2boHqF8CY16LpsYgBt6tKxxWH00XcyD
CdW2KlBCeqbQPcsFmWyWugxdcekhYsAWyoSf818NUsZdBWBaR/OukXrNLfkQ79Iy
ZohZbvabO/X+MVT3rriAoKc8oE2Uws6DF+60PV7/WIPjNvXySdqspImSN78mflxD
qwLqRBYkA3I75qppLGG9rp7UCdRjxMl8ZDBld+7yvHVgt1cVzJx9xnyGCC23Uaic
MDSXYrB4I4WHXPGjxhZuCuPBLTdOLU8YRvMYdEvYebWHMpvwGCF6bAx3JBpIeOQ1
wDB5y0USicV3YgYGmi+NZfhA4URSh77Yd6uuJOJENRaNVTzk
-----END CERTIFICATE-----

  1. 验证密钥对证书(证书链首位)的描述信息中attestationSecurityLevel元素被设置为TrustedEnvironment安全等级;
    (安全等级包含:TrustedEnvironment/Software/StrongBox)
    在实际测试中Redmi Note 5的证书链根证书与Google认证根密钥签名的根证书不一致,且它的安全级别为只是Software,官方的demo中为此情况做出以下提示:
    The root certificate is NOT correct. The attestation was probably generated by software, not in secure hardware. This means that, although the attestation contents are probably valid and correct, there is no proof that they are in fact correct. If you’re using a production-level system, you should now treat the properties of this attestation certificate as advisory only, and you shouldn’t rely on this attestation certificate to provide security guarantees.
  2. 验证密钥对证书的信息中包含,预设密钥对时设置的质疑值;(可选步骤)
  3. ASN.1解析。

具体详见:https://developer.android.com/training/articles/security-key-attestation#verifying

使用示例

安全方面,主要依靠Android原生的KeyStore来实现
对称加密的一般示例:

  1. 创建安全密钥
try {
    //也可以使用其他的加解密算法,官方文档都已列出
    KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
    try {
        //配置密钥的用途,加密还是解密(示例代码中的密钥通用于加密和解密)
        keyGenerator.init(new KeyGenParameterSpec.Builder(aliasName, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                //下面这一行是创建安全密钥的关键步骤,使用此设置后,只有用户通过了身份验证才可以使用此时创建的密钥
                .setUserAuthenticationRequired(true)
                .build());
    } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();
    }
    //建议在生成密钥之前提前检查是否存在相同aliasName的密钥
    SecretKey key = keyGenerator.generateKey();
} catch (NoSuchAlgorithmException e) {
    e.printStackTrace();
}
  1. 请求指纹(生物)验证
SecretKey key = (SecretKey) mKeyStore.getKey(aliasName, null);
Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
//与非KeyStore的方式不同,加密时不可以指定向量,否则会抛异常
cipher.init(Cipher.ENCRYPT_MODE, key);
//缓存加密自动生成的向量(如果需要解密)
byte[] cacheIv = cipher.getIV();
BiometricPrompt.CryptoObject cryptoObject = new BiometricPrompt.CryptoObject(cipher);
mBiometricPrompt.authenticate(cryptoObject
                    , new CancellationSignal()
                    , App.getInstance().getMainExecutor()
                    , new BiometricPrompt.AuthenticationCallback() {
                        @Override
                        public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
                            super.onAuthenticationSucceeded(result);
                            BiometricPrompt.CryptoObject cryptoObject = result.getCryptoObject();
                            Cipher cipher = cryptoObject.getCipher();
                            doEnc(cipher, "Bill".getBytes());
                        }
                    } .......);
  1. 加密/解密
byte[] resultBytes = cipher.doFinal(bytes);

非对称加密&证书链和根证书示例:

  1. 创建安全密钥
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
//支持Cipher的非对称算法只有RSA,如果使用Signature会有更多选择
//详见:https://developer.android.com/training/articles/keystore
KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
//为此密钥设定用途,加密&解密(!)
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
        .setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(512, RSAKeyGenParameterSpec.F4))
        .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA512)
        //使用RSA/ECB/PKCS1Padding
        .setBlockModes(KeyProperties.BLOCK_MODE_ECB)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
        //下面这一行是创建安全密钥的关键步骤,使用此设置后,只有用户通过了身份验证才可以使用此时创建的密钥
        .setUserAuthenticationRequired(true);
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
    //关键步骤,设置是否为该密钥生成认证证书,以及证书中的质疑值
    //此质疑值会包含在x509Certificate.getExtensionValue(Constants.KEY_DESCRIPTION_OID)中(KEY_DESCRIPTION_OID = "1.3.6.1.4.1.11129.2.1.17";)
    //质疑值的目的是使依赖方能够验证密钥是根据特定请求创建的,如果不需要验证此值,可以随机设置非空字段
    builder.setAttestationChallenge(genChallenge());
}
kpGenerator.initialize(builder.build());
kpGenerator.generateKeyPair();
  1. 创建RSA密钥
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
Certificate[] certificates = keyStore.getCertificateChain(alias);
//将证书链发送给服务器,由服务器验证
//示例:https://github.com/google/android-key-attestation/blob/master/server/src/main/java/com/android/example/KeyAttestationExample.java
//其主要逻辑是:先验证整条证书链的派生关系,再通过公用的查询地址(需翻墙)查询每个节点证书的有效性,最后验证根证书是否与Google预设的一致
verifyByServer(certificates);
  1. 验证证书链
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
//先加密数据
//虽然在第一步(创建安全密钥)中为此密钥设定了验证后才可使用的要求,但公钥本身的意义就是“所有人可知的”,因此公钥加密不需要验证用户身份,同理利用公钥验签也不需要验证用户身份
//如果强行使用目的为加密的Cipher进行生物验证,那么会抛出“Crypto primitive not backed by AndroidKeyStore provider”异常
Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_RSA + "/" + KeyProperties.BLOCK_MODE_ECB + "/" + KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1);
cipher.init(KeyProperties.PURPOSE_ENCRYPT, keyStore.getCertificate(alias).getPublicKey());
byte[] resultEncBytes = cipher.doFinal("Bill".getBytes());
//解密行为(即使用私钥的行为)必须经过验证
cipher.init(KeyProperties.PURPOSE_DECRYPT, keyStore.getKey(alias, null));
BiometricPrompt.CryptoObject cryptoObject = new BiometricPrompt.CryptoObject(cipher);
mBiometricPrompt.authenticate(cryptoObject
                    , new CancellationSignal()
                    , App.getInstance().getMainExecutor()
                    , new BiometricPrompt.AuthenticationCallback() {
                        @Override
                        public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
                            super.onAuthenticationSucceeded(result);
                            BiometricPrompt.CryptoObject cryptoObject = result.getCryptoObject();
                            Cipher cipher = cryptoObject.getCipher();
                            byte[] resultBytes = cipher.doFinal(resultEncBytes);
                            Log.e(TAG, "result: " + new String(resultBytes));
                        }
                    } .......);

较为安全的Google原生方案

SafetyNet+证书链(根正书)

SafetyNet保证了设备的真实性/应用的安全性等,但由于它依赖于Google Play Services,所以这里不再过多描述

SafetyNet请查看https://developer.android.com/training/safetynet

Soter(微信支付)

介绍

由微信推出的生物认证方案,与Google原生生物识别的区别:

  1. 实现了人脸识别(从源码类com.tencent.soter.core.model.ConstantsSoter中可以看到常量FACEID_AUTH)
  2. 不提供统一的认证UI
  3. 可以获得指纹索引
  4. 设备覆盖与局限(依赖于厂商的支持)

原理及步骤

原文(https://github.com/Tencent/soter/wiki/%E5%8E%9F%E7%90%86)

全套的Soter方案涉及三个key(ATTK,App Secure Key(ASK)以及AuthKey),且都是RSA-2048的非对称密钥,使用步骤如下:

  1. 设备出厂前在TEE中生成ATTK
    ATTK私钥存储在TEE中
    ATTK公钥被厂商发送到TAM服务器(腾讯)
  2. 第三方应用通过TEE生成唯一ASK
    ASK私钥存储在TEE中
    ASK公钥由开发者发送给应用服务器,发送的信息中包含{ASK公钥+辅助信息+ATTK对公钥数据的签名},应用服务器请求TAM服务器,校验接受的ASK公钥的合法性(用TAM中存的ATTK公钥对发送信息中的签名进行验签)
  3. 每个应用场景建议生成一对AuthKey
    AuthKey私钥存储在TEE中
    AuthKey公钥发送到应用服务器,如果是合法的公钥就存到应用服务器(这里需要应用服务器自行验证此公钥的合法性)
  4. 认证开始前,随机生成一个挑战因子
    开发者将{{挑战因子+fid+其他信息}+前面内容对应AuthKey的签名}发送给应用服务器,应用服务器通过存储的AuthKey公钥进行验签

使用示例及前后端接入方案

https://github.com/Tencent/soter/wiki/%E5%AE%89%E5%85%A8%E6%8E%A5%E5%85%A5

Soter的ATTK与原生的根证书对比

比较一下二者在实现硬件可信性上的限制:

设备厂商的限制设备覆盖范围(在硬件支持的前提下)第三方服务器的介入
ATTK厂商可能会上传虚假的ATTK-公钥到TAM绝大部分Android6.0及以上设备,部分Android5.0/5.1(需要厂商的额外支持,约等于所有可以使用微信指纹支付的手机TAM
根证书厂商可能会创建自己的根证书Android 7.0及以上设备,且是GMS设备(如果是非GMS设备,会有前面提到的厂商限制问题GMS
 类似资料: