关于android:使用自签名证书接受HTTPS连接

accepting HTTPS connections with self-signed certificates

我正在尝试使用HttpClient lib建立HTTPS连接,但是问题是,由于证书不是由公认的证书颁发机构(CA)(如Verisign,GlobalSIgn等)签名的,因此,该证书颁发机构在Android设备上列出了该证书 受信任的证书,我一直得到javax.net.ssl.SSLException: Not trusted server certificate

我已经看到了一些解决方案,在这些解决方案中,您仅接受所有证书,但是如果我想问用户该怎么办?

我想要一个类似于浏览器的对话框,让用户决定是否继续。 最好是我想使用与浏览器相同的证书存储。 有任何想法吗?


您需要做的第一件事是设置验证级别。
这样的水平不是很多:

  • ALLOW_ALL_HOSTNAME_VERIFIER
  • BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
  • STRICT_HOSTNAME_VERIFIER

尽管setHostnameVerifier()方法对于新库apache已过时,但对于Android SDK中的版本而言却是正常的。
因此,我们采用ALLOW_ALL_HOSTNAME_VERIFIER并将其设置在方法工厂SSLSocketFactory.setHostnameVerifier()中。

接下来,您需要将协议的工厂设置为https。为此,只需调用SchemeRegistry.register()方法。

然后,您需要使用SingleClientConnManager创建一个DefaultHttpClient
同样在下面的代码中,您可以看到默认情况下还将通过方法HttpsURLConnection.setDefaultHostnameVerifier()使用我们的标志(ALLOW_ALL_HOSTNAME_VERIFIER)

下面的代码对我有用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;

DefaultHttpClient client = new DefaultHttpClient();

SchemeRegistry registry = new SchemeRegistry();
SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);
registry.register(new Scheme("https", socketFactory, 443));
SingleClientConnManager mgr = new SingleClientConnManager(client.getParams(), registry);
DefaultHttpClient httpClient = new DefaultHttpClient(mgr, client.getParams());

// Set verifier    
HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);

// Example send http request
final String url ="https://encrypted.google.com/";
HttpPost httpPost = new HttpPost(url);
HttpResponse response = httpClient.execute(httpPost);


需要以下主要步骤来实现来自证书颁发机构的安全连接,而该证书颁发机构不会被android平台信任。

根据许多用户的要求,我已经在我的博客文章中反映了最重要的部分:

  • 获取所有必需的证书(根证书和任何中间CA)
  • 使用keytool和BouncyCastle提供程序创建密钥库,并导入证书
  • 将密钥库加载到您的android应用中,并将其用于安全连接(我建议使用Apache HttpClient而不是标准的java.net.ssl.HttpsURLConnection(易于理解,性能更高)
  • 抓住证书

    您必须获取从端点证书一直到根CA一直构建链的所有证书。这意味着,任何(如果存在)中间CA证书以及根CA证书。您无需获取端点证书。

    创建密钥库

    下载BouncyCastle Provider并将其存储到已知位置。
    还要确保您可以调用keytool命令(通常位于JRE安装的bin文件夹下)。

    现在,将获得的证书(不导入端点证书)导入到BouncyCastle格式的密钥库中。

    我没有测试过,但是我认为导入证书的顺序很重要。这意味着,首先导入最低的中间CA证书,然后一直导入根CA证书。

    使用以下命令,将创建带有密码mysecret的新密钥库(如果尚不存在),并将导入中间CA证书。我还定义了BouncyCastle提供程序,可以在我的文件系统和密钥库格式中找到它。对链中的每个证书执行此命令。

    1
    keytool -importcert -v -trustcacerts -file"path_to_cert/interm_ca.cer" -alias IntermediateCA -keystore"res/raw/myKeystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath"path_to_bouncycastle/bcprov-jdk16-145.jar" -storetype BKS -storepass mysecret

    验证证书是否正确导入到密钥库中:

    1
    keytool -list -keystore"res/raw/myKeystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath"path_to_bouncycastle/bcprov-jdk16-145.jar" -storetype BKS -storepass mysecret

    应该输出整个链:

    1
    2
    RootCA, 22.10.2010, trustedCertEntry, Thumbprint (MD5): 24:77:D9:A8:91:D1:3B:FA:88:2D:C2:FF:F8:CD:33:93
    IntermediateCA, 22.10.2010, trustedCertEntry, Thumbprint (MD5): 98:0F:C3:F8:39:F7:D8:05:07:02:0D:E3:14:5B:29:43

    现在,您可以在res/raw/下的android应用中将密钥库复制为原始资源。

    在您的应用中使用密钥库

    首先,我们必须创建一个自定义Apache HttpClient,它将我们的密钥库用于HTTPS连接:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    public class MyHttpClient extends DefaultHttpClient {

      final Context context;

      public MyHttpClient(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));
          return new SingleClientConnManager(getParams(), registry);
      }

      private SSLSocketFactory newSslSocketFactory() {
          try {
              // Get an instance of the Bouncy Castle KeyStore format
              KeyStore trusted = KeyStore.getInstance("BKS");
              // Get the raw resource, which contains the keystore with
              // your trusted certificates (root and any intermediate certs)
              InputStream in = context.getResources().openRawResource(R.raw.mykeystore);
              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
              // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
              sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
              return sf;
          } catch (Exception e) {
              throw new AssertionError(e);
          }
      }
    }

    我们已经创建了自定义HttpClient,现在可以将其用于安全连接。例如,当我们对REST资源进行GET调用时。

    1
    2
    3
    4
    5
    6
    // Instantiate the custom HttpClient
    DefaultHttpClient client = new MyHttpClient(getApplicationContext());
    HttpGet get = new HttpGet("https://www.mydomain.ch/rest/contacts/23");
    // Execute the GET call and obtain the response
    HttpResponse getResponse = client.execute(get);
    HttpEntity responseEntity = getResponse.getEntity();

    而已 ;)


    如果您的设备上没有服务器上的自定义/自签名证书,则可以使用以下类加载该证书并在Android的客户端上使用它:

    将证书*.crt文件放在/res/raw中,以便可以从R.raw.*使用

    使用下面的类来获取HTTPClientHttpsURLConnection,它们将具有使用该证书的套接字工厂:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    package com.example.customssl;

    import android.content.Context;
    import org.apache.http.client.HttpClient;
    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.AllowAllHostnameVerifier;
    import org.apache.http.conn.ssl.SSLSocketFactory;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
    import org.apache.http.params.BasicHttpParams;
    import org.apache.http.params.HttpParams;

    import javax.net.ssl.HttpsURLConnection;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.TrustManagerFactory;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.URL;
    import java.security.KeyStore;
    import java.security.KeyStoreException;
    import java.security.NoSuchAlgorithmException;
    import java.security.cert.Certificate;
    import java.security.cert.CertificateException;
    import java.security.cert.CertificateFactory;

    public class CustomCAHttpsProvider {

        /**
         * Creates a {@link org.apache.http.client.HttpClient} which is configured to work with a custom authority
         * certificate.
         *
         * @param context       Application Context
         * @param certRawResId  R.raw.id of certificate file (*.crt). Should be stored in /res/raw.
         * @param allowAllHosts If true then client will not check server against host names of certificate.
         * @return Http Client.
         * @throws Exception If there is an error initializing the client.
         */
        public static HttpClient getHttpClient(Context context, int certRawResId, boolean allowAllHosts) throws Exception {


            // build key store with ca certificate
            KeyStore keyStore = buildKeyStore(context, certRawResId);

            // init ssl socket factory with key store
            SSLSocketFactory sslSocketFactory = new SSLSocketFactory(keyStore);

            // skip hostname security check if specified
            if (allowAllHosts) {
                sslSocketFactory.setHostnameVerifier(new AllowAllHostnameVerifier());
            }

            // basic http params for client
            HttpParams params = new BasicHttpParams();

            // normal scheme registry with our ssl socket factory for"https"
            SchemeRegistry schemeRegistry = new SchemeRegistry();
            schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
            schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));

            // create connection manager
            ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);

            // create http client
            return new DefaultHttpClient(cm, params);
        }

        /**
         * Creates a {@link javax.net.ssl.HttpsURLConnection} which is configured to work with a custom authority
         * certificate.
         *
         * @param urlString     remote url string.
         * @param context       Application Context
         * @param certRawResId  R.raw.id of certificate file (*.crt). Should be stored in /res/raw.
         * @param allowAllHosts If true then client will not check server against host names of certificate.
         * @return Http url connection.
         * @throws Exception If there is an error initializing the connection.
         */
        public static HttpsURLConnection getHttpsUrlConnection(String urlString, Context context, int certRawResId,
                                                               boolean allowAllHosts) throws Exception {

            // build key store with ca certificate
            KeyStore keyStore = buildKeyStore(context, certRawResId);

            // Create a TrustManager that trusts the CAs in our KeyStore
            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keyStore);

            // Create an SSLContext that uses our TrustManager
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, tmf.getTrustManagers(), null);

            // Create a connection from url
            URL url = new URL(urlString);
            HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
            urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());

            // skip hostname security check if specified
            if (allowAllHosts) {
                urlConnection.setHostnameVerifier(new AllowAllHostnameVerifier());
            }

            return urlConnection;
        }

        private static KeyStore buildKeyStore(Context context, int certRawResId) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
            // init a default key store
            String keyStoreType = KeyStore.getDefaultType();
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(null, null);

            // read and add certificate authority
            Certificate cert = readCert(context, certRawResId);
            keyStore.setCertificateEntry("ca", cert);

            return keyStore;
        }

        private static Certificate readCert(Context context, int certResourceId) throws CertificateException, IOException {

            // read certificate resource
            InputStream caInput = context.getResources().openRawResource(certResourceId);

            Certificate ca;
            try {
                // generate a certificate
                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                ca = cf.generateCertificate(caInput);
            } finally {
                caInput.close();
            }

            return ca;
        }

    }

    关键点:

  • Certificate对象是从.crt文件生成的。
  • 将创建默认的KeyStore
  • keyStore.setCertificateEntry("ca", cert)正在将证书添加到别名为" ca"的密钥存储中。您修改代码以添加更多证书(中间CA等)。
  • 主要目标是生成一个SSLSocketFactory,然后可由HTTPClientHttpsURLConnection使用。
  • 可以进一步配置SSLSocketFactory,例如跳过主机名验证等。
  • 有关更多信息,请访问:http://developer.android.com/training/articles/security-ssl.html


    由于不推荐使用HTTPClient,因此Google建议将Android Volley用于HTTP / HTTPS连接。因此,您知道正确的选择:)。

    还有,永不NUKE SSL证书(永不!!!)。

    核对SSL证书完全违反SSL的目的,SSL的目的在于提高安全性。如果您打算炸毁所有随附的SSL证书,则没有使用SSL的感觉。更好的解决方案是不使用SSL,或者更好的解决方案是在应用程序上创建自定义TrustManager,并使用Android Volley进行HTTP / HTTPS连接。

    这是我使用基本的LoginApp创建的要点,它使用服务器端使用的自签名证书执行HTTPS连接,并接受该应用程序。

    这也是另一个要点,它可能会帮助您创建用于在服务器上设置的自签名SSL证书,并在您的App上使用该证书。非常重要:您必须将上述脚本生成的.crt文件复制到Android项目中的"原始"目录中。


    尝试使用https将我的Android应用程序连接到RESTful服务时,我感到沮丧。另外,对于建议完全禁用证书检查的所有答案,我也有些恼火。如果这样做,https的意义是什么?

    在搜索了一段时间后,我终于找到了不需要外部jar的解决方案,只需使用Android API。感谢Andrew Smith,他于2014年7月发布了它

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
     /**
     * Set up a connection to myservice.domain using HTTPS. An entire function
     * is needed to do this because myservice.domain has a self-signed certificate.
     *
     * The caller of the function would do something like:
     * HttpsURLConnection urlConnection = setUpHttpsConnection("https://littlesvr.ca");
     * InputStream in = urlConnection.getInputStream();
     * And read from that"in" as usual in Java
     *
     * Based on code from:
     * https://developer.android.com/training/articles/security-ssl.html#SelfSigned
     */
    public static HttpsURLConnection setUpHttpsConnection(String urlString)
    {
        try
        {
            // Load CAs from an InputStream
            // (could be from a resource or ByteArrayInputStream or ...)
            CertificateFactory cf = CertificateFactory.getInstance("X.509");

            // My CRT file that I put in the assets folder
            // I got this file by following these steps:
            // * Go to https://littlesvr.ca using Firefox
            // * Click the padlock/More/Security/View Certificate/Details/Export
            // * Saved the file as littlesvr.crt (type X.509 Certificate (PEM))
            // The MainActivity.context is declared as:
            // public static Context context;
            // And initialized in MainActivity.onCreate() as:
            // MainActivity.context = getApplicationContext();
            InputStream caInput = new BufferedInputStream(MainActivity.context.getAssets().open("littlesvr.crt"));
            Certificate ca = cf.generateCertificate(caInput);
            System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());

            // Create a KeyStore containing our trusted CAs
            String keyStoreType = KeyStore.getDefaultType();
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(null, null);
            keyStore.setCertificateEntry("ca", ca);

            // Create a TrustManager that trusts the CAs in our KeyStore
            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keyStore);

            // Create an SSLContext that uses our TrustManager
            SSLContext context = SSLContext.getInstance("TLS");
            context.init(null, tmf.getTrustManagers(), null);

            // Tell the URLConnection to use a SocketFactory from our SSLContext
            URL url = new URL(urlString);
            HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection();
            urlConnection.setSSLSocketFactory(context.getSocketFactory());

            return urlConnection;
        }
        catch (Exception ex)
        {
            Log.e(TAG,"Failed to establish SSL connection to server:" + ex.toString());
            return null;
        }
    }

    对于我的样机应用程序来说效果很好。


    最佳答案对我没有用。经过一番调查,我在" Android Developer"上找到了所需的信息:
    https://developer.android.com/training/articles/security-ssl.html#SelfSigned

    创建X509TrustManager的空实现可以解决问题:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    private static class MyTrustManager implements X509TrustManager
    {

        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType)
             throws CertificateException
        {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType)
            throws CertificateException
        {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers()
        {
            return null;
        }

    }

    ...

    HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
    try
    {
        // Create an SSLContext that uses our TrustManager
        SSLContext context = SSLContext.getInstance("TLS");
        TrustManager[] tmlist = {new MyTrustManager()};
        context.init(null, tmlist, null);
        conn.setSSLSocketFactory(context.getSocketFactory());
    }
    catch (NoSuchAlgorithmException e)
    {
        throw new IOException(e);
    } catch (KeyManagementException e)
    {
        throw new IOException(e);
    }
    conn.setRequestMethod("GET");
    int rcode = conn.getResponseCode();

    请注意,TustManager的这种空实现只是一个示例,在生产环境中使用它会造成严重的安全威胁!


    您可以通过以下方法将其他证书添加到KeyStore中,以避免出现此问题:通过HTTPS使用HttpClient信任所有证书

    它不会像您要求的那样提示用户,但是这将减少用户遇到"不受信任的服务器证书"错误的可能性。


    创建SSL证书的最简单方法

    打开Firefox(我想Chrome也可以,但使用FF对我来说更容易)

    使用自签名SSL证书访问您的开发站点。

    单击证书(站点名称旁边)

    点击"更多信息"

    点击"查看证书"

    点击"详细信息"

    点击"导出..."

    选择" X.509证书蒙山链(PEM)",选择要保存的文件夹和名称,然后单击"保存"

    转到命令行,转到下载了pem文件的目录,然后执行" openssl x509 -inform PEM -outform DM -in .pem -out .crt"

    将.crt文件复制到Android设备内/ sdcard文件夹的根目录
    在您的Android设备中,依次选择设置>安全>从存储安装。

    它应该检测证书并将其添加到设备中
    浏览到您的开发站点。

    第一次应要求您确认安全例外。就这样。

    该证书应可与您Android上安装的任何浏览器(浏览器,Chrome,Opera,海豚...)一起使用。

    请记住,如果您要从其他域(我们都是页面速度母狗)提供静态文件,则还需要添加该域的证书。


    这些修补程序都不适用于我针对SDK 16,Release 4.1.2的开发平台,因此我找到了一种解决方法。

    我的应用程序使用" http://www.example.com/page.php?data=somedata"将数据存储在服务器上

    最近,page.php移至" https://www.secure-example.com/page.php",并且我不断收到" javax.net.ssl.SSLException:不可信服务器证书"。

    从本指南开始,我不再只接受一个页面上的所有证书,而是解决了自己编写在" http://www.example.com/page.php"上发布的page.php的问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <?php

    caronte ("https://www.secure-example.com/page.php");

    function caronte($url) {
        // build curl request
        $ch = curl_init();
        foreach ($_POST as $a => $b) {
            $post[htmlentities($a)]=htmlentities($b);
        }
        curl_setopt($ch, CURLOPT_URL,$url);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS,http_build_query($post));

        // receive server response ...
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $server_output = curl_exec ($ch);
        curl_close ($ch);

        echo $server_output;
    }

    ?>

    我编写了小型库ssl-utils-android来信任Android上的特定证书。

    您只需提供资产目录中的文件名即可加载任何证书。

    用法:

    1
    2
    3
    OkHttpClient client = new OkHttpClient();
    SSLContext sslContext = SslUtils.getSslContextForCertificateFile(context,"BPClass2RootCA-sha2.cer");
    client.setSslSocketFactory(sslContext.getSocketFactory());

    这是由于ndroid 2.x中缺少SNI(服务器名称标识)支持而导致的。我在这个问题上奋斗了一个星期,直到遇到以下问题,这不仅为问题提供了良好的背景,而且还提供了一个有效且有效的解决方案,没有任何安全漏洞。

    Android 2.3中没有对等证书错误,但4中没有


    也许这会有所帮助...它适用于使用自签名证书的Java客户端(不检查证书)。请小心,仅将其用于开发案例,因为这根本不安全!!

    如何忽略Apache HttpClient 4.0中的SSL证书错误

    希望它可以在Android上运行,只需添加HttpClient库...祝您好运!