【问题标题】:Using a self-signed certificate to create a secure client-server connection in android使用自签名证书在 android 中创建安全的客户端-服务器连接
【发布时间】:2013-08-23 03:57:58
【问题描述】:

我正在开发一个企业 android 应用程序,因此在我的测试阶段有必要在客户端(android 模拟器/测试手机)和服务器之间创建一个安全连接,即使服务器的证书是自签名的,而合法的证书也是如此正在被公司收购(目前我无法控制)。

我需要信任服务器的自签名证书及其证书颁发机构,当然,android 操作系统本身不信任它们。我几乎一字不差地关注google's suggestion for creating an HTTPS environment in this scenario。

我目前面临的问题是我无法访问我的 .crt 文件,如 google 示例中的这一行:

InputStream caInput = new BufferedInputStream(
    new FileInputStream("load-der.crt"));

代替上述,我正在使用:

InputStream caInput = new BufferedInputStream(
getResources().openRawResource(R.raw.mycrtfile));

打开派生自mycrtfile.crt 的InputStream,其中.crt 文件存在于/res/raw/mycrtfile.crt 中。但是,我在那条线上得到了NullPointerException。

与存储在res 目录中的原始资源相比,有没有更好的方法来存储和访问我需要加载为InputStream 或FileInputStream 的证书文件?

【问题讨论】:

  • 我遇到了同样的问题,请检查我的答案,如果您对步骤感到困惑,我可以为您提供帮助。这将只接受您的证书。
  • 谢谢@Moises,我今天已经完成了那个项目,所以明天我会查看解决方案,如果我遇到困难,请告诉你。

标签: android security ssl


【解决方案1】:

有多种方法可以解决您的问题,但这是我使用的一种: 所有步骤都在这个链接http://blog.antoine.li/2010/10/22/android-trusting-ssl-certificates/,但有些部分可能会混淆,所以我将解释所有过程:

1.-将您的 mycrtfile.crt 存储在已知路径中,我会说 c:BKS/mycrtfile.crt。

2.-要创建您的 BKS 或密钥库,您需要文件 bcprov-jdk15on-146.jar,这个类将为我们完成所有工作,有不同的版本,但这个也适用于我 http://www.bouncycastle.org/download/bcprov-jdk15on-146.jar将此文件存储到 C:BKS/

3.-现在您将使用 Keytool(keytool 随 Java SDK 一起提供。您应该在包含 javac 的目录中找到它)来生成我们的密钥库并确保它正在工作,请转到您的 cmd 并键入“Keytool ",您将看到可用的命令,这意味着正在运行,或者您可以通过 "C:\Program Files (x86)\Java\jre7\bin>keytool" 访问。

4.-现在一切就绪,我们可以使用以下命令行生成密钥库:

keytool -importcert -v -trustcacerts -file "c:\BKS/mycrtfile.crt" -alias certificate -keystore "c:\BKS/keystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "c:\BKS/prov-jdk15on-146.jar" -storetype BKS -storepass mysecret

让我们看看这一行是什么(这部分我真的很困惑): -"c:\BKS/mycrtfile.crt" :这是您的证书的路径。 -"c:\BKS/keystore.bks" 这是我们将存储密钥库的路径,您可以更改我使用密钥库的输出名称,只需确保扩展文件为 .bks -“c:\BKS/prov-jdk15on-146.jar”:这是我们完成所有工作的文件的路径。 -mysecret:这是使用密钥库的密码,您将需要此密码,所以不要忘记这一点。

已编辑:
4.1- 还可以使用此命令行来验证证书是否已正确导入密钥库:

keytool -list -keystore "res/raw/Keystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "c:\BKS/prov-jdk15on-146.jar" -storetype BKS -storepass mysecret

4.2- 之后您应该会看到如下输出:

RootCA,22.10.2010,trustedCertEntry,指纹 (MD5):24:77:D9:A8:91:D1:3B:FA:88:2D:C2:FF:F8:CD:33:93 中级CA,22.10.2010,trustedCertEntry,指纹 (MD5):98:0F:C3:F8:39:F7:D8:05:07:02:0D:E3:14:5B:29:43
这意味着它已正确导入。

5.- 之后,如果您转到您的 BKS 文件夹,您将看到一个 Keystore.bks 文件,这意味着我们正在路上。

6.- 现在让我们进入 ANDROID 部分。在您的项目中检查您是否有“原始”文件夹,如果没有在 res 下创建此文件夹,则它必须位于 Yourproject/res/raw 中。

7.-将您的 Keystore.bks 文件复制到原始文件夹中。现在一切就绪,让我们进入代码。

8.--- 现在我们将创建一个类来读取和信任我们的 Keystore:

import java.io.InputStream;
import java.security.KeyStore;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.SingleClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;

import com.futureconcepts.anonymous.R;
import android.content.Context;


public class Client extends  DefaultHttpClient   {
final Context context;
  public Client(Context context) {
      this.context = context;
  }

  @Override
  protected ClientConnectionManager createClientConnectionManager() {
      SchemeRegistry registry = new SchemeRegistry();
      registry.register(new Scheme("http", 
      PlainSocketFactory.getSocketFactory(), 80));
      // Register for port 443 our SSLSocketFactory with our keystore
      // to the ConnectionManager
      registry.register(new Scheme("https", newSslSocketFactory(),443));

    HttpParams httpParams = new BasicHttpParams();
     HttpConnectionParams.setConnectionTimeout(httpParams,9000);
     HttpConnectionParams.setSoTimeout(httpParams, 9000);

      return new SingleClientConnManager(httpParams, registry);
  }


  private SSLSocketFactory newSslSocketFactory() {
      try {

          // Get an instance of the Bouncy Castle KeyStore format
            KeyStore trusted = KeyStore.getInstance("BKS");//put BKS literal  
            // Get the raw resource, which contains the keystore with
            // your trusted certificates (root and any intermediate certs)
            InputStream in =context.getResources().openRawResource(R.raw.keystore);
            try {
                // Initialize the keystore with the provided trusted certificates
                // Also provide the password of the keystore
                trusted.load(in, "mysecret".toCharArray());
            } finally {
                in.close();
            }
          // Pass the keystore to the SSLSocketFactory. The factory is responsible
          // for the verification of the server certificate.
          SSLSocketFactory sf = new SSLSocketFactory(trusted);
          // Hostname verification from certificate

           sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
          return sf;
      } catch (Exception e) {
          throw new AssertionError(e);
      }
  }

}

9.我们现在完成了请求,只需执行以下操作: HttpClient客户端=新客户端(这个); ///设置你的Httpclient。

就是这样,您将只信任您的证书。我希望这个解释能帮助你或任何有同样问题的人。

【讨论】:

  • 起初我在上面的链接中遇到了一些问题。我能够通过查看this 在我的应用程序中配置 HTTPS 的指南获得快速解决方案。然而,我担心的是,这只是禁用了对 SSL 的所有检查,并且一些恶意第三方可能会注入自己的第三方证书。
  • 我认为禁用自签名证书的验证不是一个好主意,你能做的最好的就是只信任你的证书,如果这样做的目的是在你创建后有一些安全性一个请求,否则您可以接受所有证书(我认为这是一个非常糟糕的主意,但可以做到)。我给你的链接将指导你为你的证书创建一个密钥库,你只会信任那个证书,我会解释所有的过程,给我一些时间。这看起来很复杂,但实际上并不复杂,而且是实现这一目标的正确方法。
  • 非常感谢您提供更广泛的解释。我正在进一步研究。
  • 如果我在 SSL 服务器上使用与默认端口 (443) 不同的端口,例如 6565,我是否应该在此代码中将端口更改为 6565?
  • 我尝试在上面的代码中使用我自己的自定义端口(例如6565)和443,但两者都不起作用。我按照说明进行操作,但它仍然给出 Not trusted server certificate 错误...奇怪。
【解决方案2】:

Keystore(这里有一个很棒的 article 关于 Keystore 和相关问题)和 KeyChain 可以解决问题,但您可能需要检查您所针对的 API 级别。此外,由于您提到了企业应用程序,您可能需要考虑证书固定。 This 帮助您进行证书固定。

【讨论】:

    【解决方案3】:

    1) 在 app/src/main/ 中创建目录“assets”

    2) 将您的证书放在此目录中。

    3) 现在你可以得到 InputStream 了:

    InputStream is = this.getAssets().open("mycrtfile.crt");
    

    要使用 this.getAssets() 你必须在 Activity 中,因为在 Activity 中“this”对应于“Context”。 如果您不在 Activity 中,则必须将 Context (this) 作为参数传递。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-04
      • 1970-01-01
      • 2018-08-31
      • 1970-01-01
      • 1970-01-01
      • 2011-02-23
      相关资源
      最近更新 更多