Android Java更新Android KeyStore中的证书和私钥 [英] Android java updating certificate and private keys in Android KeyStore
问题描述
我有一个使用HTTPS客户端证书进行身份验证的系统,但是证书本身是根据以下过程生成的:
I have a system that uses HTTPS client certificates to authenticate, but the certificates themselves are generated, according to the following process:
- 客户端设备生成证书(包括公钥和私钥)
- 客户端设备将公钥发送到服务器,服务器对公钥进行签名,并将其作为签名证书返回
- 客户端以安全的方式存储证书,然后将其用作HTTPS客户端证书
我们已经将该系统在iOS上运行,并且我试图移植到android,但是由于Android的文档记录薄弱且安全的API令人困惑,因此遇到了很多问题.
We have this system working on iOS, and I'm trying to port across to android, but encountering a lot of problems with Android's poorly documented and confusing security API's.
我的代码大致如下:
生成证书
keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
keyStore.load(null);
Date startDate = new Date();
Date endDate = new Date(startDate.getTime() + FORTY_YEARS_IN_MILLISECONDS);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
.setAlias(alias)
.setKeySize(2048)
.setKeyType(KeyProperties.KEY_ALGORITHM_RSA)
.setSubject(new X500Principal("CN=" + alias))
.setSerialNumber(BigInteger.TEN)
.setStartDate(startDate)
.setEndDate(endDate)
.build();
KeyPairGenerator generator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEYSTORE);
generator.initialize(spec);
KeyPair keyPair = generator.generateKeyPair(); // this will put a certificate and key pair in the keyStore.
dumpKeyStore(keyStore);
byte[] entireKey = keyPair.getPublic().getEncoded();
// chop off first 24 bytes; the java key pair generator puts an object ID of 1.2.840.113549.1.1.1 RSA (RSA_SIGN) before the key which gets mangled when the server signs and sends back the certificate
byte[] publicKeyBytes = Arrays.copyOfRange(entireKey, 24, entireKey.length);
dumpKeyStore
是一种实用程序方法,它迭代密钥库,调用 keyStore.getEntry
来获取每个条目,并仅记录事物.此时,它将报告具有给定别名的单个条目,并且其类型为 KeyStore.PrivateKeyEntry
.它具有关联的证书和公共密钥,可以从 PrivateKeyEntry
中获取.
dumpKeyStore
is a utility method which iterates the keystore, calls keyStore.getEntry
to get each entry and and just logs things.
At this point, it reports that there is a single entry with the given alias, and it is of type KeyStore.PrivateKeyEntry
. It has an associated certificate and public key which can be retrived from the PrivateKeyEntry
.
发送到服务器
publicKeyBytes
发送到服务器,服务器将其作为新的签名x509证书的公钥,该证书将在响应中发送回去.我没有输入代码,这只是基本的网络.根据我的判断,返回的证书已加载并且看起来还不错.
publicKeyBytes
is sent to the server, which puts it as the public key for a new, signed x509 certificate, which is sent back in the response. I haven't put code in, it's just basic networking. The returned certificate loads and looks fine from what I can tell.
保存和关联证书
我正在尝试使用相同的别名将其放入keyStore,因此(理论上)它可以与早期的正确私钥相关联.到目前为止,我的代码是这样的:
I'm trying to put it into the keyStore with the same alias, so it (in theory) can be associated with the correct private key from earlier. My code thus far is like this:
KeyStore keyStore;
try {
keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
keyStore.load(null);
}catch (IOException | NoSuchAlgorithmException | CertificateException e) {
Log.wtf(TAG, e);
throw new FatalError(TAG, e);
}
CertificateFactory certificateFactory;
try {
certificateFactory = CertificateFactory.getInstance("X.509");
} catch (CertificateException e) {
Log.wtf(TAG, e);
throw new FatalError(TAG, e);
}
Certificate cert = certificateFactory.generateCertificate(new ByteArrayInputStream(certificateFromServer));
// find the existing certificate, copy it's private key out, then replace the certificate with the one from the server but keeping the private key
try {
KeyStore.PrivateKeyEntry existingPrivateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);
KeyStore.PrivateKeyEntry newEntry = new KeyStore.PrivateKeyEntry(existingPrivateKeyEntry.getPrivateKey(), new Certificate[]{ cert });
keyStore.setEntry(alias, newEntry, null);
} catch (Exception e) {
Log.wtf(TAG, e);
throw new FatalError(TAG, e);
}
dumpKeyStore(keyStore);
在这一点上,最终的dumpKeyStore指示存在具有正确别名的条目,但是在尝试调用 keyStore.getEntry
At this point, the final dumpKeyStore indicates that there is an entry with the correct alias, however it gets a "NoSuchAlgorithmException: Unknown key entry" exception thrown when it tries to call keyStore.getEntry
我要执行的操作(替换证书但保留私钥)是否可能在android中进行?如果是这样,我该怎么办?看来这真的没有用
Is what I'm trying to do (replace a certificate but keep the private key) possible in android? If so, how might I do it? It seems like this isn't really working
谢谢
猎户座
推荐答案
事实证明,我做错了事.您不需要替换或修改KeyStore中的证书,只需在初始化 HttpsURLConnection 使用的
SSLContext
时使用自定义的 KeyManager
code>和KeyManager可以选择您想要的任何证书或私钥.
As it turns out, I was doing the wrong thing. You don't need to replace or modify the certificates in the KeyStore, you just need to use a custom KeyManager
when you initialize the SSLContext
used by HttpsURLConnection
and the KeyManager can pick whichever certificates or private keys you'd like.
这大大简化了KeyStore管理.现在是我的情况
This simplifies the KeyStore management greatly. My scenario is now
- 使用
KeyPairGenerator
(别名为X
)生成公用/专用密钥对. - 将公共密钥发送到服务器,该服务器从该公共密钥生成新的签名证书,然后将其发送回
- 使用别名为
X-Signed
的
setCertificateEntry
将此签名证书放入KeyStore中.- Generate a public/private key pair using
KeyPairGenerator
with an alias ofX
- Send the public key to the server which generates a new signed certificate from that public key, and and sends it back
- Put this signed certificate in the KeyStore using
setCertificateEntry
with an alias ofX-Signed
当我建立 HttpsURLConnection
时,它是这样的:
When I establish the HttpsURLConnection
, it goes like this:
KeyStore androidKeyStore = KeyStore.getInstance(LocalKeyStore.ANDROID_KEYSTORE);
androidKeyStore.load(null);
X509Certificate signedClientCertificate = (X509Certificate)androidKeyStore.getCertificate("X-Signed");
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)androidKeyStore.getEntry("X", null);
X509ExtendedKeyManager keyManager = new X509ExtendedKeyManager() {
@Override
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
return clientCertificateAlias;
}
@Override
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
return null; // different if you're validating the server's cert
}
@Override
public X509Certificate[] getCertificateChain(String alias) {
return new X509Certificate[] { signedClientCertificate };
}
@Override
public String[] getClientAliases(String keyType, Principal[] issuers) {
return new String[]{ "X" };
}
@Override
public String[] getServerAliases(String keyType, Principal[] issuers) {
return null; // different if you're validating server's cert
}
@Override
public PrivateKey getPrivateKey(String alias) {
if(alias != clientCertificateAlias) {
Log.e(TAG, String.format("X509ExtendedKeyManager is asking for privateKey with unknown alias %s. Expecting it to ask for %s", alias, clientCertificateAlias));
return null;
}
return privateKeyEntry.getPrivateKey();
}
};
X509TrustManager trustServerCertificates = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// do nothing, this method doesn't get called
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
// code to validate server's cert in here
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null; // any issuer
}
};
m_sslContext = SSLContext.getInstance("TLS");
m_sslContext.init(new KeyManager[]{ keyManager }, new TrustManager[] { trustServerCertificates }, null);
// later on
conn = (HttpURLConnection)url.openConnection();
SSLContext sslContext = m_sslContext;
if(conn instanceof HttpsURLConnection && sslContext != null) {
((HttpsURLConnection)conn).setSSLSocketFactory(sslContext.getSocketFactory());
}
这对我来说很好,并且我可以继续使用 AndroidKeyStore
及其按应用程序隐私和硬件支持的存储方式
This is working well for me, and I can keep using AndroidKeyStore
with it's per-app privacy and hardware-backed storage
这篇关于Android Java更新Android KeyStore中的证书和私钥的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!