`
iiaf
  • 浏览: 50497 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

用OpenSSL与JAVA(JSSE)通信

    博客分类:
  • java
阅读更多
概念
JAVA使用keystore文件来存储所有KEY,keystore文件可以存放多个KEY,访问它需要密码。
下面我介绍下如何将用OpenSSL做自签名的证书一文中介绍的OpenSSL产生的KEY与JAVA的KEY转换后使用,从而达到JAVA与OpenSSL通信的目的。


用OpenSSL生成CA根证书,即(P1,V1)
此步骤参见用OpenSSL做自签名的证书一文


在JAVA环境下生成自己的KEY,即(P2,V2)

keytool -genkey -alias clientapp -keystore mycerts

注意:这里会提示输入访问keystore的密码,以及组织、城市、省份,一定要与CA根证书的一致,否则不能被CA签名。


从keystore中导出public key,即P2

keytool -keystore mycerts -certreq -alias clientapp -file clientapp.crs


用CA根证书为这个public key进行签名,即用V1给P2加密

openssl ca -out clientapp.pem -config ./openssl.cnf -infiles clientapp.crs


转换PEM到DER格式

openssl x509 -in clientapp.pem -out clientapp.der -outform DER


导入CA证书,即P1

keytool -keystore mycerts -alias systemca -import -file cacert.pem


导入用户证书,即被V1加密过的P2

keytool -keystore mycerts -alias clientapp -import -file clientapp.der

注意:这里一定要先导入CA证书再导入用户证书,否则会报错。

现在我们就生成了JAVA服务器使用的所有KEY了,在程序中将mycerts这个keystore导入就可以了。
如果客户端是使用OpenSSL的程序,那么用CA证书cacert.pem就能正常通信了,如果也是JAVA程序,那么我们需要将CA证书也转换成keystore:

keytool -import -keystore clikeystore -import -trustcacerts -file cacert.pem

生成的clikeystore供JAVA客户端使用,就能通信。


再附上SVR和CLI的JAVA程序,我已经用上面的KEY都测试通过:
SVR端:
import  java.io. * ;
import  java.net. * ;
import  com.sun.net.ssl.KeyManagerFactory;
import  com.sun.net.ssl.KeyManager;
import  com.sun.net.ssl.TrustManagerFactory;
import  com.sun.net.ssl.TrustManager;
import  com.sun.net.ssl.SSLContext;
import  javax.net.ServerSocketFactory;
import  java.security.KeyStore;

public   class  svr  implements  Runnable {
 
   public   static   final   int  PORT  =   5555 ;
   public   static   final  String HOST  =   " localhost " ;
   public   static   final  String QUESTION  =   " Knock, knock. " ;
   public   static   final  String ANSWER  =   " Who's there? " ;

   //  The new constants that are used during setup.
   public   static   final  String KEYSTORE_FILE  =   " mycerts " ; // "server_keystore";
   public   static   final  String ALGORITHM  =   " sunx509 " ;
   public   static   final  String PASSWORD  =   " churchillobjects " ;
 
   public   static   void  main(String[] args) {
     new  Thread( new  svr()).start();
  }
 
   public   void  run() {
    ServerSocket ss  =   null ;
     try   {

       //  Local references used for clarity. Their presence
       //  here is part of the reason we need to import
       //  so many classes.
      KeyManagerFactory kmf;
      KeyManager[] km;
      KeyStore ks;
      TrustManagerFactory tmf;
      TrustManager[] tm;
      SSLContext sslc;
     
       //  Create a keystore that will read the JKS (Java KeyStore)
       //  file format which was created by the keytool utility.
      ks  =  KeyStore.getInstance( " JKS " );
     
       //  Load the keystore object with the binary keystore file and
       //  a byte array representing its password.
      ks.load( new  FileInputStream(KEYSTORE_FILE), PASSWORD.toCharArray());
     
       //  Gives us a factory for key managers that will let
       //  us handle the asymetric keys we created earlier.
      kmf  =  KeyManagerFactory.getInstance(ALGORITHM);

       //  Initialize the key manager factory with the keystore object,
       //  again using the same password for security since it is going to
       //  access the private key.
      kmf.init(ks, PASSWORD.toCharArray());
     
       //  Now we can get the key managers from the factory, since it knows
       //  what type we are using now.
      km  =  kmf.getKeyManagers();
     
       //  Next, create a trust manager factory using the same algorithm.
       //  This is to avoid using the certificates in cacerts that
       //  represent an authentication security risk.
      tmf  =  TrustManagerFactory.getInstance(ALGORITHM);
     
       //  then initialize it with the keystore object. This time we don't
       //  need the keystore password. This is because trusted certificates
       //  are not a sensitive element in the keystore, unlike the
       //  private keys.
      tmf.init(ks);
     
       //  Once that's initialized, get the trust managers from the factory.
      tm  =  tmf.getTrustManagers();
     
       //  Almost done, we need a context object that will get our
       //  server socket factory. We specify TLS to indicate that we will
       //  need a server socket factory that supports SSL.
      sslc  =  SSLContext.getInstance( " TLS " );
     
       //  Initialize the context object with the key managers and trust
       //  managers we got earlier. The third parameter is an optional
       //  SecureRandom object. By passing in null, we are letting the
       //  context object create its own.
      sslc.init(km, tm,  null );
     
       //  Finally, we get the ordinary-looking server socket factory
       //  from the context object.
      ServerSocketFactory ssf  =  sslc.getServerSocketFactory();
     
       //  From the factory, we simply ask for an ordinary-looking
       //  server socket on the port we wish.
      ss  =  ssf.createServerSocket(PORT);

      listen(ss);
    }
     catch (Exception e) {
      e.printStackTrace();
    }
     finally {
       if (ss != null ) {
         try {
          ss.close();
        }
         catch (IOException e) {
           //  oh, well
        }
      }
      System.exit( 0 );
    }
  }
 
   static   void  listen(ServerSocket ss)  throws  Exception {
    System.out.println( " Ready for connections. " );
     while ( true ) {
      Socket s  =  ss.accept();
      BufferedWriter bw  =   new  BufferedWriter(
         new  OutputStreamWriter(s.getOutputStream()));
      BufferedReader br  =   new  BufferedReader(
         new  InputStreamReader(s.getInputStream()));
      String q  =  br.readLine();
       if ( ! QUESTION.equals(q)) {
         throw   new  RuntimeException( " Wrong question: \ ""  + q +  " \ "" );
      }
      System.out.println( " Question: \ ""  + q +  " \ "" );
      bw.write(ANSWER + " \n " );
      bw.flush();
      s.close();
    }
  }
}


CLI端程序:
import  java.io. * ;
import  java.net. * ;
import  com.sun.net.ssl.KeyManagerFactory;
import  com.sun.net.ssl.TrustManagerFactory;
import  com.sun.net.ssl.SSLContext;
import  java.security.KeyStore;
import  javax.net.SocketFactory;

public   class  cli  implements  Runnable {
 
   public   static   final   int  PORT  =   5555 ;
   public   static   final  String HOST  =   " localhost " ;
   public   static   final  String KEYSTORE_FILE  =   " clikeystore " ; // "client_keystore";
   public   static   final  String ALGORITHM  =   " sunx509 " ;
   public   static   final  String PASSWORD  =   " churchillobjects " ;
   public   static   final  String QUESTION  =   " Knock, knock. " ;
   public   static   final  String ANSWER  =   " Who's there? " ;
 
   public   static   void  main(String[] args) {
     new  Thread( new  cli()).start();
  }
 
   public   void  run() {
    Socket socket  =   null ;
     try {
      KeyManagerFactory kmf;
      KeyStore ks;
      TrustManagerFactory tmf;
      SSLContext sslc;

      kmf  =  KeyManagerFactory.getInstance(ALGORITHM);
      ks  =  KeyStore.getInstance(  " JKS "  );
      ks.load( new  FileInputStream(KEYSTORE_FILE), PASSWORD.toCharArray());
      kmf.init(ks, PASSWORD.toCharArray());
      tmf  =  TrustManagerFactory.getInstance(ALGORITHM);
      tmf.init(ks);
      sslc  =  SSLContext.getInstance( " TLS " );
      sslc.init(kmf.getKeyManagers(), tmf.getTrustManagers(),  null );

       //  The process is different from here on the client. Instead of
       //  getting a ServerSocketFactory, we ask for a SocketFactory from
       //  the SSL context.
      SocketFactory sf  =  sslc.getSocketFactory();

       //  Then we get the socket from the factory and treat it
       //  as if it were a standard (plain) socket.
      socket  =  sf.createSocket(HOST, PORT);
   
      doQuery(socket);
    }
     catch (Exception e) {
      e.printStackTrace();
    }
     finally {
       if (socket != null ) {
         try {
          socket.close();
        }
         catch (IOException e) {
           //  oh, well
        }
      }
      System.exit( 0 );
    }
  }

   private   void  doQuery(Socket s)  throws  Exception {
    BufferedWriter bw  =   new  BufferedWriter( new  OutputStreamWriter(s.getOutputStream()));
    BufferedReader br  =   new  BufferedReader( new  InputStreamReader(s.getInputStream()));
    bw.write(QUESTION + " \n " );
    bw.flush();
    String response  =  br.readLine();
     if ( ! ANSWER.equals(response)) {
       throw   new  RuntimeException( " Wrong answer: \ ""  + response +  " \ "" );
    }
    System.out.println( " Got the right answer: \ ""  + response +  " \ "" );
  }
}




以上方法主要参考了如下两个网页:

http://www.churchillobjects.com/c/11201g.html

http://mark.foster.cc/kb/openssl-keytool.html
posted on 2006-12-03 12:36 我爱佳娃 阅读(3442) 评论(6)  编辑  收藏 所属分类: SSL

评论:
# re: 用OpenSSL与JAVA(JSSE)通信 2008-05-15 21:03 | 不不
你好,你在另一片文章里这样写的:

公私钥:公钥可以唯一解密私钥加密过的数据,反之亦然。
SSL过程:需要两对公私钥(P1,V1),(P2,V2),假设通信双方是A和B,B是服务器,A要确认和它通信的是B:
A->B: hello
B->A: 用V2加密过的P1(即用户证书,A就用P2解密出P1)
A->B: ok
B->A: 用V1加密的一段信息
A->B: 用P1加密一个自动生成的K(用之前的P1解密成功这段信息则认为B是可信的了)
B->A: 用K加密的数据(之后两对密钥功能结束,由K来加解密数据)
这里,P2就是第3方的CA证书,由于非对称加密很慢,所以公私钥只是用来保证K的传送安全,之后通信是用K的对称加密算法来保证。

为什么这里是P2是CA证书,而上面的例子里变成了P1是CA证书?我刚接触SSL不太久,所以不是很懂。抱歉把你06年的东西都搬出来了!  回复  更多评论
 
# re: 用OpenSSL与JAVA(JSSE)通信 2008-05-16 18:02 | 我爱佳娃
上文描述的是:
用OpenSSL生成CA根证书,即(P1,V1)
在JAVA环境下生成自己的KEY,即(P2,V2)

这里,P1,V1指代的是CA根证书,所以P1是CA证书,V1是签名公司需要保密,专门用来给别人签字的私钥。

而,我的另一篇文章提到:
B->A: 用V2加密过的P1(即用户证书,A就用P2解密出P1)

这里,V2相当于CA公司的私钥,相当于上边的V1,P2自然就是CA证书了。也即,这两篇文章在变量指代上正好反了,但内容应该都没有问题。

  回复  更多评论
 
# re: 用OpenSSL与JAVA(JSSE)通信 2008-11-20 11:15 | llp20_2000
@我爱佳娃
我爱娃娃,按照上文,用OPENSSL的私钥 ./private/cakey.pem 为javatool的公钥签名请求"clientapp.crs"做签名的时候,也就是执行语句:
openssl ca -out clientapp.pem -config ./openssl.cnf -infiles clientapp.crs
结果如下:
**************************************************
[~~~~ CA]$ openssl ca -out clientapp.pem -config ./openssl.cnf -infiles clientapp.crs
Using configuration from ./openssl.cnf
Enter pass phrase for ./private/cakey.pem:
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName :ASN.1 12:'liangleping'
organizationName :ASN.1 12:'HuaWei'
organizationalUnitName:ASN.1 12:'Develop'
localityName :ASN.1 12:'Cheng Du'
stateOrProvinceName :ASN.1 12:'Si Chuan'
countryName :ASN.1 12:'ch'
The countryName field needed to be the same in the
CA certificate (ch) and the request (ch)
************************************************
最后出现:The countryName field needed to be the same in the
  回复  更多评论
 
# re: 用OpenSSL与JAVA(JSSE)通信 2008-11-20 11:18 | llp20_2000
接楼上,刚才少发了:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
最后出现:The countryName field needed to be the same in the
....CA certificate (ch) and the request (ch)
并且生成的clientapp.pem大小为0(零)k,因此不能由pem生成dem.不知是何原因?
我反复对比过两次输入的countryName ,并且在javatool中都和Openssl生成时候都输入全部一样的公司,组织,城市,国家等,都是一样的结果.而博主的另一文章,openssl做自签名,用V1给 P2签名是可以的.并且此时-infiles为 req.pem.
新人,不熟悉openssl.请博主帮助
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  回复  更多评论
 
# re: 用OpenSSL与JAVA(JSSE)通信 2008-11-22 08:24 | 我爱佳娃
俺也不知为什么了。
国名好像可以为空的,你试下为空的情况。
另外,你的提示符为什么会多ASNXXX:commonName :ASN.1 12:
如果解决了,请贴上来。  回复  更多评论
 
# re: 用OpenSSL与JAVA(JSSE)通信 2009-02-03 11:25 | Jamie
那是因为他的openssl.conf里对于CN的设定是match, 而非optional.
countryName = match

如下:
[ policy_match ]
countryName = match
stateOrProvinceName = match
organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional

所以, 必须是匹配的才行.
你可以改为optional来解决这个问题.
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics