Android Java更新证书和Android KeyStore中的私钥

Ori*_*rds 4 java android keystore

我有一个使用HTTPS客户端证书进行身份验证的系统,但根据以下过程生成证书本身:

  1. 客户端设备生成证书(包括公钥和私钥)
  2. 客户端设备将公钥发送到签署公钥的服务器,并将其作为签名证书返回
  3. 客户端以安全的方式存储证书,然后将其用作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);
Run Code Online (Sandbox Code Playgroud)

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);
Run Code Online (Sandbox Code Playgroud)

此时,最终的dumpKeyStore指示存在具有正确别名的条目,但是当它尝试调用时会抛出"NoSuchAlgorithmException:Unknown key entry"异常 keyStore.getEntry

我想在android中做什么(替换证书但保留私钥)?如果是这样,我该怎么做?看起来这不是真的有效

谢谢

猎户座

Ori*_*rds 5

事实证明,我做错了.您不需要替换或修改KeyStore中的证书,只需KeyManager在初始化SSLContextused by 时使用自定义HttpsURLConnection,KeyManager就可以选择您想要的任何证书或私钥.

这极大地简化了KeyStore管理.我的情景现在

  1. 使用KeyPairGenerator别名生成公钥/私钥对X
  2. 将公钥发送到服务器,该服务器从该公钥生成新的签名证书,然后将其发回
  3. 将此签名证书放在KeyStore中,使用setCertificateEntry别名为X-Signed

当我建立HttpsURLConnection它时,它是这样的:

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());
}
Run Code Online (Sandbox Code Playgroud)

这对我很有用,我可以继续使用AndroidKeyStore它的每个应用程序隐私和硬件支持的存储


pkz*_*zip 2

我对本机 AndroidKeyStore 也有类似的问题。创建证书后我无法更新证书generator.generateKeyPair()

Android 密钥存储当前的 OpenSSL 实现似乎尚未完全完成。我寻找工作示例一段时间,但找不到任何可以让我更新属于的现有证书的内容,KeyStore.PrivateKeyEntry因此我在这里为此问题提交了错误: https: //code.google.com/p/android/issues/detail ?id=194955&q=keystore&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars

同时,如果您不介意使用第三方加密提供商,我建议使用“BKS”Bouncy Castle 密钥存储。

更新: 自定义 KeyManager 可能是在双向 SSL 情况下使用的更好的解决方法,您可以强制对私钥使用一个密钥库别名,对证书链使用另一个密钥库别名。然而Android 文档指出你应该能够替换它:

生成新的 PrivateKey 还要求您指定自签名证书将具有的初始 X.509 属性。您可以稍后用证书颁发机构签名的证书替换该证书。