android - 在 SSLContext 中使用硬件支持的 key

标签 android security keystore sslcontext secure-element

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

我找到了如何在 Android 上生成硬件支持的 key 对:

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 的方法,因为解锁机制适用于加密对象而不是私钥。

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

更新

按照@pedrofb 的观点,我更新了我的代码以使用KeyProperties.PURPOSE_SIGNKeyProperties.DIGEST_NONE 生成 key 对。我使用导入到服务器信任库中的 CA 签署了客户端 key 对。以及基于 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

最佳答案

一些注意事项:

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

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

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

TLS 需要直接管理私钥,因为TLS 协议(protocol)在握手时使用特定的算法对部分共享数据进行签名。要使用 FingerprintManager,底层加密提供程序应直接支持它。

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

1- 用指纹解锁想要的 key

2- 将 AndroidKeyStore 提供给 KeyManagerFactory

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

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

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

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

Request with automatic or user selection of appropriate client certificate

关于android - 在 SSLContext 中使用硬件支持的 key ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55692965/

相关文章:

security - 是否需要反向代理背后的 HTTPS?

java - Axis over SSL 和使用 PKCS#12 keystore 的双向身份验证

java - 如何将 SHA1 证书作为 PrivateKeyEntry 类型而不是 trustCertEntry 导入到 keystore 中

android - 运行 ionic cordova build android 时如何修复 AAPT 错误前景 Activity 未找到?

Android 提取解码编码 MUX 音频

encryption - 如何保护/加密 Lucene 索引?

java - 使用生成的 keystore 文件的 tomcat https 配置

java - 如何在应用程序运行时探索 SQLite 数据库? Eclipse (Juno) > Android (adb)

android - 如何获取 Android 应用程序的构建/版本号?

java - AES 加密(Python 和 Java)