主页 > 手机  > 

国密https访问

国密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加密解决方案演示网站 sm2test.ovssl /

和 中国银行官网 ebssec.boc /

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

 

BouncyCastle

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

 

截止当前的日期

 

不支持国密源码分析

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

<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk18on</artifactId> <version>1.76</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bctls-jdk18on</artifactId> <version>1.76</version> </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

<dependency> <groupId>com.tencent.kona</groupId> <artifactId>kona-provider</artifactId> <version>${kona.version}</version> </dependency> <dependency> <groupId>com.tencent.kona</groupId> <artifactId>kona-crypto</artifactId> <version>${kona.version}</version> </dependency> <dependency> <groupId>com.tencent.kona</groupId> <artifactId>kona-ssl</artifactId> <version>${kona.version}</version> </dependency> <dependency> <groupId>com.tencent.kona</groupId> <artifactId>kona-pkix</artifactId> <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 "; String addr = "ebssec.boc "; 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 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 <!DOCTYPE html><html><head><meta http-equiv="refresh" content="0;url=/boc15/login.html"><meta name="renderer" content="ie-stand"></head><body></body></html> 简单源码分析

协议支持,通过注册provider

 

然后com.tencent.kona.sun.security.ssl.CipherSuite定义了TLCP的加密套件,所以可以匹配国密,当然BouncyCastle的tls也可以在上面支持,不过这个就跟kona区别不大,毕竟开始kona也是基于BouncyCastle开发的

有了加密套件,那么按照jce和jsse的模式进行ssl握手,然后加密传输数据。

httpclient

实际上就是那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(" ebssec.boc "); 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 <!DOCTYPE html><html><head><meta http-equiv="refresh" content="0;url=/boc15/login.html"><meta name="renderer" content="ie-stand"></head><body></body></html>

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

总结

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

标签:

国密https访问由讯客互联手机栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“国密https访问