zoukankan      html  css  js  c++  java
  • Java安全通信概述

     1.安全通信介绍  

      计算机安全通信过程中,常使用消息摘要和消息验证码来保证传输的数据未曾被第三方修改。 

      消息摘要是对原始数据按照一定算法进行计算得到的结果,它主要检测原始数据是否被修改过。消息摘要与加密不同,加密是对原始数据进行变换,可以从变换后的数据中获得原始数据,而消息摘要是从原始数据中获得一部分信息,它比原始数据少得多,因此消息摘要可以看作是原始数据的指纹。 

      例:下面一段程序计算一段字符串的消息摘要 

    package com.messagedigest;
    import java.security.*;
    public class DigestPass {
     public static void main(String[] args) throws Exception{
      String str="Hello,I sent to you 80 yuan.";
      MessageDigest md = MessageDigest.getInstance("MD5");//常用的有MD5,SHA算法等
      md.update(str.getBytes("UTF-8"));//传入原始字串
      byte[] re = md.digest();//计算消息摘要放入byte数组中
      
    //下面把消息摘要转换为字符串
      String result = "";
      for(int i=0;i<re.length;i++){
       result += Integer.toHexString((0x000000ff&re[i])|0xffffff00).substring(6);
      }
      System.out.println(result);
     }
    }

      当我们有时需要对一个文件加密时,以上方式不再适用。 

      又例:下面一段程序计算从输入(出)流中计算消息摘要。 

    package com.messagedigest;

    import java.io.*;
    import java.security.*;
    public class DigestInput {
     public static void main(String[] args) throws Exception{
      String fileName = "test.txt";
      MessageDigest md = MessageDigest.getInstance("MD5");
      FileInputStream fin = new FileInputStream(fileName);
      DigestInputStream din = new DigestInputStream(fin,md);//构造输入流
      
    //DigestOutputStream dout = new DigestOutputStream(fout,md);
      
    //使用输入(出)流可以自己控制何时开始和关闭计算摘要
      
    //也可以不控制,将全过程计算
      
    //初始时是从开始即开始计算,如我们可以开始时关闭,然后从某一部分开始,如下:
      
    //din.on(false);
      int b;
      while((b=din.read())!=-1){
       //做一些对文件的处理
       
    //if(b=='$') din.on(true); //当遇到文件中的符号$时才开始计算
      }
      byte[] re = md.digest();//获得消息摘要
      
    //下面把消息摘要转换为字符串
      String result = "";
      for(int i=0;i<re.length;i++){
       result += Integer.toHexString((0x000000ff&re[i])|0xffffff00).substring(6);
      }
      System.out.println(result);
     }
    }

      当A和B通信时,A将数据传给B时,同时也将数据的消息摘要传给B,B收到后可以用该消息摘要验证A传的消息是否正确。这时会产生问题,即若传递过程中别人修改了数据时,同时也修改了消息摘要。B就无法确认数据是否正确。消息验证码可以解决这一问题。 

      使用消息验证码的前提是 A和B双方有一个共同的密钥,这样A可以将数据计算出来的消息摘要加密后发给B,以防止消息摘要被改。由于使用了共同的密钥,所以称为“验证码”。 
       例、下面的程序即可利用共同的密钥来计算消息摘要的验证码 

    package com.mac;

    import java.io.*;
    import java.security.*;
    import javax.crypto.*;
    import javax.crypto.spec.*;
    public class MyMac {
     public static void main(String[] args) throws Exception{
      //这是一个消息摘要串
      String str="TestString";
      //共同的密钥编码,这个可以通过其它算法计算出来
      byte[] kb={11,105,-119,50,4,-105,16,38,-14,-111,21,-95,70,-15,76,-74,
        67,-88,59,-71,55,-125,104,42};
      //获取共同的密钥
      SecretKeySpec k = new SecretKeySpec(kb,"HMACSHA1");
      //获取Mac对象
      Mac m = Mac.getInstance("HmacMD5");
      m.init(k);
      m.update(str.getBytes("UTF-8"));
      byte[] re = m.doFinal();//生成消息码
      
    //下面把消息码转换为字符串
      String result = "";
      for(int i=0;i<re.length;i++){
       result += Integer.toHexString((0x000000ff&re[i])|0xffffff00).substring(6);
      }
      System.out.println(result);
     }
    }

      使用以上两种技术可以保证数据没有经过改变,但接收者还无法确定数据是否确实是某个人发来的。尽管消息码可以确定数据是某个有同样密钥的人发来的,但这要求双方具有共享的密钥,若有一组用户共享,我们就无法确定数据的来源了。 

      基于SSL的数字签名可以解决这一问题。数字签名利用非对称加密技术,发送者使用私钥加密数据产生的消息摘要(签名),接收者使用发送者的公钥解密消息摘要以验证签名是否是某个人的。由于私钥只有加密者才有,因此如果接收者用某个公钥解密了某个消息摘要,就可以确定这段消息摘要必然是对应的私钥持有者发来的。

      使用数字签名的前提是接收数据者能够确信验证签名时(用发送者的私钥加密消息摘要)所用的公钥确实是某个人的 (因为有可能有人假告公钥)。数字证书可以解决这个问题。 

      数字证书含有两部分数据:一部分是对应主体(单位或个人)的信息,另一部分是这个主体所对应的公钥。即数字证书保存了主体和它的公钥的一一对应关系。同样,数字证书也有可能被假造,如何判定数字证书的内容的真实性呢?所以,有效的数字证书必须经过权威 CA的签名,即权威CA验证数字证书的内容的真实性,然后再在数字证书上使用自己的私钥签名(相当于在证书加章确认)。

      这样,当用户收到这样的数字证书后,会用相应的权威 CA的公钥验证该证书的签名(因为权威的CA的公钥在操作系统中己经安装)。根据非对称加密的原理,如果该证书不是权威CA签名的,将不能通过验证,即该证书是不可靠的。 


      若通过验证,即可证明此证书含的信息(发信人的公钥和信息)是无误的。于是可以信任该证书,便可以通过该证书内含的公钥来确认数据确实是发送者发来的。 

      于是,双方通信时, A把数据的消息摘要用自己的私钥加密(即签名),然后把自己的数字证书和数据及签名后的消息摘要一起发送给B,B处查看A的数字证书,如果A的数字证书是经过权威CA验证可靠的,便信任A,便可使用A的数字证书中附带的A的公钥解密消息摘要(这一过程同时确认了发送数据的人又可以解密消息摘要),然后通过解密后的消息摘要验证数据是否正确无误没被修改。 

    2.SSL安全证书

      SSL(安全套接层)是Netscape公司在1994年开发的,最初用于WEB浏览器,为浏览器与服务器间的数据传递提供安全保障,提供了加密、来源认证和数据完整性的功能。现在SSL3.0得到了普遍的使用,它的改进版TLS(传输层安全)已经成为互联网标准。SSL本身和TCP套接字连接是很相似的,在协议栈中,SSL可以被简单的看作是安全的TCP连接,但是某些TCP连接的特性它是不支持的,比如带外数据(out-of-bound)。

       在构建基于Socket的C/S程序时,通过添加对SSL的支持来保障数据安全和完整是不错的方法。完善的Java为我们提供了简单的实现方法:JSSE(Java安全套接字扩展)。JSSE是一个纯Java实现的SSL和TLS协议框架,抽象了SSL和TLS复杂的算法,使安全问题变得简单。JSSE已经成为J2SE1.4版本中的标准组件,支持SSL 3.0和TLS 1.0。我们将通过一个具体的例子演示JSSE的一些基本应用。例子中的服务器端将打开一个SSL Socket,只有持有指定证书的客户端可以与它连接,所有的数据传递都是加密的。

        构造一个SSLSocket是非常简单的:

     SSLServerSocketFactory factory=(SSLServerSocketFactory)SSLServerSocketFactory.getDefault();

    SSLServerSocket server = (SSLServerSocket) factory.createServerSocket(portNumber);
    SSLSocket socket = (SSLSocket);

       但是执行这样的程序会产生一个异常,报告找不到可信任的证书。SSLSocket和普通的Socket是不一样的,它需要一个证书来进行安全认证。

    1)证书

        生成一个CA证书,在命令行下执行:

     keytool –genkey –keystore SSLKey –keyalg rsa –alias SSL
    

      黑体部分是用户可以自己指定的参数,第一个参数是要生成的证书的名字,第二个参数是证书的别名。rsa指明了我们使用的加密方法。

      系统会要求输入证书发放者的信息,逐项输入即可。

      系统生成的文件命将会和证书名相同。证书可以提交给权威CA认证组织审核,如果通过审核,组织会提供信任担保,向客户担保你的连接是安全的。当然这不是必须的。在我们的例子中会把证书直接打包到客户端程序中,保证客户端是授权用户,避免伪造客户,所以不需要提交审核。

    2)服务器端

      现在可以编写服务器端的代码,与普通的Socket代码不同,我们需要在程序中导入证书,并使用该证书构造SSLSocket。需要的说明的是:

     KeyStore ks=KeyStore.getInstance("JKS");
    

      访问Java密钥库,JKS是keytool创建的Java密钥库,保存密钥。

    KeyManagerFactory kmf=KeyManagerFactory.getInstance("SunX509");
    

      创建用于管理JKS密钥库的X.509密钥管理器。

    SSLContext sslContext=SSLContext.getInstance("SSLv3");
    

      构造SSL环境,指定SSL版本为3.0,也可以使用TLSv1,但是SSLv3更加常用。

    sslContext.init(kmf.getKeyManagers(),null,null);
    

      初始化SSL环境。第二个参数是告诉JSSE使用的可信任证书的来源,设置为null是从javax.net.ssl.trustStore中获得证书。第三个参数是JSSE生成的随机数,这个参数将影响系统的安全性,设置为null是个好选择,可以保证JSSE的安全性。

      完整代码如下:

    /*
    *SSL Socket的服务器端
    *@Author Bromon
    */

    package org.ec107.ssl;

    import java.net.*;
    import javax.net.ssl.*;
    import java.io.*;
    import java.security.*;

    public class SSLServer
    {
    static int port=8266; //系统将要监听的端口号,82.6.6是偶以前女朋友的生日^_^
    static SSLServerSocket server;

    /*
    *构造函数
    */

    public SSLServer()
    {

    }

    /*
    *@param port 监听的端口号
    *@return 返回一个SSLServerSocket对象
    */

    private static SSLServerSocket getServerSocket(int thePort)
    {
    SSLServerSocket s=null;
    try
    {
    String key="SSLKey"; //要使用的证书名

    char keyStorePass[]="12345678".toCharArray(); //证书密码

    char keyPassword[]="12345678".toCharArray(); //证书别称所使用的主要密码

    KeyStore ks=KeyStore.getInstance("JKS"); //创建JKS密钥库

    ks.load(new FileInputStream(key),keyStorePass);


    //创建管理JKS密钥库的X.509密钥管理器
    KeyManagerFactory kmf=KeyManagerFactory.getInstance("SunX509");

    kmf.init(ks,keyPassword);

    SSLContext sslContext=SSLContext.getInstance("SSLv3");

    sslContext.init(kmf.getKeyManagers(),null,null);

    //根据上面配置的SSL上下文来产生SSLServerSocketFactory,与通常的产生方法不同
    SSLServerSocketFactory factory=sslContext.getServerSocketFactory();

    s=(SSLServerSocket)factory.createServerSocket(thePort);

    }catch(Exception e)
    {
    System.out.println(e);
    }
    return(s);
    }


    public static void main(String args[])
    {
    try
    {
    server=getServerSocket(port);
    System.out.println("在”+port+”端口等待连接...");

    while(true)
    {
    SSLSocket socket=(SSLSocket)server.accept();

    //将得到的socket交给CreateThread对象处理,主线程继续监听
    new CreateThread(socket);

    }
    }catch(Exception e)
    {
    System.out.println("main方法错误80:"+e);
    }
    }
    }



    /*
    *内部类,获得主线程的socket连接,生成子线程来处理
    */
    class CreateThread extends Thread
    {
    static BufferedReader in;
    static PrintWriter out;
    static Socket s;

    /*
    *构造函数,获得socket连接,初始化in和out对象
    */

    public CreateThread(Socket socket)
    {
    try
    {
    s=socket;
    in=new BufferedReader(new InputStreamReader(s.getInputStream(),"gb2312"));
    out=new PrintWriter(s.getOutputStream(),true);
    start(); //开新线程执行run方法

    }catch(Exception e)
    {
    System.out.println(e);
    }

    }

    /*
    *线程方法,处理socket传递过来的数据
    */
    public void run()
    {
    try
    {
    String msg=in.readLine();
    System.out.println(msg);
    s.close();
    }catch(Exception e)
    {
    System.out.println(e);
    }
    }
    }

      将我们刚才生成的证书放到程序所在的目录下,上面的代码就可以在编译之后执行:

    java org.ec107.ssl.SSLServer
    

       在8266端口等待连接…

    3) 客户端

      客户端的代码相对简单,我们可以不在程序中指定SSL环境,而是在执行客户端程序时指定。需要注意的是客户端并没有导入证书,而是采用了默认的工厂方法构造SSLSocket:

    SSLSocketFactory factory=(SSLSocketFactory)SSLSocketFactory.getDefault();
    

     

       构造默认的工厂方法

    Socket s=factory.createSocket("localhost",port);
    

       打开一个SSLSocket连接

      

    /*

    *SSL Socket 的客户端
    *@Author Bromon
    */

    import java.net.*;
    import javax.net.ssl.*;
    import javax.net.*;
    import java.io.*;

    public class SSLClient
    {
    static int port=8266;
    public static void main(String args[])
    {
    try
    {
    SSLSocketFactory factory=(SSLSocketFactory)SSLSocketFactory.getDefault();

    Socket s=factory.createSocket("localhost",port);

    PrintWriter out=new PrintWriter(s.getOutputStream(),true);
    out.println("安全的说你好");
    out.close();
    s.close();
    }catch(Exception e)
    {
    System.out.println(e);
    }
    }
    }

      把服务器产生的证书(SSLKey)拷贝到程序所在的目录,执行这个程序的时候需要向javax.net.ssl.trustStore环境变量传入证书名:

     java –Djavax.net.ssl.trustStore=SSLKey org.ec107.ssl.SSLClient

      可以在服务器的控制台看到客户端发送过来的数据。

       执行客户端可以有另一种方法,把证书拷贝到java home/lib/security目录下,名字改为jssecacerts,然后可以直接执行客户端:

    java org.ec107.ssl.SSLClient
    

      程序会自动的到上述目录下去寻找jssecacerts文件作为默认的证书。需要注意的是这里的java home并不是我们在安装J2SE时指定的那个JAVA_HOME。可以执行一个程序来得到java home的位置:

     public class GetJavaHome
    {
    public static void main(String args[])
    {
    System.out.println(System.getProperty(“java.home”));
    }
    }

      

      一般情况下(windows 2K)hava home的位置是在C:Program FilesJavaj2re1.4.0_02,相对的,证书就应该拷贝到C:Program FilesJavaj2re1.4.0_02libsecurity下,如果安装了自带JDK的Java IDE,比如JBuilder,情况可能会有不同。

      如果程序客户在不持有证书的情况下直接进行连接,服务器端会产生运行时异常,不允许进行连接。

      运行环境:windows 2K server,j2sdk1.4.1

    PS: 欢迎关注公众号"Devin说",会不定期更新Java相关技术知识。 

  • 相关阅读:
    HDU 5818 Joint Stacks
    HDU 5816 Hearthstone
    HDU 5812 Distance
    HDU 5807 Keep In Touch
    HDU 5798 Stabilization
    HDU 5543 Pick The Sticks
    Light OJ 1393 Crazy Calendar (尼姆博弈)
    NEFU 2016省赛演练一 I题 (模拟题)
    NEFU 2016省赛演练一 F题 (高精度加法)
    NEFU 2016省赛演练一 B题(递推)
  • 原文地址:https://www.cnblogs.com/devinzhang/p/2370069.html
Copyright © 2011-2022 走看看