在 SSLContext 中使用硬件支持的密钥



我想在我的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 上协同工作?

更新

在@pedrofb点之后,我更新了我的代码以生成具有KeyProperties.PURPOSE_SIGNKeyProperties.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

一些注意事项:

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

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

  • FingerprintManager 封装了Signature对象的使用,但它不会扩展默认KeyManager所需的 java.security.KeyStore

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

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

1-使用指纹解锁所需的密钥

2-向KeyManagerFactory提供AndroidKeyStore

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

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

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

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

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

相关内容

  • 没有找到相关文章

最新更新