如何在Android 4.0 WebView中配置SSL证书实现信任?
解决Android 4.0信任Let's Encrypt证书的问题
首先得帮你理清手里的.pem证书和常见的.crt文件的区别——其实它们本质都是X.509证书格式,只是扩展名不同而已:.crt可以是二进制或Base64编码,.pem一般是带-----BEGIN CERTIFICATE-----标记的Base64编码文件。你手里的几个文件作用分别是:
cert.pem:你的域名专属的服务器证书,只包含站点公钥chain.pem:Let's Encrypt的中间证书链,用来连接你的服务器证书和根证书fullchain.pem:是cert+chain的合并版,包含完整的证书验证链,旧Android系统必须要完整链才能通过验证key.pem:服务器的私钥,这个是服务器端用的,客户端完全不需要碰它
为什么Android 4.0连不上你的站点?
核心原因是Android 4.0(API 14-15)的系统信任列表里没有收录Let's Encrypt的根证书(也就是ISRG Root X1),而且旧版本的SSL/TLS栈对证书链的校验更严格,缺少中间链直接就判定证书不可信,这也是新版本Android能正常访问的原因——新版本系统已经把这些根证书加入信任列表了。
下面给你两个可行的解决方案,根据你的需求选就行:
方案1:用Portecle给Android 4.0系统手动导入证书(适合测试/定制设备)
你已经有Portecle了,操作很简单:
- 打开Portecle,导入
fullchain.pem文件(一定要用fullchain,它包含完整的证书链) - 将证书导出为DER格式(导出时可以把扩展名改成.crt,Android认这个格式)
- 把导出的证书放到Android 4.0设备的SD卡根目录
- 打开设备「设置」→「安全」→「从SD卡安装证书」,选择刚才的证书,用途选「VPN和应用程序」,跟着提示完成导入
- 重启设备后,你的站点就能被系统信任了
方案2:在App代码中自定义信任管理器(推荐给正式发布的应用)
如果是你开发的App,不想让用户手动操作,可以在代码里让App主动信任Let's Encrypt的证书链:
- 把
fullchain.pem复制到App的res/raw目录下(重命名时别加空格或特殊字符) - 编写一个自定义SSL工具类,加载证书链:
import android.content.Context; import java.io.InputStream; import java.security.KeyStore; import java.security.cert.CertificateFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; public class LetsEncryptSSLHelper { public static SSLContext getTrustedSSLContext(Context context) throws Exception { // 初始化证书工厂,加载fullchain.pem CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); InputStream certStream = context.getResources().openRawResource(R.raw.fullchain); // 构建自定义信任密钥库 KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null); keyStore.setCertificateEntry("lets_encrypt_cert", certFactory.generateCertificate(certStream)); // 初始化信任管理器 TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(keyStore); // 创建并返回自定义SSLContext SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, trustManagerFactory.getTrustManagers(), null); return sslContext; } }
- 在你的WebView或网络请求库中使用这个自定义SSLContext:
比如WebView的配置:
WebView myWebView = findViewById(R.id.my_webview); WebSettings webSettings = myWebView.getSettings(); webSettings.setJavaScriptEnabled(true); try { SSLContext sslContext = LetsEncryptSSLHelper.getTrustedSSLContext(this); myWebView.setWebViewClient(new WebViewClient() { @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { // 正常配置下不会走到这里,除非证书有问题,这里不要直接忽略错误 handler.proceed(); } }); // 如果用OkHttp发起网络请求,也可以配置客户端使用这个SSLContext // OkHttpClient client = new OkHttpClient.Builder() // .sslSocketFactory(sslContext.getSocketFactory()) // .build(); } catch (Exception e) { e.printStackTrace(); } myWebView.loadUrl("你的站点URL");
额外要注意的点
- Android 4.0的SSL/TLS栈不支持TLS 1.2及以上版本,所以你的服务器必须开启TLS 1.0支持——检查你的Nginx/Apache配置,确保
ssl_protocols里包含TLSv1 - 绝对不要把
key.pem放到客户端App里,这是服务器的私钥,泄露会导致严重的安全问题
内容的提问来源于stack exchange,提问作者why




