【加密与解密】【07】SSL安全套件全解析

SSL/TLS协议

SSL,Secure Socket Layer,安全套接层

TLS,Transport Layer Security,传输层安全协议

TLS是SSL的最终完善版本,一般也可称为SSL协议

SSL是负责传输层安全,确定传输层数据如何封装的一套协议

SSL协议阶段
  • 算法协商
  • 数字证书验证
  • 数据通信
SSL协议核心内容
  • 事先准备:

  • 服务端生成自己的私钥和公钥

  • 服务端向CA机构申请SSL证书

  • CA向服务器颁发数字证书,该证书通过CA私钥加密,包含了服务端域名和公钥,用于证明服务端身份

  • 算法协商阶段:

  • 客户端和服务端分别生成随机数RNC和RNS,连同SSL算法参数一起发送给对方

  • 数字证书验证阶段:

  • 服务端将自己的数据进行摘要,再通过私钥加密,生成数字签名

  • 服务端将数据+数字签名+数字证书,一同发往客户端

  • 客户端通过CA公钥解密数字证书,确认证书与域名一致后,拿到服务端公钥(保证了公钥未被篡改)

  • 客户端通过服务端公钥解开数字签名,得到数据摘要(保证了数据来自服务端)

  • 客户端对数据部分进行摘要,与数字签名中的摘要进行对比(保证了数据完整性)

  • 客户端生成随机数PMS(Pre Master Secret),通过服务端公钥加密,发给服务端

  • 服务端通过私钥解密出PMS

  • 双方根据RNC+RNS+PMS构建主密钥MS(Master Secret),完成对称秘钥协商

  • 数据通信阶段:

  • 服务端和客户端通过主密钥MS,加密解密通信数据

  • 双向认证:

  • 如果服务端也要求客户端持有SSL证书,则增加一步服务端验证客户端证书的过程

  • 秘钥协商算法:

  • 以上流程使用的是DH秘钥协商算法

  • 如果用的是RSA算法,则直接将PMS作为MS使用,并且由客户端发送给服务端

SSL协议应用
  • 最常见的是应用于Https协议
  • 此外也可应用于Socket和WebSocket
SSL核心类
  • KeyManager,用于管理自己的私钥和证书,多用于服务端
  • TrustManager,用于验证收到的证书是否可信,多用于客户端
  • 服务端也可以要求客户端也发送安全证书,即双向认证,此时双方都要同时设置KeyManager+TrustManager
  • KeyStore,秘钥仓库,用来存储私钥、公钥、证书等数据,可设置密码
  • TrustStore,KeyStore的一种,存储CA证书,用于验证服务端身份正确性,密码公开
  • KeyStore和TrustStore的区别,仅在于存储的内容不同,它们可以是同一个文件,但是不建议这么做
  • Java中比较常见的KeyStore文件格式是JKS,此外还有CRT、PEM、P12、KEY等格式,私钥和证书也可能分开保存
  • KeyManagerFactory,用于创建KeyManager,一般通过algorithm和keystore文件来初始化
  • TrustManagerFactory,用于创建TrustManager,一般通过algorithm和keystore文件来初始化
  • SSLContext,用于整体管理SSL相关事务,一般通过KeyManager和TrustManager来初始化
  • 当KeyManager和TrustManager未指定时,SSLContext会从系统已安装的SecurityProvider中,搜索合适的Provider来处理对应工作
  • SSLServerSocketFactory,用于创建带SSL功能的ServerSocket,可通过SSLContext创建
  • SSLSocketFactory,用于创建带SSL功能的ClientSocket,可通过SSLContext创建
  • 也可以不使用Factory和KeyStore,通过自定义的方式来创建KeyManager和TrustManager
  • SSLSessionContext,维护所有会话信息,可通过SSLContext.getSessionContext获得
  • SSLSession,可通过SSLSessionContext或SSLSocket来获得
  • SSLServerSocket可以接收来自多个客户端的连接,SSLSocket只能连接指定的服务器和端口
  • SSLEngine,用于实现SSL握手,通过SSLContext创建,一般不用自己去实现,属于偏内部的Class
通过KeyTool生成KeyStore

学习SSL首先得有用于测试的SSL证书库,所以我们先来看看如何生成KeyStore

private.keystore和public.truststore本质上都是JKS文件,只是为了区分作用而换了后缀

# 生成私钥证书,首个名称请输入域名或ip
keytool -genkeypair -alias alias -keyalg RSA -validity 365 -keystore private.keystore

# 生成公钥证书
keytool -export -alias alias -keystore private.keystore -rfc -file public.cer

# 公钥证书转为JKS格式
keytool -import -alias alias -file public.cer -keystore public.truststore
KeyStore格式转换

JKS是Java专用的秘钥仓库格式

不同平台和语言的对KeyStore的支持性和使用习惯并不一样

比如安卓就不支持JKS格式,安卓上的证书一般使用P12格式

我们可以通过KeyStore Explorer软件来转换

https://keystore-explorer.org/downloads.html
使用带SSL的TcpSocket
import java.io.*
import java.security.KeyStore
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLServerSocket
import javax.net.ssl.SSLSocket
import javax.net.ssl.TrustManagerFactory

const val serverKeyStore = "resources/private.keystore"
const val clientKeyStore = "resources/public.truststore"
const val passphrase = "123456"

const val serverPort = 18001

fun main() {
    Thread(::launchServerSocket).start()
    Thread.sleep(500)
    Thread(::launchClientSocket).start()
}

fun launchServerSocket() {
    // load key manager from key store
    val keyManagerFactory = KeyManagerFactory.getInstance("SunX509")
    val keyStore = KeyStore.getInstance("JKS")
    keyStore.load(FileInputStream(serverKeyStore), passphrase.toCharArray())
    keyManagerFactory.init(keyStore, passphrase.toCharArray())
    val keyManagers = keyManagerFactory.keyManagers
    // init ssl context
    val context = SSLContext.getInstance("TLS")
    context.init(keyManagers, null, null)
    // create server socket
    val serverSocketFactory = context.serverSocketFactory
    val serverSocket = serverSocketFactory.createServerSocket(serverPort) as SSLServerSocket
    serverSocket.needClientAuth = false
    // accept client session
    val socket = serverSocket.accept() as SSLSocket
    // communication with client
    while (true) {
        Thread.sleep(500)
        socket.getOutputStream().write("Hello World".encodeToByteArray())
        socket.getOutputStream().flush()
    }
}

fun launchClientSocket() {
    // load trust manager from trust store
    val trustManagerFactory = TrustManagerFactory.getInstance("SunX509")
    val trustStore = KeyStore.getInstance("JKS")
    trustStore.load(FileInputStream(clientKeyStore), passphrase.toCharArray())
    trustManagerFactory.init(trustStore)
    val trustManagers = trustManagerFactory.trustManagers
    // init ssl context
    val context = SSLContext.getInstance("TLS")
    context.init(null, trustManagers, null)
    // create client socket and auto connect
    val sslSocketFactory = context.socketFactory
    val socket = sslSocketFactory.createSocket("localhost", serverPort) as SSLSocket
    // communication with server
    val buffer = ByteArray(1024)
    while (true) {
        val len = socket.getInputStream().read(buffer)
        if (len > 0) {
            val message = String(buffer, 0, len)
            println("Client Received: $message")
        }
    }
}
使用带SSL的HttpServer

大多网络编程框架,都是默认支持Https协议的,但仅限于由CA机构颁发的可信任证书

对于人工签发,未经CA机构授权的自签名证书,必须由开发者自己去实现验证逻辑

import com.sun.net.httpserver.HttpsConfigurator
import com.sun.net.httpserver.HttpsServer
import java.io.*
import java.net.InetSocketAddress
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.security.KeyStore
import java.util.*
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory

const val serverKeyStore = "resources/private.keystore"
const val clientKeyStore = "resources/public.truststore"
const val passphrase = "123456"

const val serverPort = 18001

fun main() {
    Thread(::launchHttpServer).start()
    Thread.sleep(1500)
    Thread(::launchHttpClient).start()
}

fun launchHttpServer() {
    // load key manager from key store
    val keyManagerFactory = KeyManagerFactory.getInstance("SunX509")
    val keyStore = KeyStore.getInstance("JKS")
    keyStore.load(FileInputStream(serverKeyStore), passphrase.toCharArray())
    keyManagerFactory.init(keyStore, passphrase.toCharArray())
    val keyManagers = keyManagerFactory.keyManagers
    // configure ssl context
    val context = SSLContext.getInstance("TLS")
    context.init(keyManagers, null, null)
    // create https server
    val server = HttpsServer.create(InetSocketAddress(serverPort), 10)
    // configure https
    val configurator = HttpsConfigurator(context)
    server.httpsConfigurator = configurator
    // create service
    server.createContext("/home") { exchange ->
        val response = Date().toString().encodeToByteArray()
        exchange.sendResponseHeaders(200, response.size.toLong())
        exchange.responseBody.write(response)
        exchange.responseBody.close()
    }
    // start https server
    server.start()
}

fun launchHttpClient() {
    // load trust manager from trust store
    val trustManagerFactory = TrustManagerFactory.getInstance("SunX509")
    val trustStore = KeyStore.getInstance("JKS")
    trustStore.load(FileInputStream(clientKeyStore), passphrase.toCharArray())
    trustManagerFactory.init(trustStore)
    val trustManagers = trustManagerFactory.trustManagers
    // init ssl context
    val context = SSLContext.getInstance("TLS")
    context.init(null, trustManagers, null)
    // create http client
    val uri = URI.create("https://localhost:18001/home")
    val client = HttpClient.newBuilder()
        .version(HttpClient.Version.HTTP_1_1)
        .sslContext(context)
        .build()
    // send request
    val request = HttpRequest.newBuilder()
        .GET()
        .uri(uri)
        .build()
    // get response
    val response = client.send(request, HttpResponse.BodyHandlers.ofString())
    println(response.body())
}
使用带SSL的WebSocket

这里我们使用比较出名的Java-WebSocket库来实现WebSocket服务端和客户端功能

api("org.java-websocket:Java-WebSocket:1.5.1")

为WebSocketServer设置SSL

fun setServerSSL(server: WebSocketServer) {
    // load key manager from key store
    val keyManagerFactory = KeyManagerFactory.getInstance("SunX509")
    val keyStore = KeyStore.getInstance("JKS")
    keyStore.load(FileInputStream(serverKeyStore), passphrase.toCharArray())
    keyManagerFactory.init(keyStore, passphrase.toCharArray())
    val keyManagers = keyManagerFactory.keyManagers
    // configure ssl context
    val context = SSLContext.getInstance("TLS")
    context.init(keyManagers, null, null)
    // configure server ssl
    val websocketServerFactory = DefaultSSLWebSocketServerFactory(context)
    server.setWebSocketFactory(websocketServerFactory)
}

为WebSocketClient设置SSL

fun setClientSSL(client: WebSocketClient) {
    // load trust manager from trust store
    val trustManagerFactory = TrustManagerFactory.getInstance("SunX509")
    val trustStore = KeyStore.getInstance("JKS")
    trustStore.load(FileInputStream(clientKeyStore), passphrase.toCharArray())
    trustManagerFactory.init(trustStore)
    val trustManagers = trustManagerFactory.trustManagers
    // configure ssl context
    val context = SSLContext.getInstance("TLS")
    context.init(null, trustManagers, null)
    // configure client ssl
    val sslSocketFactory = context.socketFactory
    client.setSocketFactory(sslSocketFactory)
}
在OkHttp中自定义KeyManager和TrustManager

以上案例,都是通过KeyStore来实现KeyManager和TrustManager的管理功能

现在我们不用KeyStore,通过自定义规则,来实现秘钥管理和证书验证功能

我们以OkHttp框架为例

import okhttp3.OkHttpClient
import java.security.cert.X509Certificate
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSession
import javax.net.ssl.X509TrustManager

fun setOkHttpSSL(builder: OkHttpClient.Builder) {
    val trustManager = object : X509TrustManager {
        override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String) {}
        override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String) {}
        override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray()
    }
    val trustManagers = arrayOf(trustManager)
    val hostnameVerifier = HostnameVerifier { hostname: String, session: SSLSession -> true }
    val sslContext = SSLContext.getInstance("SSL")
    sslContext.init(null, trustManagers, null)
    val socketFactory = sslContext.socketFactory
    builder.sslSocketFactory(socketFactory, trustManager)
    builder.hostnameVerifier(hostnameVerifier)
}

现在我们用OkHttp来替换上面的HttpClient来访问服务器

fun launchHttpClient() {
    val url = "https://localhost:18001/home"
    // create okhttp client
    val builder = OkHttpClient.Builder()
    setOkHttpSSL(builder)
    val client = builder.build()
    // create request
    val request = Request.Builder()
        .url(url)
        .get()
        .build()
    // execute call
    val call = client.newCall(request)
    val response = call.execute()
    // print response
    val responseBody = response.body.string()
    println(responseBody)
}

这里我们为了演示,不让问题复杂化,只是简单地信任了所有的证书,并不能起到实际的安全作用

关于KeyManager,TrustManager,HostnameVerifier的正式用法,可以参考以下类的源码

SunX509KeyManagerImpl X509TrustManagerImpl OkHostnameVerifier

SSL证书格式
  • JKS,二进制格式存储,Java专属格式

    一般为私钥+证书+密码,或只有公钥的组合

    常用于Tomcat服务器

  • PEM,文本格式存储

    可保存私钥和证书,一般以BEGIN开头,END结尾,中间为BASE64编码字符串

    常用于Apache或Nginx服务器

  • CER/DER,二进制格式存储

    只能保存证书

    常用于Windows服务器

  • CRT,只是一个后缀名,可以是PEM编码,也可以是CER编码

    一般只用来保存证书,不存储私钥

    可以将私钥单独保存,以KEY作为后缀,来区分秘钥文件和证书文件

    KEY文件可以是PEM编码,也可以是CER编码

  • P12/PKCS12/PFX,二进制格式存储

    一般同时包含私钥和证书,有密码保护

    常用于Windows IIS服务器

  • CSR,证书请求文件

    这个不是证书,而是通过私钥向CA申请公钥的请求文件

  • 最后,要注意的是,文件后缀和证书格式之间没有必然关系,还是以文件内容的实际存储格式为准

SSL证书转换工具
  • OpenSSL
  • KeyTool
  • KeyStore Explorer
OpenSSL指令
  • genrsa,生成秘钥
  • req,创建自签名根证书,或生成证书请求文件
  • x509,查看,创建,或转换证书
  • -in,输入文件
  • -out,输出文件
  • -inform,输入文件格式
  • -outform,输出文件格式
  • -CA,指定根证书
  • -CAkey,指定根证书私钥
  • -CAserial,指定CA证书
  • -CAcreateserial,创建下级CA证书
OpenSSL制作证书
# 创建根证书
openssl req -new -x509 -days 365 -extensions v3_ca -keyout crt/ca.key -out crt/ca.crt

# 颁发服务端证书
openssl genrsa -out crt/server.key 2048
openssl req -out crt/server.csr -key crt/server.key -new
openssl x509 -req -in crt/server.csr -CA crt/ca.crt -CAkey crt/ca.key -CAcreateserial -out crt/server.crt -days 365

# 颁发客户端证书
openssl genrsa -out crt/client.key 2048
openssl req -out crt/client.csr -key crt/client.key -new
openssl x509 -req -in crt/client.csr -CA crt/ca.crt -CAkey crt/ca.key -CAcreateserial -out crt/client.crt -days 365

# 查看服务端证书
openssl x509 -in crt/server.crt -text -noout
End

到此为止,基本涵盖了Java SSL的所有核心内容,已经足以满足大家日常开发需要

更高阶的用法,可能在专业的领域才能用得到,希望大家遇到时,能勇于自己去研究!

  • 47
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值