4

我想在我的 Android 上为客户端双向 TLS 使用硬件支持的密钥。应使用生物识别技术解锁钥匙。

我发现了如何在 Android 上生成硬件支持的密钥对:

KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance( KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
keyGenerator.initialize(
    new KeyGenParameterSpec.Builder(myAlias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
        .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
        .setUserAuthenticationRequired(true)
        .build());
keyGenerator.generateKeyPair();

以及如何使用指纹解锁硬件支持的私钥:

FingerprintManager fingerprintManager = (FingerprintManager) this.getSystemService(Context.FINGERPRINT_SERVICE);
PrivateKey key  = (PrivateKey) keyStore.getKey(myAlias, null);
Cipher cipher = Cipher.getInstance(cipherAlgorithm, "AndroidKeyStore");
cipher.init(Cipher.DECRYPT_MODE, key);
FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher);
fingerprintManager.authenticate(cryptoObject, cancellationSignal, 0, authenticationCallback, null);

我还可以将我的 HttpClient 配置为使用客户端证书:

// I have loaded the PrivateKey privateKey and Certificate certificate from PEM files
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(null);
final char pseudoSecretPassword[] = ("##" + System.currentTimeMillis()).toCharArray();
keyStore.setKeyEntry(
    PKIModule.DEFAULT_KEYSTORE_ALIAS,
    privateKey,
    pseudoSecretPassword,
    new Certificate[] {certificate}
);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(keyStore, pseudoSecretPassword);
KeyManager[] keyManagers = kmf.getKeyManagers();
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(keyManagers, trustManagers, new SecureRandom());
OkHttpClient newClient = new OkHttpClient.Builder()
    .sslSocketFactory(sslContext.getSocketFactory())
    .build();

但是,我没有找到直接解锁硬件支持的私钥以在 SSLContext 使用的 KeyManager 中使用的方法,因为解锁机制适用于加密对象而不是私钥。

如何使生物识别密钥解锁和 TLS 客户端证书在 Android 上协同工作?

更新

KeyProperties.PURPOSE_SIGN在@pedrofb 点之后,我更新了我的代码以使用and生成密钥对KeyProperties.DIGEST_NONE。我使用导入到服务器信任库的 CA 签署了客户端密钥对。以及基于 AndroidKeyStore 创建客户端的 KeyManager:

KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
factory.init(keyStore, null);
KeyManager[] keyManagers = factory.getKeyManagers();
sslContext.init(keyManagers, trustManagers, new SecureRandom());

然而,这失败了

W/CryptoUpcalls: Preferred provider doesn't support key:
W/System.err: java.security.InvalidKeyException: Keystore operation failed
        at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1256)
        at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1281)
        at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:54)
        at android.security.keystore.AndroidKeyStoreSignatureSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreSignatureSpiBase.java:219)
        at android.security.keystore.AndroidKeyStoreSignatureSpiBase.engineInitSign(AndroidKeyStoreSignatureSpiBase.java:99)
        at android.security.keystore.AndroidKeyStoreSignatureSpiBase.engineInitSign(AndroidKeyStoreSignatureSpiBase.java:77)
        at java.security.Signature$Delegate.init(Signature.java:1357)
        at java.security.Signature$Delegate.chooseProvider(Signature.java:1310)
        at java.security.Signature$Delegate.engineInitSign(Signature.java:1385)
        at java.security.Signature.initSign(Signature.java:679)
        at com.android.org.conscrypt.CryptoUpcalls.rawSignDigestWithPrivateKey(CryptoUpcalls.java:88)
        at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
        at com.android.org.conscrypt.NativeSsl.doHandshake(NativeSsl.java:383)
        at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:231)
        at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:336)
        at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.java:300)
        at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:185)
        at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.java:224)
        at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.java:107)
        at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.java:87)
        at okhttp3.internal.connection.Transmitter.newExchange(Transmitter.java:169)
        at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:41)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
        at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:94)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
        at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
        at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:88)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
        at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:221)
        at okhttp3.RealCall.execute(RealCall.java:81)
        at com.jemmic.secuchat.biometriccrypto.MainActivity.testMutualTLS(MainActivity.java:402)
        at com.jemmic.secuchat.biometriccrypto.MainActivity.access$300(MainActivity.java:87)
        at com.jemmic.secuchat.biometriccrypto.MainActivity$TestMutualTlsTask.doInBackground(MainActivity.java:315)
        at com.jemmic.secuchat.biometriccrypto.MainActivity$TestMutualTlsTask.doInBackground(MainActivity.java:311)
        at android.os.AsyncTask$2.call(AsyncTask.java:333)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:764)
W/System.err: Caused by: android.security.KeyStoreException: Incompatible padding mode
        at android.security.KeyStore.getKeyStoreException(KeyStore.java:1159)
        ... 43 more
W/CryptoUpcalls: Could not find provider for algorithm: NONEwithRSA
4

1 回答 1

1

一些考虑:

  • 除非设备具有硬件支持,否则密钥不是硬件支持的。您可以使用 . 检查密钥是否存储在安全硬件中KeyInfo.isInsideSecurityHardware()

  • TLS 需要数字签名,但您的密钥是为加密目的而创建的。你需要KeyProperties.PURPOSE_ENCRYPT改变KeyProperties.PURPOSE_SIGN

  • FingerprintManager 封装了Signature对象的使用但没有扩展默认需要的java.security.KeyStoreKeyManager

TLS 需要直接管理私钥,因为 TLS 协议在握手期间使用特定算法对部分共享数据执行签名。要使用 FingerprintManager,底层加密提供者应该直接支持它。

我相信这样做可以得到相同的结果:

1-用指纹解锁所需的钥匙

2- 将 AndroidKeyStore 提供给KeyManagerFactory

KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null,null);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(keyStore, pseudoSecretPassword);

我认为这个解决方案可以工作,但是您需要将证书与与服务器发送的已接受 CA 列表匹配的私钥相关联,因此您必须使用公钥颁发证书并将其存储在 android与私钥关联的密钥库。

您没有提到是否要使用这种风格的解决方案,这很复杂

如果您不打算使用证书,您可以编写自己的证书以在 TLS 握手期间KeyManager检索权限。PrivateKey在这里查看我的答案,它与您的用例非常相似,但使用的是 AndroidKeyChain 而不是 AndroidKeyStore

请求自动或用户选择适当的客户端证书

于 2019-04-15T20:36:43.677 回答