现在的SSL的加密算法实际上主要是国际算法,包括JDK,Go等语言也仅支持国际算法加密(毕竟是国外开源项目),hash。随着国密算法的普及,比如openssl就支持国密了,还要新版本的Linux内核也开始支持,以openssl为例:

那么如果需要国密证书,或者访问国密https的时候就需要特定的sslsocket的握手算法
TLCP是中国基于TLS 1.1和1.2协议定制而成的协议,对应的中国国家标准为:
该协议与TLS协议的最重大区别,就是要求通信端提供两个证书:认证证书和加密证书。其中认证证书与TLS协议使用的证书功能类似,用于对通信端的身份进行验证。而加密证书则为TLCP协议独有,它只会用于密钥交换。
常用的国密开源套件有BouncyCastle、Kona,BouncyCastle时间比较久远了,用的比较多,比如OOM的坑,Kona是腾讯开源的国密套件,开始基于BouncyCastle,后面重写了,分别使用者两个套件试试沃通搭建的SM2SSL证书测试网站欢迎访问沃通基于国密算法的https加密解决方案演示网站
https://sm2test.ovssl.cn/
和 中国银行官网
https://ebssec.boc.cn/
其中沃通是支持国密和国际算法的无缝切换的,因为有双证书

https://www.bouncycastle.org/java.html,github使用MIT license

截止当前的日期

bouncycastle的ssl支持org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
- <dependency>
- <groupId>org.bouncycastlegroupId>
- <artifactId>bcprov-jdk18onartifactId>
- <version>1.76version>
- dependency>
- <dependency>
- <groupId>org.bouncycastlegroupId>
- <artifactId>bctls-jdk18onartifactId>
- <version>1.76version>
- dependency>
定义了protocol

都是标准的,而这些支持的加密套件呢,实际上国密SSL(中国银行官网)使用的是TLSV1.1变化版本,这里实际执行的类是org.bouncycastle.jsse.provider.ProvSSLContextSpi的
createSupportedCipherSuiteMap
定义了使用的加密套件,然而国密的套件实际上有定义
org.bouncycastle.tls.CipherSuite

RFC 8998
RFC 8998规范将国密算法要素应用到了TLS 1.3协议中。KonaSSL实现了该规范定义的椭圆曲线curveSM2(41),签名机制sm2sig_sm3(0x0708)和密码套件TLS_SM4_GCB_SM3(0x00C6)。

所以,虽然支持RFC 8998标准,但是tls不支持国密通信,因为没有加入加密套件,也没有定义协议版本

笔者自己加了一个,不过发现修改的地方不少,国密ssl版本号定义0x0101,跟主流的ssl差别很大
企鹅开源的,在1.0.5版本不再基于BouncyCastle
腾讯Kona国密套件使用的许可协议是GNU GPL v2.0 license with Classpath Exception,这也正是OpenJDK使用的许可协议。
腾讯Kona国密套件包含四个Java Security Provider:
--来源于腾讯Kona国密套件:从基础算法到安全协议-腾讯云开发者社区-腾讯云
以kona为例,实际上BouncyCastle也有TLS模块,但是貌似没做国密的协议簇,只能使用国际标准,这里使用不知道初始原作者是谁的gmssl的demo代码,引入pom
- <dependency>
- <groupId>com.tencent.konagroupId>
- <artifactId>kona-providerartifactId>
- <version>${kona.version}version>
- dependency>
- <dependency>
- <groupId>com.tencent.konagroupId>
- <artifactId>kona-cryptoartifactId>
- <version>${kona.version}version>
- dependency>
- <dependency>
- <groupId>com.tencent.konagroupId>
- <artifactId>kona-sslartifactId>
- <version>${kona.version}version>
- dependency>
- <dependency>
- <groupId>com.tencent.konagroupId>
- <artifactId>kona-pkixartifactId>
- <version>${kona.version}version>
- dependency>
然后通过socket方式支持国密https,以中国银行官网为测试
- package org.example;
-
- import com.tencent.kona.KonaProvider;
- import org.bouncycastle.jce.provider.BouncyCastleProvider;
- import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
-
- import java.net.*;
- import java.io.*;
- import java.security.*;
-
- import javax.net.*;
- import javax.net.ssl.*;
-
- public class SocketGet {
- public static void main(String[] args) {
- SocketFactory fact = null;
- SSLSocket socket = null;
-
- // String addr = "sm2test.ovssl.cn";
- String addr = "ebssec.boc.cn";
- int port = 443;
- String uri = "/";
-
- try {
- if (args.length > 0) {
- addr = args[0];
- port = Integer.parseInt(args[1]);
- uri = args[2];
- }
-
- System.out.println("\r\naddr=" + addr);
- System.out.println("port=" + port);
- System.out.println("uri=" + uri);
-
- // 加载国密提供者
- // Security.insertProviderAt(new BouncyCastleProvider(), 1);
- // Security.insertProviderAt(new BouncyCastleJsseProvider(), 2);
- Security.insertProviderAt(new KonaProvider(), 1);
-
- fact = createSocketFactory(null, null);
- socket = (SSLSocket) fact.createSocket();
- socket.setTcpNoDelay(true);
-
- System.out.println("\r\nGM SSL connecting...");
- socket.connect(new InetSocketAddress(addr, port), 5000);
- socket.setTcpNoDelay(true);
- socket.startHandshake();
-
- System.out.println("Connected!\n");
-
- DataInputStream in = new DataInputStream(socket.getInputStream());
- OutputStream out = socket.getOutputStream();
-
- String s = "GET " + uri + " HTTP/1.1\r\n";
- s += "Accept: */*\r\n";
- s += "User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)\r\n";
- s += "Host: " + addr + (port == 443 ? "" : ":" + port) + "\r\n";
- s += "Connection: Close\r\n";
- s += "\r\n";
- out.write(s.getBytes());
- out.flush();
-
- // 读取HTTP头
- while (true) {
- byte[] lineBuffer = ReadLine.read(in);
- if (lineBuffer == null || lineBuffer.length == 0) {
- System.out.println();
- break;
- }
- String line = new String(lineBuffer);
- System.out.println(line);
- }
-
- // 读取HTTP内容
- {
- byte[] buf = new byte[1024];
- while (true) {
- int len = in.read(buf);
- if (len == -1) {
- break;
- }
- System.out.println(new String(buf, 0, len));
- }
- }
-
- in.close();
- out.close();
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- socket.close();
- } catch (Exception e) {
- }
- }
- }
-
- private static SSLSocketFactory createSocketFactory(KeyStore kepair, char[] pwd) throws Exception {
- X509TrustManager[] trust = {new MyTrustAllManager()};
-
- KeyManager[] kms = null;
- if (kepair != null) {
- KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
- kmf.init(kepair, pwd);
- kms = kmf.getKeyManagers();
- }
-
- // 使用国密SSL
- // String protocol = "TLSV1.1";
- String protocol = "TLCPv1.1";
- String provider = KonaProvider.NAME;
- // String provider = BouncyCastleJsseProvider.PROVIDER_NAME;
- SSLContext ctx = SSLContext.getInstance(protocol, provider);
-
- java.security.SecureRandom secureRandom = new java.security.SecureRandom();
- ctx.init(kms, trust, secureRandom);
-
- SSLSocketFactory factory = ctx.getSocketFactory();
- return factory;
- }
-
-
- }
-
- package org.example;
-
- import java.io.*;
- import java.net.SocketException;
-
- class ReadLine {
- public static final byte[] CRLF = {'\r', '\n'};
- public static final byte CR = '\r';
- public static final byte LF = '\n';
-
- private static final int LINE_MAX_SIZE = 16384;
-
- public static byte[] read(DataInputStream in) throws IOException, SocketException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- DataOutputStream s = new DataOutputStream(baos);
- boolean previousIsCR = false;
-
- int len = 0;
- byte b = 0;
-
- try {
- b = in.readByte();
- len++;
- } catch (EOFException e) {
- return new byte[0];
- }
-
- while (true) {
- if (b == LF) {
- if (previousIsCR) {
- s.flush();
- byte[] rs = baos.toByteArray();
- s.close();
- return rs;
- } else {
- s.flush();
- byte[] rs = baos.toByteArray();
- s.close();
- return rs;
- }
- } else if (b == CR) {
- if (previousIsCR) {
- s.writeByte(CR);
- }
- previousIsCR = true;
- } else {
- if (previousIsCR) {
- s.writeByte(CR);
- }
- previousIsCR = false;
- s.write(b);
- }
-
- if (len > LINE_MAX_SIZE) {
- s.close();
- throw new IOException("Reach line size limit");
- }
-
- try {
- b = in.readByte();
- len++;
- } catch (EOFException e) {
- s.flush();
- byte[] rs = baos.toByteArray();
- s.close();
- return rs;
- }
- }
- }
- }
-
-
- package org.example;
-
- import javax.net.ssl.X509TrustManager;
- import java.security.cert.X509Certificate;
-
- class MyTrustAllManager implements X509TrustManager
- {
- private X509Certificate[] issuers;
-
- public MyTrustAllManager()
- {
- this.issuers = new X509Certificate[0];
- }
-
- public X509Certificate[] getAcceptedIssuers()
- {
- return issuers ;
- }
-
- public void checkClientTrusted(X509Certificate[] chain, String authType)
- {}
-
- public void checkServerTrusted(X509Certificate[] chain, String authType)
- {}
- }
实际上就是2步:
1. 注册国密
Security.insertProviderAt(new KonaProvider(), 1);
2. 使用国密sslcontext
SSLContext ctx = SSLContext.getInstance(protocol, provider);
实际上这步是核心,BouncyCastle不支持国密就是因为国密protocol在BouncyCastle没有定义
访问后如下:
- addr=ebssec.boc.cn
- port=443
- uri=/
-
- GM SSL connecting...
- Connected!
-
- HTTP/1.1 200 OK
- Date: Sat, 21 Oct 2023 11:55:15 GMT
- Last-Modified: Sat, 27 Jun 2015 16:48:38 GMT
- Accept-Ranges: bytes
- Content-Length: 156
- Cache-Control: max-age=300
- Expires: Sat, 21 Oct 2023 12:00:15 GMT
- Vary: Accept-Encoding,User-Agent
- Content-Type: text/html
- Connection:close
-
- "refresh" content="0;url=/boc15/login.html">"renderer" content="ie-stand">
协议支持,通过注册provider

然后com.tencent.kona.sun.security.ssl.CipherSuite定义了TLCP的加密套件,所以可以匹配国密,当然BouncyCastle的tls也可以在上面支持,不过这个就跟kona区别不大,毕竟开始kona也是基于BouncyCastle开发的
有了加密套件,那么按照jce和jsse的模式进行ssl握手,然后加密传输数据。
实际上就是那2行代码,再核心一点就是国密算法和加密套件的注册和使用
- public class HttpClientDemo {
- public static void main(String[] args) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, KeyManagementException {
- Security.insertProviderAt(new KonaProvider(), 1);
-
- SSLContext sslContext = SSLContext.getInstance("TLCPv1.1", KonaProvider.NAME);
- sslContext.init(null, new TrustManager[] {new MyTrustAllManager()}, new SecureRandom());
- CloseableHttpClient client = HttpClients.custom().setSSLContext(sslContext).build();
- HttpGet get = new HttpGet("https://ebssec.boc.cn");
- CloseableHttpResponse response = client.execute(get);
- System.out.println(response.getStatusLine().toString());
- Header[] headers = response.getAllHeaders();
- for(Header header : headers ){
- System.out.println(header.getName() +" : "+header.getName());
- }
- HttpEntity e = response.getEntity();
- System.out.println(EntityUtils.toString(e,"UTF-8"));
- }
- }
运行后,结果如下
- HTTP/1.1 200 OK
- Date : Date
- Last-Modified : Last-Modified
- Accept-Ranges : Accept-Ranges
- Cache-Control : Cache-Control
- Expires : Expires
- Vary : Vary
- Keep-Alive : Keep-Alive
- Connection : Connection
- Content-Type : Content-Type
- "refresh" content="0;url=/boc15/login.html">"renderer" content="ie-stand">
相当于把刚刚,那一系列的代码用Apache的jar实现了,开源方便👍🏻
国密ssl的密钥交换过程和tls1.2基本上一致

通过抓包分析,得出结论:

基于这个结论,那么可以在bctls实现国密的ssl,不过因为Knoa早期版本就是基于这个,没必要重复造轮子,可以参考kona早期版本。
国密SSL实际上就是TLS1.1和TLS1.2的结合体,一般而言使用TLS1.1即可,协议版本定义0x0101,而且ssl握手需要双证书认证:握手中发送签名证书、加密证书,一般而言签名证书先发,后发加密证书。签名证书仅用于验证身份,私钥自己生成,由CA机构签发;加密证书用于数据加密,CA机构生成并持有私钥。国密SSL实际上跟普通SSL区别不大,核心是使用国密算法密码套件。