对称加密、非对称加密、DH协定、数字证书、SSL实现安全通信

为什么要加密

当数据从一个ip到另一个ip的过程中,往往是需要经过很多的ip才能到达目标ip,这个过程可以通过tracert命令来查看,比如,输入一个淘宝的网址,跟踪它的路由,如下:
对称加密、非对称加密、DH协定、数字证书、SSL实现安全通信
我们可以发现,这个过程经过了很多的ip,我们不知道这些ip是谁的主机,只知道我们的数据经过了它们。数据的传输是以字节的形式的,如果不加密,这些数据可以轻易人获取并得到内部的信息,因此这种明文传输的方式是不安全的。一旦采取了加密的手段,只要第三方不了解解密的手段,那么他就无法获取其中的信息。

对称加密算法

概念:顾名思义,这种加密算法是“对称”的,发送方和接收方持有一把相同的钥匙,当发送方需要发送数据的时候,只要用这把秘钥加密数据,然后再发送;接收方收到了数据后再用相同的秘钥去解密,就可以还原得到数据。
优点:这种加密算法很方便,效率也比较高。对称加密的可靠性好,不容易被**。
缺点:这两把秘钥的保管十分重要,一旦其中有一把秘钥被他人得知,数据就不安全了。因为是公共秘钥,秘钥的生成方需要把被加密的数据和秘钥一起发给接收方,接收方才能解密,虽然加密的数据不容易被**,但是秘钥在传输的过程中任意暴露给第三方,这种以小秘密守护大秘密的方式是不安全的。
这里我使用了DESede算法来演示这个过程:服务器加密数据并通过IO流把数据发给客户端,客户端对数据进行解密。
创建秘钥
秘钥的创建一般分为如下几步:
1、获取秘钥生成器。
2、指定秘钥长度。
3、生成秘钥对象。
4、获取秘钥对象的字节数组。
5、把秘钥信息保存在文件中。

public class CreateDESede {
	  public static void main(String[] args)throws Exception{
		  //得到DESede算法的秘钥生成器
		   KeyGenerator kg=KeyGenerator.getInstance("DESede");
		   //指定秘钥长度为168位
		   kg.init(168);
		   //生成秘钥对象
		   SecretKey key=kg.generateKey();
		   //将秘钥以字节形式保存在文件中
		   byte[] data=key.getEncoded();
		   FileOutputStream fos =new FileOutputStream("DESedekey.netjava");
		   fos.write(data);
		   fos.flush();
		   fos.close();
		   System.out.println("DESede秘钥文件生成成功!");
	  }
}

服务器加密并发送数据
服务器首先通过IO流读取秘钥信息,然后用秘钥加密数据,用socket获取流发出去。
加密的实现:生成秘钥对象、密码器对象,设置参数为加密,然后密码器用秘钥来加密数据,就得到了加密后的字节数组。

public class DESSocketServer {
	public static void main(String[] args)throws Exception{
		ServerSocket sc=new ServerSocket(9090);
		while(true){
			Socket client=sc.accept();			
			processConn(client);					
		}
	}	
	/**
	 * 连接并发送数据的方法
	 * @param sc
	 * @throws Exception
	 */
	public static void processConn(Socket sc)throws Exception{
		//获取原始输出流
		OutputStream ous=sc.getOutputStream();
        //获取秘钥字节
		byte[] keyBytes=getkeyByte();
        //定义内容字符串
		String szSrc = "This is a 3DES test. 测试";
        System.out.println("加密前的字符串:" + szSrc);
        //获取加密后的字节数组
        byte[] encoded = encryptMode(keyBytes, szSrc.getBytes());
        System.out.println("加密后的字符串:" + new String(encoded));
        //把加密后的数据写到客户端
        ous.write(encoded);       
	}	
	/**
	 * 加密的方法
	 * @param keybyte 秘钥文件中读取的字节数组
	 * @param src 需要加密的内容
	 * @return 加密后的内容
	 */
    public static byte[] encryptMode(byte[] keybyte, byte[] src) {
        try {
            // 生成**对象
            SecretKey deskey = new SecretKeySpec(keybyte, "DESede");
            // 获得有加密和解密功能的对象,即密码器对象
            Cipher c1 = Cipher.getInstance("DESede");
            //初始化为用钥匙来实现加密
            c1.init(Cipher.ENCRYPT_MODE, deskey);
            return c1.doFinal(src);
        } catch (java.security.NoSuchAlgorithmException e1) {
            e1.printStackTrace();
        } catch (javax.crypto.NoSuchPaddingException e2) {
            e2.printStackTrace();
        } catch (java.lang.Exception e3) {
            e3.printStackTrace();
        }
        return null;
    }			
	/**
	 * 从公共文件中读取秘钥的信息
	 * @return 秘钥信息的字节数组
	 */
    public static byte[] getkeyByte()throws Exception{
		FileInputStream fis=new FileInputStream("DESedekey.netjava");
		//从秘钥文件中读取秘钥数据
		byte[] keyData=new byte[fis.available()];
		fis.read(keyData);
		return keyData;
    }    
}

客户端接收数据,解密
客户端和服务器代码很类似,解密的过程把模式设置为解密即可。客户端的秘钥这里从本地直接读取了,实际上还需要有秘钥文件的传输这个部分。

public class DESSocketClient {
	  public static void main(String[] args){		 
		  try{ 
		  Socket socket=new Socket("localhost", 9090);
		  processConn(socket);
		  }catch(Exception e){
			  e.printStackTrace();
		  }
	  }	
	  /**
	   * 客户端连服务器并获取数据
	   * @param sc
	   * @throws Exception
	   */
	  public static void processConn(Socket sc)throws Exception{
		  //准备接收数据
		  InputStream ins=sc.getInputStream();
          byte[] buffer=new byte[1024];
          byte[] data=null;
          int length=0;
		  length=ins.read(buffer);
		  data=new byte[length];
		  System.arraycopy(buffer, 0, data, 0, length);		  
		  System.out.println("被加密的内容:"+new String(data));
		  //获取秘钥
		  byte[] keyByte=getkeyByte();
		  byte[] content=decryptMode(keyByte, data);
		  System.out.println("解密后的字符串:"+new String(content));	
	  }	  
	    /**
	     * 解密的方法
	     * @param keybyte 秘钥信息的字节数组
	     * @param src  需要解密的内容
	     * @return
	     */
	    public static byte[] decryptMode(byte[] keybyte, byte[] src) {
	        try {
	            // 生成**
	            SecretKey deskey = new SecretKeySpec(keybyte, "DESede");
	            // 生成密码器对象
	            Cipher c1 = Cipher.getInstance("DESede");
	            //设置为解密
	            c1.init(Cipher.DECRYPT_MODE, deskey);
	            return c1.doFinal(src);
	        } catch (java.security.NoSuchAlgorithmException e1) {
	            e1.printStackTrace();
	        } catch (javax.crypto.NoSuchPaddingException e2) {
	            e2.printStackTrace();
	        } catch (java.lang.Exception e3) {
	            e3.printStackTrace();
	        }
	        return null;
	    }		
		/**
		 * 从文件中读取秘钥数据
		 * @param ous
		 * @return
		 */
	    public static byte[] getkeyByte()throws Exception{
			FileInputStream fins=new FileInputStream("DESedekey.netjava");
			//从秘钥文件中读取秘钥数据
			byte[] keyData=new byte[fins.available()];
			fins.read(keyData);
			return keyData;
	    }
}

加密和解密的测试
服务器:
对称加密、非对称加密、DH协定、数字证书、SSL实现安全通信
客户端:
对称加密、非对称加密、DH协定、数字证书、SSL实现安全通信
加密后的数据生成字符串就是这样一个乱码的效果了,实现了在传输中的数据的加密。
思考:数据在传输过程中可能被人得到并且修改,如何得知?
通过消息摘要判断原文是否被窜改:消息的发送方在发送信息的同时也会发送一份消息摘要,接收方通过判断消息摘要有没有变化来判断数据在传输中有没有窜改。消息摘要是对原文的一些特征信息的摘取,就像是人的指纹一样,它是你身份的象征,但它不能还原为一个人,同理消息摘要不能还原得到原文的信息,它仅仅是用来校验数据在传输过程中是不是被修改了。
消息摘要一般通过MD5算法和SHA-1算法实现,发送方通过MD5算法获取摘要,把摘要和原文一起发给接收方,接收方也通过MD5算法根据接收到的“原文”数据来获取摘要,把这份摘要与那份和原文一起传来的摘要进行比对,如果不一致,那么就说明原文被窜改过了。

非对称加密算法

概念:非对称加密算法是一种加密方和解密方使用不同秘钥的算法,这里称为公钥和私钥,公钥和私钥是一个相对的概念,私钥加密的内容只有对应的公钥可以解,公钥加密的内容只有对应的私钥可以解,一般来说私钥只有一把,它在资源的拥有者手中,公钥可以有很多份,它是公开的,所有人都可以得到。
非对称加密的流程:资源的发布者(比如服务器)在发布资源的同时会生成一対公钥和私钥,当有客户端希望连接它的时候,形成了socket通道,服务器就把公钥传给他,用户把数据加密之后发给服务器,在这个传输过程中是安全的(数据只有私钥可以解,私钥在服务器手中),服务器收到数据并解密。
非对称加密的优点:信息的传输更加安全,只要保管好秘钥就可以了。
非对称加密的缺点:计算量很大,效率比较低。
用RSA算法实现加密与解密
步骤(和DESede类似):
1、获取秘钥生成器。
2、设定秘钥长度。
3、生成秘钥对。
4、获取公、私钥。
5、密码器通过相应的秘钥实现加密和解密。

public class RSATest {
	public static void main(String[] args)throws Exception{
		//取得RSA算法的秘钥生成器对象
		KeyPairGenerator keyPairGen=KeyPairGenerator.getInstance("RSA");
		//设定秘钥长度为1024
		keyPairGen.initialize(1024);
		//生成秘钥对对象
		KeyPair keyPair=keyPairGen.generateKeyPair();
		//分别取得公钥和私钥对象
		RSAPrivateKey privateKey=(RSAPrivateKey)keyPair.getPrivate();
		RSAPublicKey publicKey=(RSAPublicKey)keyPair.getPublic();	
		//需要加密的原文
		String encryptTest="netjava Msg For Encrypy";
		System.out.println("原字符串:"+encryptTest);
		//得到需要加密的字节数组
		byte[] srcData=encryptTest.getBytes();
		//公钥加密
		byte[] e=encrypt(publicKey, srcData);
		System.out.println("公钥加密后得到的字符串:"+new String(e));
		//私钥解密
        byte[] data=decrypt(privateKey, e);
        System.out.println("私钥解密后得到的字符串:"+new String(data));
	}	
	/**
	 * 加密的方法
	 * @param publicKey 公钥
	 * @param obj 被加密的对象
	 * @return 加密结果字节数组
	 * @throws Exception
	 */
	public static byte[] encrypt(RSAPublicKey publicKey,byte[] obj)throws Exception{
		Cipher cp=Cipher.getInstance("RSA");
		cp.init(Cipher.ENCRYPT_MODE, publicKey);
		return cp.doFinal(obj);
	}	
	/**
	 * 解密的方法
	 * @param privateKey 私钥
	 * @param obj 需要解密的对象
	 * @return 解密结果字节数组
	 * @throws Exception
	 */
	public static byte[] decrypt(RSAPrivateKey privateKey,byte[] obj)throws Exception{
		Cipher cp=Cipher.getInstance("RSA");
		cp.init(Cipher.DECRYPT_MODE, privateKey);
		return cp.doFinal(obj);
	}
}

思考:如何解决计算量大的问题?

非对称的DH算法

为了解决RSA算法计算量大的问题,但同时又不希望在网络中传输秘钥,于是有了DH算法。它提高了加密解密的效率,又有比较高的可靠性。下面我们来了解一下它的原理。
DH算法原理
1、发送方和接收方通过协商确定两个较大的数(n,g),用于秘钥的生成,这两个数是可以被第三方知道的。
2、发送方生成一个随机数x,接收方生成一个随机数y,这两个数是需要保密的。
3、发送方计算:X=g^x mol n ;接收方计算:Y=g ^y mol n。
4、在计算出这两个数之后,把这两个数(公钥)分别传送给对方。
5、发送方对Y进一步计算得到最终的秘钥:K1=Y^x mol n ; K2=X ^y mol n。
6、双方就可以通过生成的秘钥来加密数据,解密数据了。
可靠性分析
外界知道的数据无非是n,g,X,Y这四个数据,而解密用到的是K1和K2,要想求得K1和K2就需要知道x和y,步骤3中,X和Y是由指数公式计算得到的,在n值很大的时候如果使用暴力法也几乎是不可能**出来的。因此这种算法的可靠性是用数学来保证的。
不足之处:这种算法不能抵御中间人的攻击,假如A要和B传输数据,这时C假冒B伪造了数据给A,A是不知道这件事的,它会把C当成真正的B,把它发布的秘钥当做B的公钥,根据错误的公钥生成的秘钥来加密得到数据。此时这样的数据只有C能解,而B不能解。“假冒”公钥的问题在非对称加密算法中很常见。
解决方法:设立第三方认证机构(CA),通过它们来“认证”公钥的真实身份。

数字证书

数字签名的过程
1、A生成自己的秘钥对(公钥和私钥)。
2、把自己的身份信息通过MD5计算摘要。
3、用私钥加密摘要。
4、把被加密的摘要,公钥,身份信息公布。
证明过程
B拿着A的公钥,试着解开它的身份信息摘要,如果可以解开,且解密后的身份信息和同时上传的那部分身份信息一致,则可以说明这个公钥是A的公钥。
为什么可以证明?因为身份信息和译文匹配、译文可解,两个条件满足,公钥的归属就被唯一确定了。如果身份信息被中间人窜改过,那么就会匹配失败。
如何生成数字证书
对称加密、非对称加密、DH协定、数字证书、SSL实现安全通信
通过以上指令就可以生成自己的数字证书啦!下面是文件打开的效果:
对称加密、非对称加密、DH协定、数字证书、SSL实现安全通信
对称加密、非对称加密、DH协定、数字证书、SSL实现安全通信
我们可以看到,数字证书中有公钥,有效期,使用者,签名算法等信息。
下面给出在Java中读取数字证书中信息的代码:

public class Cer {
	public static void main(String[] args)throws Exception{
		//取得x.509证书解析对象
		CertificateFactory cf=CertificateFactory.getInstance("X.509");
		//从证书文件读入数据
		FileInputStream in = new FileInputStream("netjava.cer");
		//将文件流转为证书对象
		Certificate c=cf.generateCertificate(in);
		//转型为x.509证书格式
		X509Certificate t=(X509Certificate)c;
		//输出证书中的相关信息
		System.out.println("版本号:"+t.getVersion());
		System.out.println("***:"+t.getSerialNumber().toString());
		System.out.println("主体名:"+t.getSubjectDN());
		System.out.println("签发者“"+t.getIssuerDN());
		System.out.println("有效期:"+t.getNotBefore());
		System.out.println("签名算法:"+t.getSigAlgName());
		//取得证书的签名数据,可以验证身份信息是否被改动
		byte[] sig=t.getSignature();//签名值
		//取得证书的公钥对象
		PublicKey publicKey=t.getPublicKey();
		byte[] pkCode=publicKey.getEncoded();
		System.out.println("公钥数据:");
		for(int i=0;i<pkCode.length;i++){
			if(i%10==0)System.out.println();
			System.out.print("  "+pkCode[i]);
		}
		//可以用公钥加密数据给对方
		//其他检验认证。。。有效期,是否通过CA认证的。。。
	}			
}

读取证书信息的输出结果
对称加密、非对称加密、DH协定、数字证书、SSL实现安全通信
思考:假如C知道了A的信息,伪造了一份A的数字证书,对外宣称这是A的公钥和证书,怎么办?

数字认证机制

为了实现身份的认证,需要有一个第三方的认证机构(CA),下面我们通过一张图来向大家展示认证中心的作用:
对称加密、非对称加密、DH协定、数字证书、SSL实现安全通信
认证流程简介:A向CA申请签名,CA经过判断知道确实是A本人来申请,那么就在A的数字证书中加上用自己的私钥加密的一段签名,然后A得到了包含CA签名的证书,然后发布证书给B。此时B需要去验证A的身份信息,于是带着证书去认证中心,认证中心用自己的私钥去解那段CA的签名,如果解开了,可以证明这是认证中心发布的证书,可信,于是B可以放心地用A的公钥来加密信息,然后发给A。
思考:如果认证中心也是伪造的怎么办?
解决思路:就设计一个多级的认证体系,它们之间形成环形地相互认证关系,以确保认证中心的可靠。示意图如下:
对称加密、非对称加密、DH协定、数字证书、SSL实现安全通信

SSL通信

SSL的概念:SSL的英文是“Secure Sockets Layer”,中文名是“安全套接层协议层”,它基于DH算法为数据的传输增加了可靠性,双方建立起一条安全信道,都认证对方是合法用户。
HTTPS:比较常见的HTTP是基于TCP实现的通信,这是一种明文传输协议,数据对于外界是完全透明的,而HTTPS是一种安全的传输协议,底层是基于SSL实现的。企业之间传输数据用的v*n,底层也是基于SSL实现的。
SSL的作用
1、对服务器和客户端合法性进行验证,确保数据被发到正确的地方。
2、加密数据,确保数据传输过程中的安全性。
3、保护数据的完整性。
传输的过程
1、客户端向服务器发出连接的请求,并把自己支持的算法列表、压缩方法、最高协议版本等参数传递给服务器。
2、服务器接收到请求后也返回一些连接参数给客户端。
3、双方此时都获得了对方的参数,于是开始交换证书(通常基于X.509证书格式)。
4、客户端有证书就可以形成双向认证,需要把对方的证书导入自己的信任库,如果客户端没有证书就随机生成公钥,双方获得对方的公钥之后生成自己的私钥。
5、双方通过公钥加密,私钥解密传输数据。