java ssl 双向zz

雪域幽狐 2011-07-13 15:12 阅读:8005


实现技术:
JSSE(Java Security Socket Extension)
Server需要:
1)KeyStore: 其中保存服务端的私钥
2)Trust KeyStore:其中保存客户端的授权证书
Client需要:
1)KeyStore:其中保存客户端的私钥
2)Trust KeyStore:其中保存服务端的授权证书


使用Java自带的keytool命令,去生成这样信息文件:

1)生成服务端私钥,并且导入到服务端KeyStore文件中

2)根据私钥,导出服务端证书

3)将服务端证书,导入到客户端的Trust KeyStore中

采用同样的方法,生成客户端的私钥,客户端的证书,并且导入到服务端的Trust KeyStore中
1)keytool -genkey -alias clientkey -keystore kclient.keystore
2)keytool -export -alias clientkey -keystore kclient.keystore -file client.crt
3)keytool -import -alias clientkey -file client.crt -keystore tserver.keystore


Server:
Java代码

public class Server implements Runnable{    
   
    private static final int     DEFAULT_PORT                     = 7777;    
   
    private static final String SERVER_KEY_STORE_PASSWORD        = "123456";    
    private static final String SERVER_TRUST_KEY_STORE_PASSWORD = "123456";    
   
    private SSLServerSocket      serverSocket;    
   
    /** 
      * 启动程序 
      * 
      * @param args 
      */   
    public static void main(String[] args) {    
         Server server = new Server();    
         server.init();    
         Thread thread = new Thread(server);    
         thread.start();    
     }    
   
    public synchronized void start() {    
        if (serverSocket == null) {    
             System.out.println("ERROR");    
            return;    
         }    
        while (true) {    
            try {    
                 Socket s = serverSocket.accept();    
                 InputStream input = s.getInputStream();    
                 OutputStream output = s.getOutputStream();    
   
                 BufferedInputStream bis = new BufferedInputStream(input);    
                 BufferedOutputStream bos = new BufferedOutputStream(output);    
   
                byte[] buffer = new byte[20];    
                 bis.read(buffer);    
                 System.out.println("------receive:--------"+new String(buffer).toString());    
   
                 bos.write("yes".getBytes());    
                 bos.flush();    
   
                 s.close();    
             } catch (Exception e) {    
                 System.out.println(e);    
             }    
         }    
     }    
    public void init() {    
        try {    
             SSLContext ctx = SSLContext.getInstance("SSL");    
   
             KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");    
             TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");    
   
             KeyStore ks = KeyStore.getInstance("JKS");    
             KeyStore tks = KeyStore.getInstance("JKS");    
   
             ks.load(new FileInputStream("src/ssl/kserver.keystore"), SERVER_KEY_STORE_PASSWORD.toCharArray());    
             tks.load(new FileInputStream("src/ssl/tserver.keystore"), SERVER_TRUST_KEY_STORE_PASSWORD.toCharArray());    
   
             kmf.init(ks, SERVER_KEY_STORE_PASSWORD.toCharArray());    
             tmf.init(tks);    
   
             ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);    
   
             serverSocket = (SSLServerSocket) ctx.getServerSocketFactory().createServerSocket(DEFAULT_PORT);    
             serverSocket.setNeedClientAuth(true);    
         } catch (Exception e) {    
             System.out.println(e);    
         }    
     }    
   
    public void run() {    
        // TODO Auto-generated method stub    
         start();    
     }    
}

Client:

Java代码
package ssl;    
   
import java.io.BufferedInputStream;    
import java.io.BufferedOutputStream;    
import java.io.FileInputStream;    
import java.io.IOException;    
import java.io.InputStream;    
import java.io.OutputStream;    
import java.security.KeyStore;    
   
import javax.net.ssl.KeyManagerFactory;    
import javax.net.ssl.SSLContext;    
import javax.net.ssl.SSLSocket;    
import javax.net.ssl.TrustManagerFactory;    
   
/** 
* SSL Client 
* 
* @author Leo 
*/   
public class Client {    
   
    private static final String DEFAULT_HOST                     = "127.0.0.1";    
    private static final int     DEFAULT_PORT                     = 7777;    
   
    private static final String CLIENT_KEY_STORE_PASSWORD        = "123456";    
    private static final String CLIENT_TRUST_KEY_STORE_PASSWORD = "123456";    
   
    private SSLSocket            sslSocket;    
   
    /** 
      * 启动客户端程序 
      * 
      * @param args 
      */   
    public static void main(String[] args) {    
        Client client = new Client();    
         client.init();    
         client.process();    
     }    
   
   
    public void process() {    
        if (sslSocket == null) {    
             System.out.println("ERROR");    
            return;    
         }    
        try {    
             InputStream input = sslSocket.getInputStream();    
             OutputStream output = sslSocket.getOutputStream();    
   
             BufferedInputStream bis = new BufferedInputStream(input);    
             BufferedOutputStream bos = new BufferedOutputStream(output);    
   
             bos.write("1234567890".getBytes());    
             bos.flush();    
   
            byte[] buffer = new byte[20];    
             bis.read(buffer);    
             System.out.println(new String(buffer));    
   
             sslSocket.close();    
         } catch (IOException e) {    
             System.out.println(e);    
         }    
     }    
   
   
    public void init() {    
        try {    
             SSLContext ctx = SSLContext.getInstance("SSL");    
   
             KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");    
             TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");    
   
             KeyStore ks = KeyStore.getInstance("JKS");    
             KeyStore tks = KeyStore.getInstance("JKS");    
   
             ks.load(new FileInputStream("src/ssl/kclient.keystore"), CLIENT_KEY_STORE_PASSWORD.toCharArray());    
             tks.load(new FileInputStream("src/ssl/tclient.keystore"), CLIENT_TRUST_KEY_STORE_PASSWORD.toCharArray());    
   
             kmf.init(ks, CLIENT_KEY_STORE_PASSWORD.toCharArray());    
             tmf.init(tks);    
   
             ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);    
   
             sslSocket = (SSLSocket) ctx.getSocketFactory().createSocket(DEFAULT_HOST, DEFAULT_PORT);    
         } catch (Exception e) {    
             System.out.println(e);    
         }    
     }    
   
}   

启动Server


启动Client,发送信息。

Server接收如下:正确解密
返回Client信息,

如此,就完成了服务端和客户端之间的基于身份认证的交互。

client采用kclient.keystore中的clientkey私钥进行数据加密,发送给server。
server采用tserver.keystore中的client.crt证书(包含了clientkey的公钥)对数据解密,如果解密成功,证明消息来自client,进行逻辑处理。

server采用kserver.keystore中的serverkey私钥进行数据加密,发送给client。
client采用tclient.keystore中的server.crt证书(包含了serverkey的公钥)对数据解密,如果解密成功,证明消息来自server,进行逻辑处理。

如果过程中,解密失败,那么证明消息来源错误。不进行逻辑处理。这样就完成了双向的身份认证。

4条评论

雪域幽狐 2011-07-13 15:13
个人在这过程中遇到和解决的几个问题
1、上面的keytoole是JDK1.4语法
JDK1.5、1.6参见
keytool -genkeypair -alias server -validity 3600 -keyalg RSA -keystore server.keystore
keytool -exportcert -alias server -keystore server.keystore -file server.crt
keytool -importcert -alias server -file server.crt -keystore tclient.keystore

2、看完这个后,好像发现服务器端只能和一个客户端实现双向SSL,主要在于tks.load时,只能load一个,后查阅相关文档,发现jks文件能存放多个证书,因此可解决这个问题

3、关于密钥对的生成,除使用keytool外,可以在证书服务那里申请,需要注意的是服务器端申请服务器端证书,客户端申请客户端证书,否则不能正常工作,另外需要注意的是:在申请证书时,需要选中“标记密钥为可导出”,申请完证书后,直接安装到了证书管理中,可在运行中输入 certmgr.msc,在弹出的程序中,可参看到相关证书。可把证书导出为pfx文件,在Java里可以直接使用pfx文件,不一定非得需要jks文件。
需要修改的代码是 KeyStore ks = KeyStore.getInstance("PKCS12");

雪域幽狐 2011-07-13 15:13
在Security编程中,有几种典型的密码交换信息文件格式:


   DER-encoded certificate: .cer, .crt
  PEM-encoded message: .pem
  PKCS#12 Personal Information Exchange: .pfx, .p12
  PKCS#10 Certification Request: .p10
  PKCS#7 cert request response: .p7r
  PKCS#7 binary message: .p7b
    
    .der用于存放X.509证书,二进制格式,不含私钥

  .cer/.crt用于存放X.509证书,它是BASE64形式存放的,不含私钥。

  pfx/p12用于存放个人证书/私钥,他通常包含保护密码,2进制方式

  p10是证书请求

  p7r是CA对证书请求的回复,只用于导入

  p7b以树状展示证书链(certificate chain),同时也支持单个证书,不含私钥

雪域幽狐 2011-07-13 15:15
在Tomcat建立SSL参见相关文章
对于keystoreFile,除了可以使用jks文件外,也可以直接使用pfx文件
需要增加一个参数
keystoreFile="conf/server.pfx" keystorePass="123456" keystoreType="PKCS12"
最后一个keystoreType

对于Tomcat,也可以实现SSL双向,继续添加参数
truststoreFile="目录/server.store" truststorePass="密码" truststoreType="jks"

雪域幽狐 2012-05-07 13:49
查看了下TLS协议详解(RFC 2246),发现上文说的“client采用kclient.keystore中的clientkey私钥进行数据加密,发送给server。 server采用tserver.keystore中的client.crt证书(包含了clientkey的公钥)对数据解密,如果解密成功,证明消息来自client,进行逻辑处理。” 应该不正确。
    SSL/TLS服务器端客户端交互过程如图所示:
1. ClientHello:发送信息到服务器的客户端,这些信息如 SSL 协议版本、会话 ID 和密码组信息,如加密算法和能支持的密匙的大小。

2. ServerHello:选择最好密码组的服务器并发送这个消息给客户端。密码组包括客户端和服务器支持。

3. Certificate:服务器将包含其公钥的证书发送给客户端。这个消息是可选的,在服务器请求验证的时候会需要它。换句话说,证书用于向客户端确认服务器的身分。

4. Certificate Request: 这个消息仅在服务器请求客户端验证它自身的时候发送。多数电子商务应用不需要客户端对自身进行。

5. Server Key Exchange:如果证书包含了服务器的公钥不足以进行密匙交换,则发送该消息。

6. ServerHelloDone:这个消息通知客户端,服务器已经完成了交流过程的初始化。

7. Certificate:仅当服务器请求客户端对自己进行验证的时候发送。

8. Client Key Exchage:客户端产生一个密匙与服务器共享。如果使用 Rivest-Shamir-Adelman (RSA) 加密算法,客户端将使用服务器的公钥将密匙加密之后再发送给服务器。服务器使用自己的私钥或者密钥对消息进行解密以得到共享的密匙。现在,客户端和服务器共享着一个已经安全分发的密匙。

9. Certificate Verify:如果服务器请求验证客户端,这个消息允许服务器完成验证过程。

10. Change Cipher Spec:客户端要求服务器使用加密模式。

11. Finished:客户端告诉服务器它已经准备好安全通信了。

12. Change Cipher Spec:服务器要求客户端使用加密模式。

13. Finished:服务器告诉客户端它已经准备好安全通信了。这是 SSL “握手”结果的标志。

14. Encrypted Data:客户端和服务器现在可以开发在安全通信通道上进行加密信息的交流了。

==========
客户端认证服务器端是根据服务器发过来的证书,和客户端自己的信任库进行对比,然后判断是否认可;同样,服务器端认证客户端是根据客户端发过来的证书(第7和第9),和服务器端自己的信任库对比,然后判断是否认可。

客户端不用担心第三方伪造服务器端,虽然这个服务器端的证书是公开的,但是客户端在用服务器端的证书加密密钥后,只有真正的服务器端才有对应的私钥,才能解开对应的内容,如果解不开,则拿不到客户端生成的密钥。

登陆后可评论