HTTPS中的证书验证
签名验证的几种写法
转自:https://github.com/lygttpod/RxHttpUtils
package com.allen.library.http;
import android.annotation.SuppressLint;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
/**
* Created by Allen on 2017/6/26.
* <p>
*
* @author Allen
* 证书工具类
*/
public class SSLUtils {
public static class SSLParams {
public SSLSocketFactory sSLSocketFactory;
public X509TrustManager trustManager;
}
/**
* 信任所有证书,不安全有风险
*/
public static SSLParams getSslSocketFactory() {
return getSslSocketFactoryBase(null, null, null);
}
/**
* https单向认证
* 可以额外配置信任服务端的证书策略,否则默认是按CA证书去验证的,若不是CA可信任的证书,则无法通过验证
*/
public static SSLParams getSslSocketFactory(X509TrustManager trustManager) {
return getSslSocketFactoryBase(trustManager, null, null);
}
/**
* https单向认证
* 用含有服务端公钥的证书校验服务端证书
*/
public static SSLParams getSslSocketFactory(InputStream... certificates) {
return getSslSocketFactoryBase(null, null, null, certificates);
}
/**
* https双向认证
* bksFile 和 password -> 客户端使用bks证书校验服务端证书
* certificates -> 用含有服务端公钥的证书校验服务端证书
*/
public static SSLParams getSslSocketFactory(InputStream bksFile, String password, InputStream... certificates) {
return getSslSocketFactoryBase(null, bksFile, password, certificates);
}
/**
* https双向认证
* bksFile 和 password -> 客户端使用bks证书校验服务端证书
* X509TrustManager -> 如果需要自己校验,那么可以自己实现相关校验,如果不需要自己校验,那么传null即可
*/
public static SSLParams getSslSocketFactory(InputStream bksFile, String password, X509TrustManager trustManager) {
return getSslSocketFactoryBase(trustManager, bksFile, password);
}
private static SSLParams getSslSocketFactoryBase(X509TrustManager trustManager, InputStream bksFile, String password, InputStream... certificates) {
SSLParams sslParams = new SSLParams();
try {
KeyManager[] keyManagers = prepareKeyManager(bksFile, password);
TrustManager[] trustManagers = prepareTrustManager(certificates);
X509TrustManager manager;
if (trustManager != null) {
//优先使用用户自定义的TrustManager
manager = trustManager;
} else if (trustManagers != null) {
//然后使用默认的TrustManager
manager = chooseTrustManager(trustManagers);
} else {
//否则使用不安全的TrustManager
manager = UnSafeTrustManager;
}
// 创建TLS类型的SSLContext对象, that uses our TrustManager
SSLContext sslContext = SSLContext.getInstance("TLS");
// 用上面得到的trustManagers初始化SSLContext,这样sslContext就会信任keyStore中的证书
// 第一个参数是授权的密钥管理器,用来授权验证,比如授权自签名的证书验证。第二个是被授权的证书管理器,用来验证服务器端的证书
sslContext.init(keyManagers, new TrustManager[]{manager}, null);
// 通过sslContext获取SSLSocketFactory对象
sslParams.sSLSocketFactory = sslContext.getSocketFactory();
sslParams.trustManager = manager;
return sslParams;
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (KeyManagementException e) {
throw new AssertionError(e);
}
}
private static KeyManager[] prepareKeyManager(InputStream bksFile, String password) {
try {
if (bksFile == null || password == null) return null;
KeyStore clientKeyStore = KeyStore.getInstance("BKS");
clientKeyStore.load(bksFile, password.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(clientKeyStore, password.toCharArray());
return kmf.getKeyManagers();
} catch (Exception e) {
Log.e("ssl", e.getMessage());
}
return null;
}
private static TrustManager[] prepareTrustManager(InputStream... certificates) {
if (certificates == null || certificates.length <= 0) return null;
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
// 创建一个默认类型的KeyStore,存储我们信任的证书
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
int index = 0;
for (InputStream certStream : certificates) {
String certificateAlias = Integer.toString(index++);
// 证书工厂根据证书文件的流生成证书 cert
Certificate cert = certificateFactory.generateCertificate(certStream);
// 将 cert 作为可信证书放入到keyStore中
keyStore.setCertificateEntry(certificateAlias, cert);
try {
if (certStream != null) certStream.close();
} catch (IOException e) {
Log.e("ssl", e.getMessage());
}
}
//我们创建一个默认类型的TrustManagerFactory
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
//用我们之前的keyStore实例初始化TrustManagerFactory,这样tmf就会信任keyStore中的证书
tmf.init(keyStore);
//通过tmf获取TrustManager数组,TrustManager也会信任keyStore中的证书
return tmf.getTrustManagers();
} catch (Exception e) {
Log.e("ssl", e.getMessage());
}
return null;
}
private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) {
for (TrustManager trustManager : trustManagers) {
if (trustManager instanceof X509TrustManager) {
return (X509TrustManager) trustManager;
}
}
return null;
}
/**
* 为了解决客户端不信任服务器数字证书的问题,网络上大部分的解决方案都是让客户端不对证书做任何检查,
* 这是一种有很大安全漏洞的办法
*/
public static X509TrustManager UnSafeTrustManager = new X509TrustManager() {
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
};
/**
* 此类是用于主机名验证的基接口。 在握手期间,如果 URL 的主机名和服务器的标识主机名不匹配,
* 则验证机制可以回调此接口的实现程序来确定是否应该允许此连接。策略可以是基于证书的或依赖于其他验证方案。
* 当验证 URL 主机名使用的默认规则失败时使用这些回调。如果主机名是可接受的,则返回 true
*/
public static HostnameVerifier UnSafeHostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
}
验证方式 - 都不验证
/**
* 信任所有证书,不安全有风险
*/
public static SSLParams getSslSocketFactory() {
return getSslSocketFactoryBase(null, null, null);
}
验证方式 - 单向认证 - 自定义X509TrustManager
/**
* https单向认证
* 可以额外配置信任服务端的证书策略,否则默认是按CA证书去验证的,若不是CA可信任的证书,则无法通过验证
*/
public static SSLParams getSslSocketFactory(X509TrustManager trustManager) {
return getSslSocketFactoryBase(trustManager, null, null);
}
以下,使用本地证书对比服务器证书公钥
```java
private X509TrustManager x509TrustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
//do nothing,接受任意客户端证书
LogUtils.i(TAG, "checkClientTrusted");
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
//do nothing,接受任意服务端证书
LogUtils.i(TAG, "checkServerTrusted");
//TODO:使用白名单对证书做验证
if (chain == null) {
throw new IllegalArgumentException("check server X509Certificate is null");
}
if (chain.length < 1) {
throw new IllegalArgumentException("check server X509Certificate is empty");
}
LogUtils.i(TAG, "chain =====" + chain.length);
int errorNum = 0;
//验证证书
for (X509Certificate cert : chain) {
// LogUtils.i(TAG, "cert ===" + cert);
// Make sure that it hasn't expired.
cert.checkValidity();
try {
PublicKey publicKey = getServerCert().getPublicKey();
// Verify the certificate's public key chain.
//使用用户证书验证根证书的公钥,知如果验证通过说明道这个用户证书是这个根证书签发的,验证不过就不是这个根证书签发的。
cert.verify(publicKey);
} catch (InvalidKeyException e) {
LogUtils.e(TAG, "使用 AES 加密时,密钥大于128bit的话会抛出java.security.InvalidKeyException异常。因为密钥长度是受限的,所以长度超过时就会抛出这个异常");
e.printStackTrace();
// throw new CertificateException(e);
errorNum++;
} catch (NoSuchAlgorithmException e) {
LogUtils.e(TAG, "意思是导入证书有问题,或者是未寻找到证书.");
e.printStackTrace();
// throw new CertificateException(e);
errorNum++;
} catch (NoSuchProviderException e) {
LogUtils.e(TAG, "NoSuchProviderException.");
e.printStackTrace();
// throw new CertificateException(e);
errorNum++;
} catch (SignatureException e) {
LogUtils.e(TAG, "签名长度不正确.");
e.printStackTrace();
// throw new CertificateException(e);
errorNum++;
}
}
if (errorNum == chain.length){
LogUtils.i(TAG, "证书=========== 异常 ");
throw new CertificateException(new SignatureException());
}else {
LogUtils.i(TAG, "证书=========== 正常 ");
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
LogUtils.i(TAG, "getAcceptedIssuers");
return new X509Certificate[0];
}
};
private X509Certificate serverCert;
private X509Certificate getServerCert() {
if (serverCert == null) {
try {
InputStream inputStream = new BufferedInputStream(AppManager.getContext().getAssets().open("root.cer"));
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
serverCert = (X509Certificate) certificateFactory.generateCertificate(inputStream);
} catch (IOException | CertificateException e) {
e.printStackTrace();
}
}
return serverCert;
}
验证方式 - 单向认证 - 指定信任证书生成X509TrustManager
/**
* https单向认证
* 用含有服务端公钥的证书校验服务端证书
*/
public static SSLParams getSslSocketFactory(InputStream... certificates) {
return getSslSocketFactoryBase(null, null, null, certificates);
}
验证方式 - 双向认证 - 自定义X509TrustManager + KeyManager
/**
* https双向认证
* bksFile 和 password -> 客户端使用bks证书校验服务端证书
* X509TrustManager -> 如果需要自己校验,那么可以自己实现相关校验,如果不需要自己校验,那么传null即可
*/
public static SSLParams getSslSocketFactory(InputStream bksFile, String password, X509TrustManager trustManager) {
return getSslSocketFactoryBase(trustManager, bksFile, password);
}
验证方式 - 单向认证 - 指定信任证书生成X509TrustManager + KeyManager
/**
* https双向认证
* bksFile 和 password -> 客户端使用bks证书校验服务端证书
* certificates -> 用含有服务端公钥的证书校验服务端证书
*/
public static SSLParams getSslSocketFactory(InputStream bksFile, String password, InputStream... certificates) {
return getSslSocketFactoryBase(null, bksFile, password, certificates);
}
更多
SSLSocketFactory
SSLSocketFactory的作用
SSLSocketFactory
是Java中用于创建SSLSocket
对象的工厂类,在基于SSL/TLS协议实现安全的网络通信(比如常见的HTTPS
通信场景)方面有着关键作用:
- 创建安全套接字:它能够生成
SSLSocket
实例,使得客户端与服务器之间可以建立起基于SSL/TLS加密的套接字连接,从而保障数据在传输过程中的保密性、完整性以及对通信双方进行身份验证,防止数据被窃取、篡改以及遭遇中间人攻击等安全问题。 - 配置加密参数:可以对创建的
SSLSocket
配置诸如加密算法套件、协议版本等加密相关参数,以适应不同的安全需求和环境要求,确保通信在符合相应安全标准的情况下进行。 - 信任管理:关联信任存储,用于验证服务器端提供的数字证书是否可信,基于信任策略来决定是否继续建立连接进行通信,保证客户端是与合法的服务器建立连接。
代码示例 - java 版
以下是一个简单的客户端使用SSLSocketFactory
与服务器建立SSL连接并发送、接收数据的示例代码。这里假设服务器已经配置好支持SSL/TLS协议并且有相应的有效证书等(示例只是简单演示连接过程,实际应用场景可能更复杂,且服务器相关配置需提前做好)。
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
public class SSLSocketFactoryExample {
public static void main(String[] args) {
try {
// 创建SSL上下文
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, null, null);
// 获取SSLSocketFactory
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
// 创建SSLSocket并连接到服务器(这里假设服务器IP为localhost,端口为8443,实际根据具体情况修改)
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket("localhost", 8443);
// 开启输入输出流
BufferedReader reader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
PrintWriter writer = new PrintWriter(new OutputStreamWriter(sslSocket.getOutputStream()), true);
// 向服务器发送数据
writer.println("Hello Server!");
// 接收服务器返回的数据
String response = reader.readLine();
System.out.println("服务器回复: " + response);
// 关闭连接相关资源
sslSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
在上述示例代码中:
- 首先通过
SSLContext.getInstance("TLSv1.2")
获取SSLContext
实例,并进行初始化,这里初始化参数暂时使用null
(实际应用中可按需配置更完善的参数,比如设置信任的证书源、密钥管理等)。 - 接着从
SSLContext
获取SSLSocketFactory
对象。 - 使用
SSLSocketFactory
创建SSLSocket
并连接到指定的服务器(示例中服务器地址是localhost
,端口为8443
,实际要按照真实的服务器配置来填写)。 - 然后获取输入输出流,通过输出流向服务器发送消息(这里发送了
"Hello Server!"
),再从输入流中读取服务器返回的数据并打印出来。 - 最后关闭
SSLSocket
释放资源。
请注意,这个示例是一个简单的示意,在实际的生产环境等场景中,还需要进一步完善对证书验证、错误处理等方面的相关代码逻辑,以确保安全可靠的通信。
SSLContext
-
SSLContext的作用和用途
- 协议版本和加密套件管理
- SSLContext用于管理SSL/TLS协议版本和加密套件。它可以指定在安全通信中使用的具体协议版本,如TLSv1.2、TLSv1.3等。不同的协议版本有不同的安全特性和性能特点,通过SSLContext可以选择适合应用场景的版本。同时,它还可以配置加密套件,加密套件是一组加密算法的组合,包括密钥交换算法、对称加密算法和消息摘要算法等,用于确保数据在传输过程中的保密性、完整性和身份验证。
- 信任管理和密钥管理
- 在建立安全连接时,SSLContext负责信任管理。它可以关联信任管理器(TrustManager),信任管理器用于验证服务器端提供的数字证书是否可信。例如,在客户端与服务器进行HTTPS通信时,SSLContext通过信任管理器来检查服务器的证书是否由受信任的证书颁发机构(CA)颁发,以及证书是否有效等。此外,SSLContext还涉及密钥管理,它可以与密钥管理器(KeyManager)协同工作,用于管理客户端自身的私钥和证书(如果需要客户端认证的话),以实现双向身份验证。
- 提供安全套接字工厂和其他安全相关对象的生成基础
- SSLContext是创建SSLSocketFactory和SSLServerSocketFactory等对象的基础。这些工厂对象用于创建安全套接字(SSLSocket和SSLServerSocket),从而建立客户端和服务器之间的安全连接。例如,SSLSocketFactory用于在客户端创建SSLSocket,通过该套接字与服务器进行加密通信。
- 协议版本和加密套件管理
-
代码示例
- 以下是一个简单的Java代码示例,演示了SSLContext在客户端建立安全连接(与支持HTTPS的服务器通信)中的使用:
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.security.NoSuchAlgorithmException; public class SSLContextExample { public static void main(String[] args) { try { // 创建SSLContext对象,指定TLSv1.2协议 SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); // 初始化SSLContext,这里简单使用默认的信任管理器和密钥管理器(实际应用可能需要自定义) sslContext.init(null, null, null); // 获取SSLSocketFactory SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); // 创建SSLSocket并连接到服务器(假设服务器地址是localhost,端口是8443) SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket("localhost", 8443); // 开启输入输出流 BufferedReader reader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream())); PrintWriter writer = new PrintWriter(new OutputStreamWriter(sslSocket.getOutputStream()), true); // 向服务器发送数据 writer.println("Hello Server!"); // 接收服务器返回的数据 String response = reader.readLine(); System.out.println("服务器回复: " + response); // 关闭连接相关资源 sslSocket.close(); } catch (NoSuchAlgorithmException | IOException e) { e.printStackTrace(); } } }
-
在这个示例中:
- 首先通过
SSLContext.getInstance("TLSv1.2")
获取了一个指定协议版本(TLSv1.2)的SSLContext对象。 - 然后使用
sslContext.init(null, null, null)
初始化SSLContext。这里传入的参数都是null
,在实际应用中,为了更好的安全和配置,可以传入自定义的信任管理器(TrustManager)和密钥管理器(KeyManager)。 - 接着从SSLContext获取SSLSocketFactory,并使用它创建SSLSocket来连接到服务器。
- 之后就像普通的Socket通信一样,通过输入输出流进行数据发送和接收。
- 最后关闭SSLSocket,释放资源。
- 首先通过
-
对于服务器端使用SSLContext的示例(以Java中的简单HTTPS服务器为例):
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.ServerSocket; import java.security.NoSuchAlgorithmException; public class SSLServerContextExample { public static void main(String[] args) { try { // 创建SSLContext对象,指定TLSv1.2协议 SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); // 初始化SSLContext,这里简单使用默认的信任管理器和密钥管理器(实际应用可能需要自定义) sslContext.init(null, null, null); // 获取SSLServerSocketFactory SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory(); // 创建SSLServerSocket,监听端口8443(假设) SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(8443); // 等待客户端连接 System.out.println("等待客户端连接..."); javax.net.ssl.SSLSocket clientSocket = (javax.net.ssl.SSLSocket) sslServerSocket.accept(); // 开启输入输出流 BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); PrintWriter writer = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream()), true); // 读取客户端发送的数据 String request = reader.readLine(); System.out.println("客户端请求: " + request); // 向客户端发送回复 writer.println("Hello Client!"); // 关闭连接相关资源 clientSocket.close(); sslServerSocket.close(); } catch (NoSuchAlgorithmException | IOException e) { e.printStackTrace(); } } }
- 在服务器端示例中:
- 同样先创建并初始化SSLContext,指定协议版本为TLSv1.2。
- 然后获取SSLServerSocketFactory,用它创建SSLServerSocket来监听指定端口(8443)。
- 当客户端连接后,接受连接并获取客户端套接字(SSLSocket),通过输入输出流读取客户端请求并发送回复。
- 最后关闭客户端套接字和服务器套接字,释放资源。
请注意,这些示例为了简单起见,在初始化SSLContext时使用了默认的信任管理器和密钥管理器(传入null
),在实际的安全敏感应用中,应该根据具体的安全策略和证书情况,正确配置信任管理器和密钥管理器,以确保安全可靠的通信。
CertificateFactory 把流转换成 X509Certificate 证书
- 用途介绍
CertificateFactory
是Java中用于创建证书和证书撤销列表(CRL)对象的工厂类。它提供了从字节流或文件等不同来源读取证书信息,并将其转换为Java安全相关的证书对象(如X509Certificate
)的功能。- 主要用于处理和验证数字证书,在建立安全的网络连接(如
HTTPS
)、代码签名验证等场景中发挥重要作用。例如,在HTTPS
通信中,客户端可以使用CertificateFactory
来解析服务器发送的数字证书,验证服务器的身份,确保通信的安全性。
- 代码示例
- 以下是一个简单的Java代码示例,用于从文件中读取
X509
证书并打印证书信息:
import java.io.FileInputStream; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; public class CertificateFactoryExample { public static void main(String[] args) { try { // 创建CertificateFactory对象,用于生成证书对象 CertificateFactory certificateFactory = CertificateFactory.getInstance("X509"); // 从文件中读取证书数据 FileInputStream fileInputStream = new FileInputStream("your_certificate_file.crt"); // 生成证书对象 Certificate certificate = certificateFactory.generateCertificate(fileInputStream); fileInputStream.close(); // 检查证书是否为X509Certificate类型 if (certificate instanceof X509Certificate) { X509Certificate x509Certificate = (X509Certificate) certificate; // 打印证书的一些基本信息 System.out.println("证书主题:" + x509Certificate.getSubjectDN()); System.out.println("证书颁发者:" + x509Certificate.getIssuerDN()); System.out.println("证书有效期从:" + x509Certificate.getNotBefore()); System.out.println("证书有效期到:" + x509Certificate.getNotAfter()); } } catch (Exception e) { e.printStackTrace(); } } }
- 在上述代码中:
- 首先通过
CertificateFactory.getInstance("X509")
获取CertificateFactory
对象,指定证书类型为X509
,这是最常见的一种数字证书类型。 - 然后使用
FileInputStream
从文件(这里假设文件名为your_certificate_file.crt
)中读取证书数据,并通过certificateFactory.generateCertificate
方法将字节流转换为Certificate
对象。 - 最后,检查证书是否为
X509Certificate
类型,如果是,则打印证书的主题、颁发者、有效期等基本信息。这些信息可以用于验证证书的合法性和真实性。
- 首先通过
- 以下是一个简单的Java代码示例,用于从文件中读取
请注意,在实际应用中,你需要将your_certificate_file.crt
替换为真实的证书文件路径,并且可能需要进行更复杂的证书验证操作,如验证证书链、检查证书是否被吊销等。
KeyStore
以下是关于 KeyStore
在 Android 中的作用、用途以及相关代码示例:
KeyStore在Android中的作用和用途
-
安全存储敏感信息
在 Android 系统中,KeyStore
用于安全地存储加密密钥、数字证书以及其他敏感的安全相关信息。例如,应用可能需要存储用于加密用户数据的对称密钥,或者保存与服务器进行安全通信(如 HTTPS 双向认证场景)所需的私钥和证书等,将这些信息存储在KeyStore
中能防止它们被恶意应用非法获取和滥用,保障数据的保密性和完整性。 -
保障应用内加密操作安全
许多 Android 应用会对本地存储的数据进行加密,像存储的用户登录凭证、隐私设置等重要信息。KeyStore
为生成和管理这些加密密钥提供了一个可靠的环境,确保只有本应用在经过适当授权的情况下才能使用这些密钥进行加密和解密操作,增强了应用内数据保护的安全性。 -
支持多种安全功能实现
- 指纹认证集成:可以与 Android 的指纹识别功能相结合。例如,应用可以配置在用户使用指纹验证通过后,从
KeyStore
中获取对应的密钥来解锁受保护的功能或解密敏感数据,为用户提供便捷且安全的身份验证方式。 - SSL/TLS 通信中的应用:在和服务器进行基于 SSL/TLS 的安全通信时,如果涉及双向认证(服务器验证客户端,客户端也验证服务器),Android 客户端应用可以把自己的证书和私钥存储在
KeyStore
中,在建立连接时按流程提供给服务器进行认证;同时也可以利用KeyStore
存储信任的根证书颁发机构的证书,用于验证服务器端证书的合法性,确保通信安全。
- 指纹认证集成:可以与 Android 的指纹识别功能相结合。例如,应用可以配置在用户使用指纹验证通过后,从
-
系统级别的信任保障
Android 系统本身依赖KeyStore
来管理系统级的证书等关键信息,比如存储用于验证系统更新来源合法性的证书,以及用于识别受信任的 Wi-Fi 网络、VPN 连接等相关的安全凭证,从系统层面维护整体的网络安全和数据安全环境。
Android代码示例
以下是几个不同应用场景下 KeyStore
在 Android 中的使用示例:
示例一:生成并存储对称加密密钥
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
public class MainActivity extends AppCompatActivity {
private static final String KEY_ALIAS = "my_secret_key_alias";
private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
keyStore.load(null);
// 检查密钥是否已存在,如果不存在则生成
if (!keyStore.containsAlias(KEY_ALIAS)) {
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALIAS_AES, KEYSTORE_PROVIDER);
KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(
KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockSizeBits(128)
.setKeySize(256)
.setEncryptionPadded(true)
.build();
keyGenerator.init(keyGenParameterSpec);
SecretKey secretKey = keyGenerator.generateKey();
}
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException |
InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
}
}
在这个示例中:
- 首先获取
AndroidKeyStore
类型的KeyStore
实例,并加载它(传入null
表示使用默认的加载参数)。 - 接着通过
keyStore.containsAlias(KEY_ALIAS)
检查名为my_secret_key_alias
的密钥是否已经存在,如果不存在,就利用KeyGenerator
来生成一个对称加密密钥(这里指定了使用 AES 算法,用于加密和解密目的,设置了密钥长度、块大小等参数)。生成的密钥会被安全地存储在AndroidKeyStore
中,后续应用就可以在需要加密或解密数据时,从这里获取该密钥进行相应操作。
示例二:使用存储在 KeyStore 中的密钥进行加密和解密(基于上一示例生成的密钥继续操作)
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
public class EncryptionActivity extends AppCompatActivity {
private static final String KEY_ALIAS = "my_secret_key_alias";
private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
private static final String PLAIN_TEXT = "This is a secret message";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_encryption);
try {
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
keyStore.load(null);
SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_ALIAS, null);
// 加密操作
Cipher encryptCipher = Cipher.getInstance(KeyProperties.KEY_ALIAS_AES + "/" +
KeyProperties.BLOCK_MODE_CBC + "/" +
KeyProperties.ENCRYPTION_PADDING_PKCS7);
encryptCipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptedBytes = encryptCipher.doFinal(PLAIN_TEXT.getBytes());
// 解密操作
Cipher decryptCipher = Cipher.getInstance(KeyProperties.KEY_ALIAS_AES + "/" +
KeyProperties.BLOCK_MODE_CBC + "/" +
KeyProperties.ENCRYPTION_PADDING_PKCS7);
decryptCipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decryptedBytes = decryptCipher.doFinal(encryptedBytes);
String decryptedText = new String(decryptedBytes);
// 可以在这里对加密和解密结果进行进一步处理,比如显示等,暂省略相关代码
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException |
InvalidAlgorithmParameterException | javax.crypto.NoSuchPaddingException |
java.security.InvalidKeyException | javax.crypto.BadPaddingException |
javax.crypto.IllegalBlockSizeException e) {
e.printStackTrace();
}
}
}
在这个示例中:
- 同样先获取并加载
AndroidKeyStore
,然后通过keyStore.getKey(KEY_ALIAS, null)
获取之前存储的对称密钥。 - 对于加密操作,创建
Cipher
实例并指定使用的算法、模式和填充方式(这里是符合AndroidKeyStore
中 AES 加密的相关设置),使用密钥初始化Cipher
为加密模式后,对明文(示例中的This is a secret message
)进行加密,得到加密后的字节数组。 - 对于解密操作,类似地创建
Cipher
实例并初始化为解密模式,使用相同的密钥对加密后的字节数组进行解密,最后将解密后的字节数组转换为字符串,以此实现基于KeyStore
中存储密钥的加密和解密过程。
示例三:在 HTTPS 双向认证中使用 KeyStore(简化示例,部分实际配置需完善)
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
public class HttpsActivity extends Activity {
private static final String KEYSTORE_TYPE = "BKS";
private static final String KEYSTORE_PASSWORD = "password";
private static final String KEY_ALIAS = "client_cert_alias";
private static final String SERVER_URL = "https://yourserverurl.com";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
// 加载客户端 KeyStore(假设是 BKS 格式,这里需要将证书和私钥等提前导入到 KeyStore 文件中)
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
InputStream keyStoreInputStream = getResources().openRawResource(R.raw.clientkeystore);
keyStore.load(keyStoreInputStream, KEYSTORE_PASSWORD.toCharArray());
keyStoreInputStream.close();
// 初始化 KeyManagerFactory
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, KEYSTORE_PASSWORD.toCharArray());
// 加载信任的服务器证书的 KeyStore(这里可以是系统默认的或者应用自定义的信任存储)
KeyStore trustStore = KeyStore.getInstance(KEYSTORE_TYPE);
InputStream trustStoreInputStream = getResources().openRawResource(R.raw.truststore);
trustStore.load(trustStoreInputStream, KEYSTORE_PASSWORD.toCharArray());
trustStoreInputStream.close();
// 初始化 TrustManagerFactory
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
// 创建 SSLContext 并进行初始化
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
// 获取 SSLSocketFactory
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
// 这里可以使用 OkHttpClient 等网络库结合 SSLSocketFactory 进行 HTTPS 网络请求,以下是简单示意
// 实际完整的网络请求代码会更复杂,暂省略具体网络请求逻辑
Log.d("HttpsActivity", "准备使用 SSLSocketFactory 进行网络请求");
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException |
KeyManagementException e) {
e.printStackTrace();
}
}
}
在这个示例中:
- 首先加载客户端的
KeyStore
(假设是BKS
格式,从应用的资源文件clientkeystore
中加载,密码为password
),并初始化KeyManagerFactory
,用于管理客户端的证书和私钥等,以便后续在双向认证中向服务器提供客户端的身份验证信息。 - 接着加载信任服务器证书的
KeyStore
(同样假设BKS
格式,从truststore
资源文件加载),初始化TrustManagerFactory
,其作用是验证服务器端发来的证书是否可信。 - 然后创建
SSLContext
,通过传入KeyManagerFactory
生成的密钥管理器和TrustManagerFactory
生成的信任管理器进行初始化,获取SSLSocketFactory
。在实际应用中,可以结合像OkHttpClient
这样的网络库,利用获取到的SSLSocketFactory
来构建安全的HTTPS
网络请求,实现与服务器的双向认证通信。
请注意,上述示例中的各种资源文件名(如 clientkeystore
、truststore
)、密码、服务器网址以及部分涉及到的具体加密参数等都是示例性质的,在实际的 Android 应用开发中,需要根据具体的安全需求、服务器配置等实际情况进行准确的替换和完善配置,并且要遵循 Android 安全最佳实践来保障应用的安全性。
TrustManagerFactory
-
TrustManagerFactory的用途
- 证书信任管理
- TrustManagerFactory是Java用于创建TrustManager对象的工厂类。TrustManager主要负责决定是否信任从远程服务器接收到的SSL/TLS数字证书。在网络通信中,特别是在HTTPS通信场景下,当客户端(如浏览器或其他网络应用)连接到服务器时,服务器会发送其数字证书。TrustManagerFactory通过初始化过程与一个包含受信任证书的KeyStore关联,然后生成的TrustManager会基于这个KeyStore中的证书来验证服务器发送的证书是否可信。例如,它会检查证书是否由受信任的证书颁发机构(CA)颁发、证书是否在有效期内、证书的签名是否有效等内容。
- 安全通信保障
- 它是建立安全的SSL/TLS连接的关键环节。通过确保只有与受信任的证书相对应的服务器才能建立连接,有效地防止中间人攻击。如果服务器提供的证书无法通过TrustManager的验证,SSL/TLS握手过程将会失败,从而阻止不安全的连接建立。这对于保护敏感信息(如用户登录凭证、金融交易数据等)在网络传输过程中的安全至关重要。
- 灵活配置信任策略
- 可以根据不同的应用场景和安全需求灵活配置信任策略。例如,在企业内部网络应用中,可以定制包含企业内部CA颁发证书的KeyStore,并通过TrustManagerFactory使用这个KeyStore来创建TrustManager,使得应用只信任企业内部服务器的证书。或者在开发和测试环境中,可以配置一个相对宽松的信任策略,允许使用自签名证书进行测试通信,而在生产环境中则采用严格的、基于公共CA证书的信任策略。
- 证书信任管理
-
代码示例
- 以下是一个简单的Java代码示例,用于在客户端创建TrustManagerFactory并使用它来验证服务器证书(假设已经有一个包含受信任证书的KeyStore):
import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; public class TrustManagerFactoryExample { public static void main(String[] args) { try { // 加载包含受信任证书的KeyStore(这里假设是JKS格式,名为truststore.jks,密码为password) KeyStore trustStore = KeyStore.getInstance("JKS"); FileInputStream fis = new FileInputStream("truststore.jks"); trustStore.load(fis, "password".toCharArray()); fis.close(); // 创建TrustManagerFactory并初始化,使用SunX509算法,关联TrustStore TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509"); trustManagerFactory.init(trustStore); // 创建SSLContext,指定TLSv1.2协议(可按需调整协议版本) SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); sslContext.init(null, trustManagerFactory.getTrustManagers(), null); // 获取SSLSocketFactory SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); // 创建SSLSocket并连接到服务器(假设服务器地址是localhost,端口是8443) SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket("localhost", 8443); // 开启输入输出流 BufferedReader reader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream())); // 这里可以进行向服务器发送数据等操作,暂省略发送部分代码示例 String response = reader.readLine(); System.out.println("服务器回复: " + response); // 关闭连接相关资源 sslSocket.close(); } catch (NoSuchAlgorithmException | CertificateException | IOException | KeyStoreException e) { e.printStackTrace(); } } }
- 在这个示例中:
- 首先通过
KeyStore.getInstance("JKS")
获取KeyStore实例,并从名为truststore.jks
的文件(密码为password
)中加载受信任的证书。 - 接着创建
TrustManagerFactory
,指定算法为SunX509
并通过trustManagerFactory.init(trustStore)
将其与前面加载的KeyStore关联,这样生成的TrustManager就会根据这个KeyStore中的证书来进行信任验证。 - 然后创建
SSLContext
,将TrustManagerFactory
生成的TrustManager传入sslContext.init
方法进行初始化,获取SSLSocketFactory
后创建SSLSocket
连接到服务器。之后可以像普通的Socket通信一样进行数据交互,这里只是简单展示了接收服务器回复的代码逻辑。最后关闭SSLSocket
,释放资源。
- 首先通过
请注意,在实际应用中,文件名称(如truststore.jks
)、密码以及服务器地址、端口等都需要根据实际情况进行替换和配置,并且对于更严谨的安全应用场景,可能还需要进一步完善证书验证等方面的详细逻辑。
X509TrustManager
-
X509TrustManager的用途
- 证书验证核心功能
- X509TrustManager是一个接口,在Java的SSL/TLS安全通信体系中起着核心的证书验证作用。它主要用于验证X509数字证书,这是最常见的一种用于SSL/TLS通信的证书格式。在客户端与服务器建立安全连接(如HTTPS连接)时,服务器会向客户端发送其数字证书,X509TrustManager会检查证书的多个关键方面来确定是否信任该证书。例如,它会验证证书是否由受信任的证书颁发机构(CA)颁发,检查证书的有效期,确认证书的签名是否正确等。
- 自定义信任策略实现
- 开发者可以通过实现X509TrustManager接口来定制自己的信任策略。这在一些特殊场景下非常有用,比如在开发和测试环境中,可能需要接受自签名证书进行测试,或者在企业内部网络中,只信任企业内部CA颁发的证书。通过自定义X509TrustManager,可以灵活地控制证书验证的逻辑,以适应不同的安全要求和应用场景。
- 安全连接建立的关键环节
- 它是建立安全的SSL/TLS连接过程中的重要组成部分。如果X509TrustManager验证证书失败,那么SSL/TLS握手过程将会中断,从而阻止不安全的连接建立。这对于保护敏感数据(如用户登录信息、金融交易数据等)在网络传输过程中的安全性至关重要。
- 证书验证核心功能
-
代码示例
- 以下是一个简单的Java示例,展示了如何自定义一个简单的X509TrustManager来接受自签名证书(在测试环境下可能会用到这种方式,但在生产环境中要谨慎使用,因为接受自签名证书可能会带来安全风险):
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; public class X509TrustManagerExample { public static void main(String[] args) { try { // 创建自定义的X509TrustManager X509TrustManager customTrustManager = new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { // 在这个简单示例中,我们不进行客户端证书验证,因为这是一个客户端示例 // 在实际的双向认证场景中,需要正确验证客户端证书 } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { // 简单地信任所有服务器证书,这是一个非常不安全的做法,仅用于示例目的 // 在实际应用中,应该根据证书的颁发机构、有效期等因素进行严格验证 System.out.println("接受服务器证书,这是一个不安全的示例做法"); } @Override public X509Certificate[] getAcceptedIssuers() { // 返回一个空数组,表示接受任何颁发机构的证书 return new X509Certificate[0]; } }; // 创建SSLContext并初始化,将自定义的X509TrustManager传入 SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); sslContext.init(null, new X509TrustManager[]{customTrustManager}, null); // 获取SSLSocketFactory SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); // 创建SSLSocket并连接到服务器(假设服务器地址是localhost,端口是8443) SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket("localhost", 8443); // 开启输入输出流 BufferedReader reader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream())); // 这里可以进行向服务器发送数据等操作,暂省略发送部分代码示例 String response = reader.readLine(); System.out.println("服务器回复: " + response); // 关闭连接相关资源 sslSocket.close(); } catch (IOException | java.security.NoSuchAlgorithmException e) { e.printStackTrace(); } } }
- 在这个示例中:
- 首先通过实现X509TrustManager接口创建了一个自定义的信任管理器
customTrustManager
。在checkServerTrusted
方法中,只是简单地打印了一条消息表示接受服务器证书,这是一个非常不安全的做法,仅用于示例目的。在实际应用中,应该根据证书的颁发机构(例如检查是否是受信任的CA颁发的证书)、有效期、证书链等因素进行严格验证。在getAcceptedIssuers
方法中,返回了一个空数组,表示接受任何颁发机构的证书,这也是一种简化的、不安全的做法。 - 然后创建SSLContext,将自定义的X509TrustManager作为参数传入
sslContext.init
方法进行初始化。之后获取SSLSocketFactory并创建SSLSocket连接到服务器,进行数据交互(这里只展示了接收服务器回复的部分),最后关闭SSLSocket释放资源。
- 首先通过实现X509TrustManager接口创建了一个自定义的信任管理器
请注意,在实际的安全敏感应用中,这种简单地接受所有证书的方式是绝对不可取的。应该根据具体的安全策略和证书情况,进行严格的证书验证,例如检查证书链、验证证书的签名、确保证书在有效期内以及证书是由受信任的CA颁发等操作。
checkClientTrusted 服务端验证客户端证书链
public class X509TrustManagerExample {
// 用于存储受信任的CA的名称(这里使用证书主题DN作为示例)
private static final Set<String> trustedCAs = new HashSet<>();
static {
// 假设将受信任的CA的名称添加到集合中
trustedCAs.add("CN=TrustedRootCA, O=ExampleOrg, C=US");
}
// 模拟的证书吊销列表(实际应用中需要从CRL分发点获取并更新)
private static final List<X509Certificate> crlList = new ArrayList<>();
public static void main(String[] args) {
try {
// 创建自定义的X509TrustManager
X509TrustManager customTrustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
//客户端证书链
if (chain == null || chain.length == 0) {
throw new CertificateException("证书链为空");
}
// 验证证书链是否完整
try {
for (int i = 0; i < chain.length - 1; i++) {
X509Certificate currentCert = chain[i];
X509Certificate nextCert = chain[i + 1];
currentCert.verify(nextCert.getPublicKey());
}
} catch (Exception e) {
throw new CertificateException("证书链验证失败", e);
}
// 验证证书颁发机构(CA)
X509Certificate clientCert = chain[0];
String issuerDN = clientCert.getIssuerDN().getName();
if (!trustedCAs.contains(issuerDN)) {
throw new CertificateException("证书不是由受信任的CA颁发");
}
// 验证证书有效期
if (System.currentTimeMillis() < clientCert.getNotBefore().getTime() ||
System.currentTimeMillis() > clientCert.getNotAfter().getTime()) {
throw new CertificateException("证书不在有效期内");
}
// 验证证书吊销状态(模拟的简单检查)
for (X509Certificate revokedCert : crlList) {
if (clientCert.equals(revokedCert)) {
throw new CertificateException("证书已被吊销");
}
}
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// 在这个示例中,我们重点关注客户端证书验证,此处暂不实现服务器证书验证逻辑
// 在实际的双向认证场景中,同样需要对服务器证书进行类似严格的验证
}
@Override
public X509Certificate[] getAcceptedIssuers() {
// 返回一个空数组,表示接受任何颁发机构的证书,这里可以根据实际情况调整
return new X509Certificate[0];
}
};
// 创建SSLContext并初始化,将自定义的X509TrustManager传入
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, new X509TrustManager[]{customTrustManager}, null);
// 获取SSLSocketFactory
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
// 创建SSLSocket并连接到服务器(假设服务器地址是localhost,端口是8443)
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket("localhost", 8443);
// 开启输入输出流
BufferedReader reader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
// 这里可以进行向服务器发送数据等操作,暂省略发送部分代码示例
String response = reader.readLine();
System.out.println("服务器回复: " + response);
// 关闭连接相关资源
sslSocket.close();
} catch (IOException | java.security.NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}
在上述代码中:
-
整体结构:
- 在
main
方法中,首先创建了自定义的X509TrustManager
实例,在其内部实现了checkClientTrusted
、checkServerTrusted
和getAcceptedIssuers
方法。然后基于这个自定义的信任管理器初始化SSLContext
,进而获取SSLSocketFactory
来创建SSLSocket
与服务器进行连接通信(这里只是简单展示了接收服务器回复的逻辑)。
- 在
-
checkClientTrusted
方法内的验证逻辑:- 证书链完整性验证:通过循环使用当前证书验证下一个证书的公钥来检查证书链是否完整,若出现异常则抛出
CertificateException
表示验证失败。 - 证书颁发机构验证:获取客户端证书的颁发者DN(
issuerDN
),与预先配置的受信任CA
集合(trustedCAs
)进行比对,若不在集合中则抛出异常,表明证书不是由受信任的CA
颁发。 - 证书有效期验证:检查当前时间是否在客户端证书的有效期(由
notBefore
和notAfter
确定)范围内,若不在则抛出异常说明证书已过期。 - 证书吊销状态验证:遍历模拟的吊销证书列表(
crlList
),检查客户端证书是否在其中,若在则抛出异常表示证书已被吊销。
- 证书链完整性验证:通过循环使用当前证书验证下一个证书的公钥来检查证书链是否完整,若出现异常则抛出
请注意,上述示例中的服务器地址、端口以及受信任CA
集合、吊销证书列表等都是模拟数据,在实际应用场景中,需要根据真实的配置和需求进行准确替换与完善,并且对于证书吊销检查等操作,建议采用更专业可靠的方式(如实时从权威的CRL分发点获取或通过OCSP协议等进行验证)来确保安全可靠的通信。
checkServerTrusted
-
用途
- 验证服务器身份:在SSL/TLS通信中,特别是在客户端 - 服务器架构里,
checkServerTrusted
是用于验证服务器身份的关键方法。当客户端尝试与服务器建立安全连接(如HTTPS连接)时,服务器会向客户端发送其数字证书。checkServerTrusted
方法会在客户端被调用,用于检查服务器提供的证书是否可信,确保客户端是与真正的目标服务器(而非中间人攻击者伪装的服务器)进行通信。 - 保障通信安全:通过验证服务器证书,可以防止中间人攻击等安全威胁。如果服务器证书无法通过验证,客户端将不会与服务器建立连接,从而保护客户端发送和接收的敏感数据(如用户登录信息、金融交易数据等)的安全。
- 遵循信任策略:它使得客户端能够根据预先配置的信任策略进行验证。这些信任策略可以基于受信任的证书颁发机构(CA)列表、证书有效期、证书吊销状态等来确定是否信任服务器证书。例如,客户端可以配置为只信任特定CA颁发的证书,或者检查证书是否在有效期内等。
- 验证服务器身份:在SSL/TLS通信中,特别是在客户端 - 服务器架构里,
-
示例
- 以下是一个简单的Java示例,展示了如何在客户端实现
checkServerTrusted
方法来验证服务器证书:
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.HashSet; import java.util.Set; public class CheckServerTrustedExample { // 存储受信任的CA名称(这里使用证书主题DN作为示例) private static final Set<String> trustedCAs = new HashSet<>(); static { // 假设将受信任的CA名称添加到集合中 trustedCAs.add("CN=TrustedRootCA, O=ExampleOrg, C=US"); } public static void main(String[] args) { try { // 创建自定义的X509TrustManager X509TrustManager customTrustManager = new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { // 在这个示例中,我们重点关注服务器证书验证,此处暂不实现客户端证书验证逻辑 // 在实际的双向认证场景中,需要正确验证客户端证书 } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { if (chain == null || chain.length == 0) { throw new CertificateException("证书链为空"); } X509Certificate serverCert = chain[0]; String issuerDN = serverCert.getIssuerDN().getName(); if (!trustedCAs.contains(issuerDN)) { throw new CertificateException("证书不是由受信任的CA颁发"); } if (System.currentTimeMillis() < serverCert.getNotBefore().getTime() || System.currentTimeMillis() > serverCert.getNotAfter().getTime()) { throw new CertificateException("证书不在有效期内"); } } @Override public X509Certificate[] getAcceptedIssuers() { // 返回一个空数组,表示接受任何颁发机构的证书,这里可以根据实际情况调整 return new X509Certificate[0]; } }; // 创建SSLContext并初始化,将自定义的X509TrustManager传入 SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); sslContext.init(null, new X509TrustManager[]{customTrustManager}, null); // 获取SSLSocketFactory SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); // 创建SSLSocket并连接到服务器(假设服务器地址是localhost,端口是8443) SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket("localhost", 8443); // 开启输入输出流 BufferedReader reader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream())); // 这里可以进行向服务器发送数据等操作,暂省略发送部分代码示例 String response = reader.readLine(); System.out.println("服务器回复: " + response); // 关闭连接相关资源 sslSocket.close(); } catch (IOException | java.security.NoSuchAlgorithmException e) { e.printStackTrace(); } } }
- 在这个示例中:
- 首先定义了一个
trustedCAs
集合,用于存储受信任的CA名称(以证书主题DN为例)。 - 然后创建了自定义的
X509TrustManager
,在checkServerTrusted
方法中进行了以下验证:- 检查证书链是否为空,如果为空则抛出
CertificateException
。 - 获取服务器证书(假设证书链中的第一个证书是服务器证书),获取其颁发者DN(
issuerDN
),并检查是否在受信任的CA集合中。如果不在,则抛出CertificateException
,表示证书不是由受信任的CA颁发的。 - 检查服务器证书的有效期,即检查当前时间是否在证书的
notBefore
和notAfter
时间戳之间。如果不在有效期内,则抛出CertificateException
。
- 检查证书链是否为空,如果为空则抛出
- 后续的步骤包括创建
SSLContext
、获取SSLSocketFactory
、创建SSLSocket
与服务器连接、进行数据交互(这里只展示了接收服务器回复)以及关闭连接相关资源。
- 首先定义了一个
- 以下是一个简单的Java示例,展示了如何在客户端实现
请注意,这个示例只是一个简单的示意,在实际应用中,还可以添加更多的验证逻辑,如验证证书链的完整性、检查证书吊销状态等,以增强服务器证书验证的安全性。
参考地址:
https://github.com/androidHarlan/RxHttpUtils-2.x
苹果核 - Android App 安全的HTTPS 通信:http://pingguohe.net/2016/02/26/Android-App-secure-ssl.html
Android 根证书管理与证书验证:https://blog.csdn.net/edmond999/article/details/87089833
https证书有效性验证引发的安全问题:https://www.cnblogs.com/by-3ks/articles/4113849.html
豆包