背景
Java自身通过JCE和JSSE支持标准的SSL协议,但并不支持国密SSL协议。本文描述了Java使用国密JCE和国密JSSE开发一个简单的客户端程序,连接国密Web网站,发送HTTP请求,并接收HTTP应答。
环境
JRE是jre8。
国密JCE和国密JSSE。下载参
https://www.gmssl.cn/gmssl/index.jsp?go=gmsdk
gmjce.jar和gmjsse.jar放到jre的lib/ext/目录下
源码
package cn.gmssl.test;
import java.net.*;
import java.io.*;
import java.security.*;
import java.security.cert.*;
import javax.net.*;
import javax.net.ssl.*;
public class SocketGet
{
public static void main(String[] args)
{
SocketFactory fact = null;
SSLSocket socket = null;
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 cn.gmssl.jce.provider.GMJCE(), 1);
Security.insertProviderAt(new cn.gmssl.jsse.provider.GMJSSE(), 2);
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 = cn.gmssl.jsse.provider.GMJSSE.GMSSLv11;
String provider = cn.gmssl.jsse.provider.GMJSSE.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;
}
}
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)
{}
}
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;
}
}
}
}
注释
首先要注册国密提供者
Security.insertProviderAt(new cn.gmssl.jce.provider.GMJCE(), 1);
Security.insertProviderAt(new cn.gmssl.jsse.provider.GMJSSE(), 2);
其中要使用国密SSL来连接
String protocol = cn.gmssl.jsse.provider.GMJSSE.GMSSLv11;
String provider = cn.gmssl.jsse.provider.GMJSSE.NAME;
SSLContext ctx = SSLContext.getInstance(protocol, provider);
是不是比想象中要简单?
测试运行
>java cn.gmssl.test.SocketGet
addr=ebssec.boc.cn
port=443
uri=/
GM SSL connecting...
Connected!
HTTP/1.1 200 OK
Date: Mon, 24 Aug 2020 03:45:28 GMT
Last-Modified: Sat, 27 Jun 2015 16:48:38 GMT
Accept-Ranges: bytes
Content-Length: 156
Cache-Control: max-age=300
Expires: Mon, 24 Aug 2020 03:50:28 GMT
Vary: Accept-Encoding,User-Agent
Connection: close
Content-Type: text/html
...
小结
通过使用国密JCE和国密JSSE,Java很容易编程来使用国密SSL连接国密Web网站。www.gmssl.cn提供了全部免费的测试组件,并且支持双向国密SSL,可供学习和测试。