公钥加解密及SSL/TLS协议概述

来源:互联网 发布:淘宝怎样一键上传 编辑:程序博客网 时间:2024/06/06 04:57
公开密钥加密,也称为非对称加密,一种密码学算法类型,在这种密码学方法中,需要一对密钥,一个是私人密钥,另一个则是公开密钥。这两个密钥是数学相关。用某用户的密钥加密后所得的信息,只能用该用户的解密密钥才能解密。如果知道了其中一个,并不能计算出另外一个。因此如果公开了一对密钥中的一个,并不会危害到另外一个的秘密性质。
如果加密密钥是公开的,这用于给私钥所有者上传加密的数据,这被称作为公开密钥加密。
如果解密密钥是公开的,用私钥加密的信息,可以用公钥对其解密,用户客户验证持有私钥一方发布的数据或文件是完整准确的,接收者由此可知这条信息确实来自于拥有私钥的某人,这被称作数字签名,公钥的形式就是数字证书。

与对称密钥加密相比,公钥加密无需共享通用密钥,解密的私钥不发往任何用户。即使公钥在网上被截获,如果没有与其匹配的私钥,也无法解密,所截获的公钥是没有任何用处的。

但公钥加密只能加密少量的数据,因此,一个完整的密码体系,往往通过公钥加密来创建私有密钥,然后用私有密钥通过对称密码学加密大量数据。公钥加密的另一个功能 —— 数字签名 ,则是对称密钥加密无法实现的。

Java实现公钥(RSA)加密

RSA公钥与私钥的产生:
假设Alice想要通过一个不可靠的媒体接收Bob的一条私人信息。她可以用以下的方式来产生一个公钥和一个私钥:
1.随意选择两个大的指数p和q,p不等于q,就是那N=p*q。
根据欧拉函数,求得r=(p-1)(q-1)
3.选择一个小于r的整数e,求得e关于模r的模反元素,命名为d.(模反元素存在,当且仅当e与r互质)
4.将p和q的记录销毁。
(N,e)是公钥,(N,d)是私钥,Alice将他的公钥(N,e)传给Bob,而将她的私钥(N,d)藏起来。

import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.FileReader;import java.io.FileWriter;import java.io.IOException;import java.security.InvalidKeyException;import java.security.KeyFactory;import java.security.KeyPair;import java.security.KeyPairGenerator;import java.security.NoSuchAlgorithmException;import java.security.SecureRandom;import java.security.interfaces.RSAPrivateKey;import java.security.interfaces.RSAPublicKey;import java.security.spec.InvalidKeySpecException;import java.security.spec.PKCS8EncodedKeySpec;import java.security.spec.X509EncodedKeySpec;import javax.crypto.BadPaddingException;import javax.crypto.Cipher;import javax.crypto.IllegalBlockSizeException;import javax.crypto.NoSuchPaddingException;import com.sun.org.apache.xml.internal.security.utils.Base64;      public class RSAEncrypt {      /**      * 字节数据转字符串专用集合      */      private static final char[] HEX_CHAR = { '0', '1', '2', '3', '4', '5', '6',              '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };        /**      * 随机生成密钥对      */      public static void genKeyPair(String filePath) {          // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象          KeyPairGenerator keyPairGen = null;          try {              keyPairGen = KeyPairGenerator.getInstance("RSA");          } catch (NoSuchAlgorithmException e) {              // TODO Auto-generated catch block              e.printStackTrace();          }          // 初始化密钥对生成器,密钥大小为96-1024位          keyPairGen.initialize(1024,new SecureRandom());          // 生成一个密钥对,保存在keyPair中          KeyPair keyPair = keyPairGen.generateKeyPair();          // 得到私钥          RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();          // 得到公钥          RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();          try {              // 得到公钥字符串              String publicKeyString = Base64.encode(publicKey.getEncoded());              // 得到私钥字符串              String privateKeyString = Base64.encode(privateKey.getEncoded());              // 将密钥对写入到文件              FileWriter pubfw = new FileWriter(filePath + "/publicKey.keystore");              FileWriter prifw = new FileWriter(filePath + "/privateKey.keystore");              BufferedWriter pubbw = new BufferedWriter(pubfw);              BufferedWriter pribw = new BufferedWriter(prifw);              pubbw.write(publicKeyString);              pribw.write(privateKeyString);              pubbw.flush();              pubbw.close();              pubfw.close();              pribw.flush();              pribw.close();              prifw.close();          } catch (Exception e) {              e.printStackTrace();          }      }        /**      * 从文件中输入流中加载公钥      *       * @param in      *            公钥输入流      * @throws Exception      *             加载公钥时产生的异常      */      public static String loadPublicKeyByFile(String path) throws Exception {          try {              BufferedReader br = new BufferedReader(new FileReader(path                      + "/publicKey.keystore"));              String readLine = null;              StringBuilder sb = new StringBuilder();              while ((readLine = br.readLine()) != null) {                  sb.append(readLine);              }              br.close();              return sb.toString();          } catch (IOException e) {              throw new Exception("公钥数据流读取错误");          } catch (NullPointerException e) {              throw new Exception("公钥输入流为空");          }      }        /**      * 从字符串中加载公钥      *       * @param publicKeyStr      *            公钥数据字符串      * @throws Exception      *             加载公钥时产生的异常      */      public static RSAPublicKey loadPublicKeyByStr(String publicKeyStr)              throws Exception {          try {              byte[] buffer = Base64.decode(publicKeyStr);              KeyFactory keyFactory = KeyFactory.getInstance("RSA");              X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);              return (RSAPublicKey) keyFactory.generatePublic(keySpec);          } catch (NoSuchAlgorithmException e) {              throw new Exception("无此算法");          } catch (InvalidKeySpecException e) {              throw new Exception("公钥非法");          } catch (NullPointerException e) {              throw new Exception("公钥数据为空");          }      }        /**      * 从文件中加载私钥      *       * @param keyFileName      *            私钥文件名      * @return 是否成功      * @throws Exception      */      public static String loadPrivateKeyByFile(String path) throws Exception {          try {              BufferedReader br = new BufferedReader(new FileReader(path                      + "/privateKey.keystore"));              String readLine = null;              StringBuilder sb = new StringBuilder();              while ((readLine = br.readLine()) != null) {                  sb.append(readLine);              }              br.close();              return sb.toString();          } catch (IOException e) {              throw new Exception("私钥数据读取错误");          } catch (NullPointerException e) {              throw new Exception("私钥输入流为空");          }      }        public static RSAPrivateKey loadPrivateKeyByStr(String privateKeyStr)              throws Exception {          try {              byte[] buffer = Base64.decode(privateKeyStr);              PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);              KeyFactory keyFactory = KeyFactory.getInstance("RSA");              return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);          } catch (NoSuchAlgorithmException e) {              throw new Exception("无此算法");          } catch (InvalidKeySpecException e) {              throw new Exception("私钥非法");          } catch (NullPointerException e) {              throw new Exception("私钥数据为空");          }      }        /**      * 公钥加密过程      *       * @param publicKey      *            公钥      * @param plainTextData      *            明文数据      * @return      * @throws Exception      *             加密过程中的异常信息      */      public static byte[] encrypt(RSAPublicKey publicKey, byte[] plainTextData)              throws Exception {          if (publicKey == null) {              throw new Exception("加密公钥为空, 请设置");          }          Cipher cipher = null;          try {              // 使用默认RSA              cipher = Cipher.getInstance("RSA");              // cipher= Cipher.getInstance("RSA", new BouncyCastleProvider());              cipher.init(Cipher.ENCRYPT_MODE, publicKey);              byte[] output = cipher.doFinal(plainTextData);              return output;          } catch (NoSuchAlgorithmException e) {              throw new Exception("无此加密算法");          } catch (NoSuchPaddingException e) {              e.printStackTrace();              return null;          } catch (InvalidKeyException e) {              throw new Exception("加密公钥非法,请检查");          } catch (IllegalBlockSizeException e) {              throw new Exception("明文长度非法");          } catch (BadPaddingException e) {              throw new Exception("明文数据已损坏");          }      }        /**      * 私钥加密过程      *       * @param privateKey      *            私钥      * @param plainTextData      *            明文数据      * @return      * @throws Exception      *             加密过程中的异常信息      */      public static byte[] encrypt(RSAPrivateKey privateKey, byte[] plainTextData)              throws Exception {          if (privateKey == null) {              throw new Exception("加密私钥为空, 请设置");          }          Cipher cipher = null;          try {              // 使用默认RSA              cipher = Cipher.getInstance("RSA");              cipher.init(Cipher.ENCRYPT_MODE, privateKey);              byte[] output = cipher.doFinal(plainTextData);              return output;          } catch (NoSuchAlgorithmException e) {              throw new Exception("无此加密算法");          } catch (NoSuchPaddingException e) {              e.printStackTrace();              return null;          } catch (InvalidKeyException e) {              throw new Exception("加密私钥非法,请检查");          } catch (IllegalBlockSizeException e) {              throw new Exception("明文长度非法");          } catch (BadPaddingException e) {              throw new Exception("明文数据已损坏");          }      }        /**      * 私钥解密过程      *       * @param privateKey      *            私钥      * @param cipherData      *            密文数据      * @return 明文      * @throws Exception      *             解密过程中的异常信息      */      public static byte[] decrypt(RSAPrivateKey privateKey, byte[] cipherData)              throws Exception {          if (privateKey == null) {              throw new Exception("解密私钥为空, 请设置");          }          Cipher cipher = null;          try {              // 使用默认RSA              cipher = Cipher.getInstance("RSA");              // cipher= Cipher.getInstance("RSA", new BouncyCastleProvider());              cipher.init(Cipher.DECRYPT_MODE, privateKey);              byte[] output = cipher.doFinal(cipherData);              return output;          } catch (NoSuchAlgorithmException e) {              throw new Exception("无此解密算法");          } catch (NoSuchPaddingException e) {              e.printStackTrace();              return null;          } catch (InvalidKeyException e) {              throw new Exception("解密私钥非法,请检查");          } catch (IllegalBlockSizeException e) {              throw new Exception("密文长度非法");          } catch (BadPaddingException e) {              throw new Exception("密文数据已损坏");          }      }        /**      * 公钥解密过程      *       * @param publicKey      *            公钥      * @param cipherData      *            密文数据      * @return 明文      * @throws Exception      *             解密过程中的异常信息      */      public static byte[] decrypt(RSAPublicKey publicKey, byte[] cipherData)              throws Exception {          if (publicKey == null) {              throw new Exception("解密公钥为空, 请设置");          }          Cipher cipher = null;          try {              // 使用默认RSA              cipher = Cipher.getInstance("RSA");              // cipher= Cipher.getInstance("RSA", new BouncyCastleProvider());              cipher.init(Cipher.DECRYPT_MODE, publicKey);              byte[] output = cipher.doFinal(cipherData);              return output;          } catch (NoSuchAlgorithmException e) {              throw new Exception("无此解密算法");          } catch (NoSuchPaddingException e) {              e.printStackTrace();              return null;          } catch (InvalidKeyException e) {              throw new Exception("解密公钥非法,请检查");          } catch (IllegalBlockSizeException e) {              throw new Exception("密文长度非法");          } catch (BadPaddingException e) {              throw new Exception("密文数据已损坏");          }      }        /**      * 字节数据转十六进制字符串      *       * @param data      *            输入数据      * @return 十六进制内容      */      public static String byteArrayToString(byte[] data) {          StringBuilder stringBuilder = new StringBuilder();          for (int i = 0; i < data.length; i++) {              // 取出字节的高四位 作为索引得到相应的十六进制标识符 注意无符号右移              stringBuilder.append(HEX_CHAR[(data[i] & 0xf0) >>> 4]);              // 取出字节的低四位 作为索引得到相应的十六进制标识符              stringBuilder.append(HEX_CHAR[(data[i] & 0x0f)]);              if (i < data.length - 1) {                  stringBuilder.append(' ');              }          }          return stringBuilder.toString();      }  }  
测试类

import com.sun.org.apache.xml.internal.security.utils.Base64; public class Main {public static void main(String[] args) throws Exception{String filepath="F:/";//RSAEncrypt.genKeyPair(filepath);String plainText="公钥加密私钥解密";//公钥加密过程          byte[] cipherData=RSAEncrypt.encrypt(RSAEncrypt.loadPublicKeyByStr(RSAEncrypt.loadPublicKeyByFile(filepath)),plainText.getBytes());          String cipher=Base64.encode(cipherData);          //私钥解密过程          byte[] res=RSAEncrypt.decrypt(RSAEncrypt.loadPrivateKeyByStr(RSAEncrypt.loadPrivateKeyByFile(filepath)), Base64.decode(cipher));          String restr=new String(res);          System.out.println("原文:"+plainText);          System.out.println("加密:"+cipher);          System.out.println("解密:"+restr);          System.out.println();  }    }
密钥分配
和其他加密过程一样,对RSA来说分配公钥的过程是非常重要的。分配公钥的过程必须能够抵挡中间人攻击。假设Eve交给Bob一个公钥,并使Bob相信这是Alice的公钥,并且它可以接下Alice和Bob之间的信息传递,那么它可以将它自己的公钥传给Bob,Bob以为这是Alice的公钥。Eve可以将所有Bob传递给Alice的消息截下来,将这个消息用它自己的密钥解密,读这个消息,然后将这个消息用Alice的公钥加密后传给Alice。理论上Alice和Bob都不会发现Eve在偷听他们的消息。一般用可靠的第三方机构签发证书来防止这样的攻击。
TLS/SSL协议的基本思路是采用公钥加密法,也就是说,客户端先向服务器端索要公钥,然后用公钥加密信息。服务器收到密文后,用自己的私钥解密。

SSL/TLS协议的基本过程
(1)客户端向服务器索要并验证公钥。
(2)双方协商生成“对话密钥”。
(3)双方采用“对话密钥”进行加密通信。
保证公钥不被篡改:将公钥放在数字证书中。只要证书是可信的,公钥就是可信的。
减少耗用时间:每一次对话(Session),客户端和服务器都生成一个“对话密钥(Session Key)”,用它来加密信息。由于“对话密钥”是对称加密,所以运算速度非常快,而服务器公钥只用于加密“对话密钥”本身,这样就减少了加密运算的消耗时间。
1.客户端发出请求(ClientHello)
首先,客户端向服务器发出加密同音的请求,在这一步客户端主要向服务器提供一下信息。
(1)支持的协议版本
(2)一个客户端生成的随机数,稍后用于生成“对话密钥”
(3)支持的加密方法
(4)支持的压缩算饭
2.服务器回应(ServerHello)
服务器收到客户端请求后,向客户端发出回应。服务器的回应:
(1)确认使用的加密通信协议版本
(2)一个服务器生成的随机数,稍后用于生成“对话密钥”
(3)确认使用的加密方法
(4)服务器证书
3.客户端回应
客户端收到服务器回应以后,首先验证服务器证书。
如果证书没有问题,客户端就会从证书中取出服务器的公钥。然后,向服务器发送下面三项信息。
(1)一个随机数。该随机数用服务器公钥加密,防止被窃听。
(2)编码改变通知,表示随后的信息都将从双方商定的加密方法和密钥发送
(3)客户端握手通知,表示客户端的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来提供服务器校验。
上面第一项的随机数,是整个握手阶段出现的第三个随机数,又称“pre-master key”。有了它以后,客户端和服务器就同时有了三个随机数,接着双方就用事先商定的加密方法,各自生成本次会话所用的用一把“会话密钥”。
三个随机数:不管是客户端还是服务器,都需要随机数,这样生成的密钥才不会每次都一样。由于SSL协议中证书是静态的,因此十分有必要引入一种随机因素来保证协商出来的密钥的随机性。
对于RSA密钥交换算法来说,pre-master-key本身就是一个随机数,再加上hello消息中的随机,三个随机数通过一个密钥导出器最终导出一个对称密钥。
pre-master的存在在于SSL协议不信任每个主机都能产生完全随机的随机数,如果随机数不随机,那么pre-master-secret就有可能被猜出来,那么仅适用pre master secret作为密钥就不合适了,因此必须引入新的随机元素,那么客户端和服务器加上pre master secret三个随机数一同生成的密钥就不容易被猜出了,一个伪随机可能完全不随机,可是三个伪随机就十分接近随机了,每增加一个自由度,随机性增加的可不是一。
4.服务器的最后回应
服务器收到客户端的第三个随机数pre-master key之后,计算生成本次会话所用的“会话密钥”。然后,向客户端最后发送下面信息。
(1)编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
(2)服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来提供客户端校验。