Android密钥库中的Android java更新证书和私钥

Android密钥库中的Android java更新证书和私钥,java,android,keystore,Java,Android,Keystore,我有一个使用HTTPS客户端证书进行身份验证的系统,但证书本身是根据以下过程生成的: 客户端设备生成证书(包括公钥和私钥) 客户端设备将公钥发送到服务器,服务器对公钥进行签名,并将其作为签名证书返回 客户端以安全的方式存储证书,然后稍后将其用作HTTPS客户端证书 我们的系统是在iOS上运行的,我正试图移植到android上,但是android的安全API文档不完整且令人困惑,因此遇到了很多问题 我的代码大致如下: KeyStore androidKeyStore = KeyStore.getI

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

  • 客户端设备生成证书(包括公钥和私钥)
  • 客户端设备将公钥发送到服务器,服务器对公钥进行签名,并将其作为签名证书返回
  • 客户端以安全的方式存储证书,然后稍后将其用作HTTPS客户端证书
  • 我们的系统是在iOS上运行的,我正试图移植到android上,但是android的安全API文档不完整且令人困惑,因此遇到了很多问题

    我的代码大致如下:

    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());
    }
    
    生成证书

    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,调用
    keystore.getEntry
    以获取每个条目,并只记录内容。 此时,它报告有一个具有给定别名的条目,其类型为
    KeyStore.PrivateKeyEntry
    。它有一个关联的证书和公钥,可以从
    PrivateKeyEntry
    检索

    发送到服务器

    publicKeyBytes
    被发送到服务器,服务器将其作为新的签名x509证书的公钥,并在响应中发送回。我没有输入代码,这只是基本的网络。返回的证书已加载,从我所能看到的情况来看,它看起来很好

    保存和关联证书

    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);
    
    我试图将它放入具有相同别名的密钥库中,这样(理论上)它就可以与之前的正确私钥相关联。到目前为止,我的代码如下:

    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中做的(替换证书但保留私钥)可能吗?如果是,我该怎么做?这似乎不太管用

    谢谢


    Orion

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

    目前Android密钥库的OpenSSL实现似乎还没有完全完成。我查找了一段时间的工作示例,但找不到任何可以让我更新属于
    密钥库的现有证书。PrivateKeyEntry
    ,因此我已在此处提交了此问题的错误:

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

    更新: 自定义密钥管理器可能是在相互SSL情况下使用的更好的解决方法,在这种情况下,您可以强制对私钥使用一个密钥库别名,对证书链使用另一个密钥库别名。但是,您应该能够替换它的状态:

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


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

    这大大简化了密钥库管理。我的设想是现在

  • 使用别名为
    X的
    KeyPairGenerator
    生成公钥/私钥对
  • 将公钥发送到服务器,该服务器根据该公钥生成新的签名证书,然后将其发送回
  • 使用别名为
    X-signed
    setCertificateEntry
    将此签名证书放入密钥库
  • 当我建立
    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());
    }
    

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

    此评论不完全准确,可以忽略

    第二篇文章中提供的解决方案似乎没有像预期的那样安全地保护密钥,因为
    null
    作为第三个参数(protoParam)传递给
    密钥库。setEntry

        KeyStore.PrivateKeyEntry newEntry = new KeyStore.PrivateKeyEntry(existingPrivateKeyEntry.getPrivateKey(), new Certificate[]{ cert });
        keyStore.setEntry(alias, newEntry, null);
    
    因此,密钥的原材料可以访问,如代码本身所示:

        KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)androidKeyStore.getEntry("X", null);
    
    我认为正确的解决方案必须利用
    安全地存储密钥并仅对其执行加密操作。如果有人提供在TLS相互身份验证中使用AndroidKeyStore保护的客户端证书的示例代码,我将不胜感激。

    根据我上面的回答,您使用
    keyStore.setEntry(别名,newEntry,null)引用的代码最终没有工作,而是在密钥库中创建密钥,永远不要复制/移动/设置它,并使用自定义密钥管理器选择正确的客户端证书。据我所知,这个答案确实使用了受AndroidKeyStore保护的客户端证书,而且它确实使用TLS相互认证。谢谢澄清。我还使用您的代码实现了相互身份验证,但在您的示例中,密钥从硬件支持的存储泄漏到了android framework/java应用程序。也可以使用每个应用程序专用的Android内部存储,而不是密钥存储,因为该示例不利于AndroidKeyStore提供的安全保护。密钥库的一个主要用途是在不访问原始私钥材料的情况下执行加密操作(签名、解密)。我会吗