Java自身經過JCE和JSSE支持標準的SSL協議,但並不支持國密SSL協議。本文描述了Java使用國密JCE和國密JSSE開發一個簡單的客戶端程序,鏈接國密Web網站,發送HTTP請求,並接收HTTP應答。html
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; } } } }
首先要註冊國密提供者java
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);
是否是比想象中要簡單?dom
>java cn.gmssl.test.SocketGetsocket
addr=ebssec.boc.cn
port=443
uri=/jsp
GM SSL connecting...
Connected!ide
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,可供學習和測試。測試