【发布时间】:2016-02-19 15:49:52
【问题描述】:
我有一个使用HTTPS客户端证书进行认证的系统,但是证书本身是按照如下流程生成的:
- 客户端设备生成证书(包括公钥和私钥)
- 客户端设备将公钥发送到服务器,服务器对公钥进行签名,并将其作为签名证书返回
- 客户端以安全方式存储证书,然后将其用作 HTTPS 客户端证书
我们有这个系统在 iOS 上运行,我正在尝试移植到 android,但遇到了很多关于 Android 记录不充分且令人困惑的安全 API 的问题。
我的代码大致是这样的:
生成证书
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 中检索到。
发送到服务器
publicKeyBytes 被发送到服务器,服务器将其作为新的签名 x509 证书的公钥,并在响应中发回。我没有输入代码,这只是基本的网络。据我所知,返回的证书已加载并且看起来不错。
保存和关联证书
我正在尝试将其放入具有相同别名的 keyStore 中,因此它(理论上)可以与之前的正确私钥相关联。到目前为止,我的代码是这样的:
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 时会抛出“NoSuchAlgorithmException: Unknown key entry”异常
我正在尝试做的事情(替换证书但保留私钥)在 android 中是否可行?如果是这样,我该怎么做?好像真的不行
谢谢
猎户座
【问题讨论】: