JSSE(JAVA安全套接字扩展,JAVA Secure Socket Extension)是SSL和TLS的纯Java实现,,通过它可以透明地提供数据加密、服务器认证、信息完整性等功能,就像使用普通的套接字一样使用安全套接字。
开始前的准备
密钥格式
SSL/TLS协议通信就必须涉及到密钥和数字证书。
在Java支持JKS,JCEKS和PKCS#12格式的密钥。但是android不支持JKS,如果入到JKS文件,我们可以把他转换为BKS文件。
JKS文件(通常为.jks或.keystore,扩展名无关)可以通过Java原生工具——KeyTool生成;
JCKES文件也是JAVA中常用的密钥格式。JKS的Provider是SUN,在每个版本的JDK中都有,JCEKS的Provider是SUNJCE,1.4后我们都能够直接使用它。
PKCS#12文件(通常为.p12或.pfx,意味个人信息交换文件),可以通过OpenSSL工具产生。
BKS文件是android单独支持的文件。可以和JKS文件转换。
文件之间的关系
.cer格式文件俗称证书,但这个证书中没有私钥,只包含了公钥;
.pfx格式文件不仅包含了公钥,还包含了私钥,当然这个私钥是加密的,不输入密码是解不了密的;
.jks格式文件表示java密钥存储器(javakey store),它可以同时容纳N个公钥跟私钥,是一个密钥库;
.keystore格式文件其实跟.jks基本是一样的;
.truststore格式文件表示信任证书存储库,它和.keystore是一样的。仅仅包含了通信对方的公钥,当然你可以直接把通信对方的jks作为信任库。
使用SSL/TLS协议通信,如果是双向认证的话,则需要客户端和服务端各自有一组自己的密钥(keyStore)以及信任文件(trustStore),当然可以使用keyStore代替trustStore免去证书相互导入的麻烦。java中的类图如下(这里的keyStore是JDK中的keyStore。android内部也有一个keyStore,这里不去管它)
UML
- 通信核心类——SSLSocket和SSLServerSocket。对于使用过socket进行通信开发的朋友比较好理解,它们对应的就是Socket与ServerSocket,只是表示实现了SSL协议的Socket和ServerSocket,同时它们也是Socket与ServerSocket的子类。SSLSocket负责的事情包括设置加密套件、管理SSL会话、处理握手结束时间、设置客户端模式或服务器模式。
- 客户端与服务器端Socket工厂——SSLSocketFactory和SSLServerSocketFactory。在设计模式中工厂模式是专门用于生产出需要的实例,这里也是把SSLSocket、SSLServerSocket对象创建的工作交给这两个工厂类。
- SSL会话——SSLSession。安全通信握手过程需要一个会话,为了提高通信的效率,SSL协议允许多个SSLSocket共享同一个SSL会话,在同一个会话中,只有第一个打开的SSLSocket需要进行SSL握手,负责生成密钥及交换密钥,其余SSLSocket都共享密钥信息。
- SSL上下文——SSLContext。它是对整个SSL/TLS协议的封装,表示了安全套接字协议的实现。主要负责设置安全通信过程中的各种信息,例如跟证书相关的信息。并且负责构建SSLSocketFactory、SSLServerSocketFactory和SSLEngine等工厂类。
- SSL非阻塞引擎——SSLEngine。假如你要进行NIO通信,那么将使用这个类,它让通过过程支持非阻塞的安全通信。
- 密钥管理器——KeyManager。此接口负责选择用于证实自己身份的安全证书,发给通信另一方。KeyManager对象由KeyManagerFactory工厂类生成。
- 信任管理器——TrustManager。此接口负责判断决定是否信任对方的安全证书,TrustManager对象由TrustManagerFactory工厂类生成。
证书
- 生成服务器证书库
这里的地址需要改为服务器的ip,否则容易出错
keytool -validity 365 -genkey -v -alias server -keyalg RSA -keystore E:\test1\T1\server.keystore -dname "CN=192.168.0.036,OU=rongyiwang,O=rongyiwang,L=Shanghai,ST=Shanghai,c=cn" -storepass 123456 -keypass 123456
- 生成客户端证书库
keytool -validity 365 -genkeypair -v -alias client -keyalg RSA -storetype PKCS12 -keystore E:\test1\T1\client.p12 -dname "CN=client,OU=rongyiwang,O=rongyiwang,L=Shanghai,ST=Shanghai,c=cn" -storepass 123456 -keypass 123456
- 从客户端证书库中导出客户端证书
keytool -export -v -alias client -keystore E:\test1\T1\client.p12 -storetype PKCS12 -storepass 123456 -rfc -file E:\test1\T1\client.cer
- 从服务器证书库中导出服务器证书
keytool -export -v -alias server -keystore E:\test1\T1\server.keystore -storepass 123456 -rfc -file E:\test1\T1\server.cer
- 生成客户端信任证书库(由服务端证书生成的证书库)
这里我直接生产的bks类型的文件
keytool -import -v -alias server -file E:\test1\T1\server.cer -keystore E:\test1\T1\client.truststore -storepass 123456 -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider
- 将客户端证书导入到服务器证书库(使得服务器信任客户端证书)
keytool -import -v -alias client -file E:\test1\T1\client.cer -keystore E:\test1\T1\serverytrust.keystore -storepass 123456
- 查看证书库中的全部证书
keytool -list -keystore E:\test1\T1\server.keystore -storepass 123456
服务端
首先加载自己的密钥和信任密钥
1.keyStore
KeyStore serverKeyStore = KeyStore.getInstance("JKS");
serverKeyStore.load(new FileInputStream(serverKeyStoreFile), serverKeyStorePwd.toCharArray());
2.trustStore
KeyStore serverTrustKeyStore = KeyStore.getInstance("JKS");
serverTrustKeyStore.load(new FileInputStream(serverTrustKeyStoreFile), serverTrustKeyStorePwd.toCharArray());
3.建立SSLServerSocket
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(serverKeyStore, catServerKeyPwd.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(serverTrustKeyStore);
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(18080);
sslServerSocket.setNeedClientAuth(true);
4.到了这里基本就和不同Socket编程差不多了,监听SSLServerSocket即可
while (true){
SSLSocket s = (SSLSocket) sslServerSocket.accept();
if(s!=null){
// do something
}
}