开源项目SMSS发开指南(五)——SSL/TLS加密通讯详解(下)

继上一篇介绍如何在多种语言之间使用SSL加密通讯,今天咱们关注Java端的证书建立以及支持SSL的NioSocket服务端开发。完整源码

1、建立keystore文件

网上大多数是经过jdk命令建立秘钥文件,可是有时候咱们须要将配套的秘钥以及证书让多个模块来使用,他们极可能是由不一样语言开发。在这样的情形下,咱们一般会选择openssl。html

生成服务端的秘钥文件

openssl genrsa -out server.key 2048java

这个秘钥文件是通过Base64编码后生成的,你可使用文本工具打开,有时候这样的编码文件又称为pem文件。android

建立基于当前秘钥的证书请求文件

openssl req -new -key server.key -out server.csrgit

生成证书请求文件会要求你输入一些相关信息,这些信息会同秘钥一块儿被加密存储在.csr文件中。它将被用来向正规的CA机构去申请证书。它也是通过Base64编码后的。算法

申请X509证书

openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crtspring

咱们申请自签名的X509证书,有效期1年,证书包含了公钥和相关信息。因为自签名证书不是由公认的CA机构签发,所以使用它来做为服务端证书的时候,浏览器会提示告警信息。不过这不妨碍咱们在内部环境中使用。apache

建立PKCS#12文件

openssl pkcs12 -export -clcerts -in server.crt -inkey server.key -out server.p12后端

PKCS#12是秘钥交换的标准证书。在加密通讯的过程当中,若是全部的信息都使用非对称加密,性能和时间损耗都很是大。所以,根据SSL握手规则,通讯双方首先利用非对称加密算法协商出一个临时通讯秘钥,而后在本次会话中仅使用基于当前秘钥对信息进行对称加密。会话结束即丢弃,不保存不复用。p12文件中包含了以前生成的私钥信息和申请的公钥信息及全部相关数据。浏览器

利用JDK生成keystore证书

keytool -importkeystore -srckeystore server.p12 -destkeystore server.jks -srcstoretype pkcs12 -deststoretype jkstomcat

这样生成的证书因为使用的是同一个私钥文件,所以.jks文件与.crt文件是同源的。在多语言支持的大系统中它们能够相互认证,也便于统一管理。

请注意,牵涉到加密通讯的系统每每都比较复杂,证书链都必须统一保存。不多会使用各自的工具在不一样的场景下独立使用,所以即便是Java开发者也依然应该掌握如何利用openssl生成完整证书的流程。

2、开发基于SSLEngine的非阻塞服务端

服务端的开发与客户端区别不大,下面说明初始化和握手流程。其余部分的介绍能够参考个人上一篇博客。

服务端初始化

服务端初始化的过程除了须要监听指定端口和处理客户端链接之外,主要是须要初始化SSLContext。SSLContext是整个SSL通讯的基础,也能够认为是生成SSLEngine和SSLSession的工厂方法。具体通讯的加解密过程又后者完成。

/** * 初始化 SSL安全层 */
private SSLContext sslContext;
private void initSSL() throws NoSuchAlgorithmException, KeyStoreException, CertificateException, FileNotFoundException, IOException, UnrecoverableKeyException, KeyManagementException { sslContext = SSLContext.getInstance("SSL"); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); TrustManagerFactory tmf = TrustManagerFactory.getInstance("X.509"); KeyStore ks = KeyStore.getInstance("JKS"); ks.load(new FileInputStream("server.jks"), keystorepass); kmf.init(ks, keypass); tmf.init(ks); sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new java.security.SecureRandom()); }

server.jkd对应以前生成的证书文件,路径根据本身项目的实际路径指定。keystorepass和keypass是生成证书时输入的秘钥。

SSL握手和SSLEngine初始化

正如前文介绍的同样,SSL握手协议中规定了交换秘钥和协商对称加密的过程。所以,实际上在JDK的抽象中,SSL的握手过程本质上是对SSLEngine的初始化。所以与客户端不一样的地方在于,服务端须要在有客户端链接进入后再进行SSLEngine的初始化,并保证每个新链接对应一个SSLEngine对象。当客户端会话关闭后,释放对应的SSLEngine。

/** * 服务端握手操做 */
private SSLEngine sslHandshake(SocketChannel socket) throws IOException { SSLEngine sslEngine = sslContext.createSSLEngine(); sslEngine.setUseClientMode(false); SSLSession sslSession = sslEngine.getSession(); ByteBuffer remoteAppData = ByteBuffer.allocate(sslSession.getApplicationBufferSize()); ByteBuffer localNetData = ByteBuffer.allocate(sslSession.getPacketBufferSize()); ByteBuffer remoteNetData = ByteBuffer.allocate(sslSession.getPacketBufferSize()); sslEngine.beginHandshake(); SSLEngineResult.HandshakeStatus hsStatus = sslEngine.getHandshakeStatus(); SSLEngineResult result; // 循环判断指导握手完成
    while (hsStatus != SSLEngineResult.HandshakeStatus.FINISHED) { switch (hsStatus) { case NEED_WRAP: localNetData.clear(); result = sslEngine.wrap(ByteBuffer.allocate(0), localNetData); // 第一个参数设置空包,SSLEngine会将握手数据写入网络包
            hsStatus = result.getHandshakeStatus(); if (handleResult(result)) { localNetData.flip(); // 确保数据所有发送完成
                while (localNetData.hasRemaining()) { socket.write(localNetData); } } break; case NEED_UNWRAP: int len = socket.read(remoteNetData); // 读取网络数据
            if (len == -1) { break; } remoteNetData.flip(); remoteAppData.clear(); do { result = sslEngine.unwrap(remoteNetData, remoteAppData); // 与握手相关的数据SSLEngine会自行处理,不会输出至第二个参数
                hsStatus = result.getHandshakeStatus(); } while (handleResult(result) && hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP); // 一次性没有完成处理的数据经过压缩的方式处理,等待下一次数据写入
 remoteNetData.compact(); break; case NEED_TASK: // SSLEngine后台任务
 Runnable runnable; while ((runnable = sslEngine.getDelegatedTask()) != null) { runnable.run(); } hsStatus = sslEngine.getHandshakeStatus(); break; default: break; } } return sslEngine; }

其它与客户端共性的部分,再也不赘述。

3、HTTPS相关配置

配置tomcat

对于Java开发者而言,对Tomcat应该不陌生。下面的配置基于tomcat7。

conf/server.xml

<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol" maxThreads="150" SSLEnabled="true" scheme="https" secure="true" clientAuth="false" sslProtocol="TLS" keystoreFile="server.jks" keystorePass="password"
    />

配置springboot

利用springboot开发微服务应用的时候,能够直接部署jar包。下面的配置基于springboot 2.x以上版本。

application.yml

server: 
port: 8081
ssl:
key-store: classpath:server.jks
enabled: true
key-store-password: password
key-store-type: JKS

证书路径为resources下或在启动配置中自由指定。若是配置成功在启动日志上会打印出8081(https)的相关消息,若是你须要让容器同时支持http和https也能够利用@Configuration经过代码加载配置,网上的资料很全,再也不赘述。

不过这样配置其实并不能完成先后端分离的访问请求,由于浏览器转发的时候会默认对证书进行验证。因为咱们的证书不是经过公认的CA机构签发,所以会被默认阻止。固然你也能够经过设置让浏览器放行,不过对于实际项目而言意义不大。

Android 6.0以上版本因为默认使用TLS通讯,所以上面的配置能够应对移动端的访问限制。下面的配置是针对后端仅支持http,android端的配置:

<application /*其它配置*/ android:usesCleartextTraffic="true">

总结

以我我的对将来的展望,因为微服务和高可用的部署会被应用到愈来愈多的场景中,各类语言间的相互调用会成为常态。底层硬件和一些对性能要求较高的场景天然须要C/C++做为支持,可是涉及业务层和互联网方向的应用更多仍是会以Java和其余高级语言为主。相互驱动,互为支持必不可少,而安全通讯也会更加被重视。固然在实际的项目中,为了得到更加稳定的支撑,咱们可能会选择使用框架。不过,可以深刻学习对开发者而言很是重要。我利用两篇博客总结了在多语言支持下与安全通讯相关的主要知识体系,基本以应用为主,少许结合了一些理论知识。但愿可以为你们的学习有所帮助。

 

相关博客:

开源项目SMSS发开指南(四)——SSL/TLS加密通讯详解(上)

开源项目SMSS开发指南

原文出处:https://www.cnblogs.com/learnhow/p/12306755.html

相关文章
相关标签/搜索