秒懂 Https 之如何在 Android 中使用自签名证书

code小生 一个专注大前端领域的技术平台公众号回复Android加入安卓技术群

作者:ShuSheng007
链接:blog.csdn.net/ShuSheng0007/article/details/107838108
声明:本文已获ShuSheng007授权发表,转发等请联系原作者授权

概述

在秒懂Https之CA证书与自签名证书漫谈中我们谈到了如何生成自签名证书的问题。这篇文章我们看一下,如何在Android中使用生成的自签名证书。

前提

本文基于网络请求库Retrofit2(OkHttp)

使用方式

未来方案

为什么说是未来方案呢?因为只有Android7.0及以上才可以使用,而时至今日一款成熟的App至少要支持到Android5.0,甚至Android4.4。但我们还是要简单的介绍一下,毕竟再过几年人家就是主角了。

从Android7.0(API Level 24)开始Android引入一套网络安全配置的机制,使用这种机制可以非常容易使用自签名证书。使用步骤如下:

  1. 将自签名证书放到App的src/main/res/raw文件下,例如我的证书叫sng_certificate.。

  2. 在App的src/main/res下建立一个xml文件夹,在里面创建一个叫network_security_config.xml的文件,名称可以任意叫,其内容如下

  <?xml version="1.0" encoding="utf-8"?>
  <network-security-config>
      <domain-config>
          <domain includeSubdomains="true">你服务器的域名</domain>
          <trust-anchors>
              <certificates src="@raw/sng_certificate"/>
          </trust-anchors>
      </domain-config>
  </network-security-config>
  1. 将此文件配置到AndroidManifest.xml文件中

 <?xml version="1.0" encoding="utf-8"?>
    <manifest ... >
        <application 
            android:networkSecurityConfig="@xml/network_security_config"
                        ... >
            ...
        </application>
    </manifest>

至此即大功告成。

注意:此种方式要求自签名证书的格式为 PEM 或 DER 。详情可以参考官网 网络安全配置

现实的方案

这个才是本文的重点,毕竟我们的App是要上线的,所以要争取更多的用户。

现在Retrofit基本上已经成为Android网络请求的标配了,而其是基于OkHttp的,所以此处处理证书的问题实际上还是仰赖于OkHttp.

具体通过调用:OkHttpClient.Builder 里的

fun sslSocketFactory(sslSocketFactory: SSLSocketFactory,  trustManager: X509TrustManager )

方法。问题进而演变为如何获得这个方法的两个入参的问题,接下来我们就来解决这个问题。

  1. 将自签名证书放到App的src/main/res/raw文件下,例如我的证书叫sng_certificate.crt。

  2. 获得第一个参数SSLSocketFactory

private SSLContext getSslContext(InputStream certificateIs) throws GeneralSecurityException {
 //从证书文件中读入证书
    CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
    Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(certificateIs);  
    if (certificates.isEmpty()) {
        throw new IllegalArgumentException("certificate can not be empty");
    }

    //将读取的证书放入KeyStore, 密码可以为任意值
    char[] password = "ss007".toCharArray();
    KeyStore keyStore = getEmptyKeyStore(password);
    int index = 0;
    for (Certificate certificate : certificates) {
        String certificateAlias = Integer.toString(index++);
        keyStore.setCertificateEntry(certificateAlias, certificate);
    }

    // 使用 keyStore去构建一个X509信任管理器
    KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    keyManagerFactory.init(keyStore, password);          
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init(keyStore);          
    TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
    
    //构建信任的证书管理器构建SSLContext 
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, trustManagers, null);
    return sslContext;
}      

//获取一个空keystore     
private KeyStore getEmptyKeyStore(char[] password) throws GeneralSecurityException {
    try {
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());//此处使用.crt文件,所以使用getDefaultType即可
            InputStream in = null; //'null' creates an empty key store.
            keyStore.load(in, password);
            return keyStore;
        } catch (IOException e) {
            throw new AssertionError(e);
        }
    }

通过以上方法可以获得SSLContext,进而获得SSLSocketFactory,如下所示

InputStream is = context.getResources().openRawResource(R.raw.sng_certificate);
SSLSocketFactory sslSocketFactory= getSslContext(is).getSocketFactory();
  1. 获得第二个参数X509TrustManager

private X509TrustManager getSystemDefaultTrustManager() throws GeneralSecurityException{
    TrustManagerFactory trustManagerFactory=TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init((KeyStore) null);
    TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
    if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
        throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
    }
    return (X509TrustManager) trustManagers[0];
}
  1. 配置OkHttpClient.Builder

//放弃验证证书中的域名信息
builder.hostnameVerifier((hostname, session) -> true);
builder.sslSocketFactory(getSslContext(is).getSocketFactory(), getSystemDefaultTrustManager());

注意: 如果你生成证书时包含的域名或者ip地址和你服务器的域名或者ip不匹配时,默认是会报错的,需要使用hostnameVerifier来放弃验证域名。

经过以上4步就完成了自签名证书在Android中使用的功能。

总结

如果此文帮助到了你,请不要吝啬你的赞美,随手点赞转发,去帮助更多的小伙伴…

相关阅读

Android 系统各个版本上https的抓包
Andriod Studio两种签名机制V1和V2的区别
LiveData+Retrofit 网络请求实战
【平台开发】如何有效的收集 Android 日志?
那些好玩的 android 小事


如果你有写博客的好习惯
欢迎投稿
赞+在看,小生感恩❤️
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值