• 国密https访问


    前言

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

    那么如果需要国密证书,或者访问国密https的时候就需要特定的sslsocket的握手算法

    传输层密码协议(TLCP)

    TLCP是中国基于TLS 1.1和1.2协议定制而成的协议,对应的中国国家标准为:

    • GB/T 38636-2020:传输层密码协议

    该协议与TLS协议的最重大区别,就是要求通信端提供两个证书:认证证书和加密证书。其中认证证书与TLS协议使用的证书功能类似,用于对通信端的身份进行验证。而加密证书则为TLCP协议独有,它只会用于密钥交换。

    开源套件

    常用的国密开源套件有BouncyCastle、Kona,BouncyCastle时间比较久远了,用的比较多,比如OOM的坑,Kona是腾讯开源的国密套件,开始基于BouncyCastle,后面重写了,分别使用者两个套件试试沃通搭建的SM2SSL证书测试网站欢迎访问沃通基于国密算法的https加密解决方案演示网站icon-default.png?t=N7T8https://sm2test.ovssl.cn/

    和 中国银行官网icon-default.png?t=N7T8https://ebssec.boc.cn/

    其中沃通是支持国密和国际算法的无缝切换的,因为有双证书

    BouncyCastle

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

    截止当前的日期

    不支持国密源码分析

    bouncycastle的ssl支持org.bouncycastle.jsse.provider.BouncyCastleJsseProvider

    1. <dependency>
    2. <groupId>org.bouncycastlegroupId>
    3. <artifactId>bcprov-jdk18onartifactId>
    4. <version>1.76version>
    5. dependency>
    6. <dependency>
    7. <groupId>org.bouncycastlegroupId>
    8. <artifactId>bctls-jdk18onartifactId>
    9. <version>1.76version>
    10. 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差别很大 

    Kona

    企鹅开源的,在1.0.5版本不再基于BouncyCastle

    腾讯Kona国密套件使用的许可协议是GNU GPL v2.0 license with Classpath Exception,这也正是OpenJDK使用的许可协议。

    腾讯Kona国密套件包含四个Java Security Provider

    • KonaCrypto,它是一个Java Cryptography Extension(JCE)实现,遵循标准的Java Cryptography Architecture(JCA)框架实现了国密基础算法SM2,SM3和SM4。
    • KonaPKIX,它实现了国密证书的解析及其证书链验证,并可加载和创建包含国密证书的密钥库(Key Store)文件。
    • KonaSSL,它实现了中国的传输层密码协议(TLCP),并遵循RFC 8998规范将国密基础算法应用到了TLS 1.3协议中。
    • Kona,它没有直接实现任何功能,而是将KonaCrypto,KonaPKIX和KonaSSL中的特性进行了简单的封装,以方便用户仅使用这一个Provider就能够调用上述三个Provider中的全部功能。一般地,建议使用这个Provider。

    --来源于腾讯Kona国密套件:从基础算法到安全协议-腾讯云开发者社区-腾讯云

    socket支持

    以kona为例,实际上BouncyCastle也有TLS模块,但是貌似没做国密的协议簇,只能使用国际标准,这里使用不知道初始原作者是谁的gmssl的demo代码,引入pom

    1. <dependency>
    2. <groupId>com.tencent.konagroupId>
    3. <artifactId>kona-providerartifactId>
    4. <version>${kona.version}version>
    5. dependency>
    6. <dependency>
    7. <groupId>com.tencent.konagroupId>
    8. <artifactId>kona-cryptoartifactId>
    9. <version>${kona.version}version>
    10. dependency>
    11. <dependency>
    12. <groupId>com.tencent.konagroupId>
    13. <artifactId>kona-sslartifactId>
    14. <version>${kona.version}version>
    15. dependency>
    16. <dependency>
    17. <groupId>com.tencent.konagroupId>
    18. <artifactId>kona-pkixartifactId>
    19. <version>${kona.version}version>
    20. dependency>

    然后通过socket方式支持国密https,以中国银行官网为测试

    1. package org.example;
    2. import com.tencent.kona.KonaProvider;
    3. import org.bouncycastle.jce.provider.BouncyCastleProvider;
    4. import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
    5. import java.net.*;
    6. import java.io.*;
    7. import java.security.*;
    8. import javax.net.*;
    9. import javax.net.ssl.*;
    10. public class SocketGet {
    11. public static void main(String[] args) {
    12. SocketFactory fact = null;
    13. SSLSocket socket = null;
    14. // String addr = "sm2test.ovssl.cn";
    15. String addr = "ebssec.boc.cn";
    16. int port = 443;
    17. String uri = "/";
    18. try {
    19. if (args.length > 0) {
    20. addr = args[0];
    21. port = Integer.parseInt(args[1]);
    22. uri = args[2];
    23. }
    24. System.out.println("\r\naddr=" + addr);
    25. System.out.println("port=" + port);
    26. System.out.println("uri=" + uri);
    27. // 加载国密提供者
    28. // Security.insertProviderAt(new BouncyCastleProvider(), 1);
    29. // Security.insertProviderAt(new BouncyCastleJsseProvider(), 2);
    30. Security.insertProviderAt(new KonaProvider(), 1);
    31. fact = createSocketFactory(null, null);
    32. socket = (SSLSocket) fact.createSocket();
    33. socket.setTcpNoDelay(true);
    34. System.out.println("\r\nGM SSL connecting...");
    35. socket.connect(new InetSocketAddress(addr, port), 5000);
    36. socket.setTcpNoDelay(true);
    37. socket.startHandshake();
    38. System.out.println("Connected!\n");
    39. DataInputStream in = new DataInputStream(socket.getInputStream());
    40. OutputStream out = socket.getOutputStream();
    41. String s = "GET " + uri + " HTTP/1.1\r\n";
    42. s += "Accept: */*\r\n";
    43. s += "User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)\r\n";
    44. s += "Host: " + addr + (port == 443 ? "" : ":" + port) + "\r\n";
    45. s += "Connection: Close\r\n";
    46. s += "\r\n";
    47. out.write(s.getBytes());
    48. out.flush();
    49. // 读取HTTP头
    50. while (true) {
    51. byte[] lineBuffer = ReadLine.read(in);
    52. if (lineBuffer == null || lineBuffer.length == 0) {
    53. System.out.println();
    54. break;
    55. }
    56. String line = new String(lineBuffer);
    57. System.out.println(line);
    58. }
    59. // 读取HTTP内容
    60. {
    61. byte[] buf = new byte[1024];
    62. while (true) {
    63. int len = in.read(buf);
    64. if (len == -1) {
    65. break;
    66. }
    67. System.out.println(new String(buf, 0, len));
    68. }
    69. }
    70. in.close();
    71. out.close();
    72. } catch (Exception e) {
    73. e.printStackTrace();
    74. } finally {
    75. try {
    76. socket.close();
    77. } catch (Exception e) {
    78. }
    79. }
    80. }
    81. private static SSLSocketFactory createSocketFactory(KeyStore kepair, char[] pwd) throws Exception {
    82. X509TrustManager[] trust = {new MyTrustAllManager()};
    83. KeyManager[] kms = null;
    84. if (kepair != null) {
    85. KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
    86. kmf.init(kepair, pwd);
    87. kms = kmf.getKeyManagers();
    88. }
    89. // 使用国密SSL
    90. // String protocol = "TLSV1.1";
    91. String protocol = "TLCPv1.1";
    92. String provider = KonaProvider.NAME;
    93. // String provider = BouncyCastleJsseProvider.PROVIDER_NAME;
    94. SSLContext ctx = SSLContext.getInstance(protocol, provider);
    95. java.security.SecureRandom secureRandom = new java.security.SecureRandom();
    96. ctx.init(kms, trust, secureRandom);
    97. SSLSocketFactory factory = ctx.getSocketFactory();
    98. return factory;
    99. }
    100. }
    101. package org.example;
    102. import java.io.*;
    103. import java.net.SocketException;
    104. class ReadLine {
    105. public static final byte[] CRLF = {'\r', '\n'};
    106. public static final byte CR = '\r';
    107. public static final byte LF = '\n';
    108. private static final int LINE_MAX_SIZE = 16384;
    109. public static byte[] read(DataInputStream in) throws IOException, SocketException {
    110. ByteArrayOutputStream baos = new ByteArrayOutputStream();
    111. DataOutputStream s = new DataOutputStream(baos);
    112. boolean previousIsCR = false;
    113. int len = 0;
    114. byte b = 0;
    115. try {
    116. b = in.readByte();
    117. len++;
    118. } catch (EOFException e) {
    119. return new byte[0];
    120. }
    121. while (true) {
    122. if (b == LF) {
    123. if (previousIsCR) {
    124. s.flush();
    125. byte[] rs = baos.toByteArray();
    126. s.close();
    127. return rs;
    128. } else {
    129. s.flush();
    130. byte[] rs = baos.toByteArray();
    131. s.close();
    132. return rs;
    133. }
    134. } else if (b == CR) {
    135. if (previousIsCR) {
    136. s.writeByte(CR);
    137. }
    138. previousIsCR = true;
    139. } else {
    140. if (previousIsCR) {
    141. s.writeByte(CR);
    142. }
    143. previousIsCR = false;
    144. s.write(b);
    145. }
    146. if (len > LINE_MAX_SIZE) {
    147. s.close();
    148. throw new IOException("Reach line size limit");
    149. }
    150. try {
    151. b = in.readByte();
    152. len++;
    153. } catch (EOFException e) {
    154. s.flush();
    155. byte[] rs = baos.toByteArray();
    156. s.close();
    157. return rs;
    158. }
    159. }
    160. }
    161. }
    162. package org.example;
    163. import javax.net.ssl.X509TrustManager;
    164. import java.security.cert.X509Certificate;
    165. class MyTrustAllManager implements X509TrustManager
    166. {
    167. private X509Certificate[] issuers;
    168. public MyTrustAllManager()
    169. {
    170. this.issuers = new X509Certificate[0];
    171. }
    172. public X509Certificate[] getAcceptedIssuers()
    173. {
    174. return issuers ;
    175. }
    176. public void checkClientTrusted(X509Certificate[] chain, String authType)
    177. {}
    178. public void checkServerTrusted(X509Certificate[] chain, String authType)
    179. {}
    180. }

    核心步骤

    实际上就是2步:

    1. 注册国密

    Security.insertProviderAt(new KonaProvider(), 1);

    2. 使用国密sslcontext

    SSLContext ctx = SSLContext.getInstance(protocol, provider);

    实际上这步是核心,BouncyCastle不支持国密就是因为国密protocol在BouncyCastle没有定义

    访问后如下:

    1. addr=ebssec.boc.cn
    2. port=443
    3. uri=/
    4. GM SSL connecting...
    5. Connected!
    6. HTTP/1.1 200 OK
    7. Date: Sat, 21 Oct 2023 11:55:15 GMT
    8. Last-Modified: Sat, 27 Jun 2015 16:48:38 GMT
    9. Accept-Ranges: bytes
    10. Content-Length: 156
    11. Cache-Control: max-age=300
    12. Expires: Sat, 21 Oct 2023 12:00:15 GMT
    13. Vary: Accept-Encoding,User-Agent
    14. Content-Type: text/html
    15. Connection:close
    16. "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握手,然后加密传输数据。

    httpclient

    实际上就是那2行代码,再核心一点就是国密算法和加密套件的注册和使用 

    1. public class HttpClientDemo {
    2. public static void main(String[] args) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, KeyManagementException {
    3. Security.insertProviderAt(new KonaProvider(), 1);
    4. SSLContext sslContext = SSLContext.getInstance("TLCPv1.1", KonaProvider.NAME);
    5. sslContext.init(null, new TrustManager[] {new MyTrustAllManager()}, new SecureRandom());
    6. CloseableHttpClient client = HttpClients.custom().setSSLContext(sslContext).build();
    7. HttpGet get = new HttpGet("https://ebssec.boc.cn");
    8. CloseableHttpResponse response = client.execute(get);
    9. System.out.println(response.getStatusLine().toString());
    10. Header[] headers = response.getAllHeaders();
    11. for(Header header : headers ){
    12. System.out.println(header.getName() +" : "+header.getName());
    13. }
    14. HttpEntity e = response.getEntity();
    15. System.out.println(EntityUtils.toString(e,"UTF-8"));
    16. }
    17. }

    运行后,结果如下

    1. HTTP/1.1 200 OK
    2. Date : Date
    3. Last-Modified : Last-Modified
    4. Accept-Ranges : Accept-Ranges
    5. Cache-Control : Cache-Control
    6. Expires : Expires
    7. Vary : Vary
    8. Keep-Alive : Keep-Alive
    9. Connection : Connection
    10. Content-Type : Content-Type
    11. "refresh" content="0;url=/boc15/login.html">"renderer" content="ie-stand">

     相当于把刚刚,那一系列的代码用Apache的jar实现了,开源方便👍🏻

    抓包分析

    国密ssl的密钥交换过程和tls1.2基本上一致

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

    • 国密SSL的协议版本0x101,奇特的协议版本号,ssl/tls一般都是300/301/302/303
    • 支持的加密套件需要国密算法SM2(签名和密钥交换,类似RSA)、SM4(对称)和SM3(hash)
    • 国密ssl等同于tls1.2逻辑,那么需要安装tls1.2设计

    基于这个结论,那么可以在bctls实现国密的ssl,不过因为Knoa早期版本就是基于这个,没必要重复造轮子,可以参考kona早期版本。

    总结

    国密SSL实际上就是TLS1.1和TLS1.2的结合体,一般而言使用TLS1.1即可,协议版本定义0x0101,而且ssl握手需要双证书认证:握手中发送签名证书、加密证书,一般而言签名证书先发,后发加密证书。签名证书仅用于验证身份,私钥自己生成,由CA机构签发;加密证书用于数据加密,CA机构生成并持有私钥。国密SSL实际上跟普通SSL区别不大,核心是使用国密算法密码套件。

  • 相关阅读:
    ActiveMQ安装
    无线投屏冷知识
    TrafficWatch 数据包嗅探器工具
    您遇到过网页抓取时被封IP的情况吗?
    Python标准库之copy
    最新MySql8.27主从复制以及SpringBoot项目中的读写分离实战
    浏览器输入URL 到页面加载过程?
    docker部署frp穿透内网
    Selenium的安装
    vue-cli、create-react-app等常用工具安装、更新、查看版本等操作命令汇总
  • 原文地址:https://blog.csdn.net/fenglllle/article/details/133961707