在HTTP协议中,信息是明文传输的,因此为了通信安全就有了HTTPS(Hyper Text Transfer Protocol over Secure Socket Layer)协议。HTTPS也是一种超文本传送协议,在HTTP的基础上加入了SSL/TLS协议,SSL/TLS依靠证书来验证服务端的身份,并为浏览器和服务端之间的通信加密。

12.1 什么是SSL/TLS

12.1.1 SSL/TLS协议的版本演进

12.1.2 SSL/TLS协议的分层结构

image.png

12.2 加密算法原理与实战

12.2.1 哈希单向加密算法原理与实战

以下代码使用Java提供的MD5、SHA1、SHA256、SHA512等哈希摘要函数生成哈希摘要(哈希加密结果)并进行验证的案例:

  1. package com.crazymakercircle.secure.crypto;
  2. //省略import
  3. public class HashCrypto
  4. {
  5. /**
  6. * 哈希单向加密测试用例
  7. */
  8. public static String encrypt(String plain)
  9. {
  10. StringBuffer md5Str = new StringBuffer(32);
  11. try
  12. {
  13. /**
  14. * MD5
  15. */
  16. //MessageDigest md = MessageDigest.getInstance("MD5");
  17. /**
  18. * SHA-1
  19. */
  20. //MessageDigest md = MessageDigest.getInstance("SHA-1");
  21. /**
  22. * SHA-256
  23. */
  24. //MessageDigest md = MessageDigest.getInstance("SHA-256");
  25. /**
  26. * SHA-512
  27. */
  28. MessageDigest md = MessageDigest.getInstance("SHA-512");
  29. String charset = "UTF-8";
  30. byte[] array = md.digest(plain.getBytes(charset));
  31. for (int i = 0; i < array.length; i++)
  32. {
  33. //转成十六进制字符串
  34. String hexString = Integer.toHexString(
  35. (0x000000FF & array[i]) | 0xFFFFFF00);
  36. log.debug("hexString:{}, 第6位之后: {}",
  37. hexString, hexString.substring(6));
  38. md5Str.append(hexString.substring(6));
  39. }
  40. } catch (Exception ex)
  41. {
  42. ex.printStackTrace();
  43. }
  44. return md5Str.toString();
  45. }
  46. public static void main(String[] args)
  47. {
  48. //原始的明文字符串,也是需要加密的对象
  49. String plain = "123456";
  50. //使用哈希函数加密
  51. String cryptoMessage = HashCrypto.encrypt(plain);
  52. log.info("cryptoMessage:{}", cryptoMessage);
  53. //验证
  54. String cryptoMessage2 = HashCrypto.encrypt(plain);
  55. log.info("验证 {},\n是否一致:{}", cryptoMessage2,
  56. cryptoMessage.equals(cryptoMessage2));
  57. //验证2
  58. String plainOther = "654321";
  59. String cryptoMessage3 = HashCrypto.encrypt(plainOther);
  60. log.info("验证 {},\n是否一致:{}", cryptoMessage3,
  61. cryptoMessage.equals(cryptoMessage3));
  62. }
  63. }

12.2.2 对称加密算法原理与实战

下面是一段使用Java语言编写的进行DES加密的演示代码:

  1. package com.crazymakercircle.secure.crypto;
  2. //省略import
  3. public class DESCrypto
  4. {
  5. /**
  6. * 对称加密
  7. */
  8. public static byte[] encrypt(byte[] data, String password) {
  9. try{
  10. SecureRandom random = new SecureRandom();
  11. //使用密码,创建一个密钥描述符
  12. DESKeySpec desKey = new DESKeySpec(password.getBytes());

12.2.3 非对称加密算法原理与实战

image.png
下面是一段使用Java代码进行RSA加密的演示代码:

  1. package com.crazymakercircle.secure.crypto;
  2. //省略import
  3. /**
  4. * RSA 非对称加密算法
  5. */
  6. @Slf4j
  7. public class RSAEncrypt
  8. {
  9. /**
  10. * 指定加密算法为RSA
  11. */
  12. private static final String ALGORITHM = "RSA";
  13. /**
  14. * 常量,用来初始化密钥长度
  15. */
  16. private static final int KEY_SIZE = 1024;
  17. /**
  18. * 指定公钥存放文件
  19. */
  20. private static final String PUBLIC_KEY_FILE =
  21. SystemConfig.getKeystoreDir() + "/PublicKey";
  22. /**
  23. * 指定私钥存放文件
  24. */
  25. private static final String PRIVATE_KEY_FILE =
  26. SystemConfig.getKeystoreDir() + "/PrivateKey";
  27. /**
  28. * 生成密钥对
  29. */
  30. protected static void generateKeyPair() throws Exception
  31. {
  32. /**
  33. * 为RSA算法创建一个KeyPairGenerator对象
  34. */
  35. KeyPairGenerator keyPairGenerator =
  36. KeyPairGenerator.getInstance(ALGORITHM);
  37. /**
  38. * 利用上面的密钥长度初始化这个KeyPairGenerator对象
  39. */
  40. keyPairGenerator.initialize(KEY_SIZE);
  41. /** 生成密钥对 */
  42. KeyPair keyPair = keyPairGenerator.generateKeyPair();
  43. /** 得到公钥 */
  44. PublicKey publicKey = keyPair.getPublic();
  45. /** 得到私钥 */
  46. PrivateKey privateKey = keyPair.getPrivate();
  47. ObjectOutputStream oos1 = null;
  48. ObjectOutputStream oos2 = null;
  49. try
  50. {
  51. log.info("生成公钥和私钥,并且写入对应的文件");
  52. File file = new File(PUBLIC_KEY_FILE);
  53. if (file.exists())
  54. {
  55. log.info("公钥和私钥已经生成,不需要重复生成,
  56. path:{}", PUBLIC_KEY_FILE);
  57. return;
  58. }
  59. /** 用对象流将生成的密钥写入文件 */
  60. log.info("PUBLIC_KEY_FILE 写入:{}", PUBLIC_KEY_FILE);
  61. oos1 = new ObjectOutputStream(
  62. new FileOutputStream(PUBLIC_KEY_FILE));
  63. log.info("PRIVATE_KEY_FILE 写入:{}", PRIVATE_KEY_FILE);
  64. oos2 = new ObjectOutputStream(
  65. new FileOutputStream(PRIVATE_KEY_FILE));
  66. oos1.writeObject(publicKey);
  67. oos2.writeObject(privateKey);
  68. } catch (Exception e)
  69. {
  70. throw e;
  71. } finally
  72. {
  73. /** 清空缓存,关闭文件输出流 */
  74. IOUtil.closeQuietly(oos1);
  75. IOUtil.closeQuietly(oos2);
  76. }
  77. }
  78. /**
  79. * 加密方法,使用公钥加密
  80. * @param plain 明文数据
  81. */
  82. public static String encrypt(String plain) throws Exception
  83. {
  84. //从文件加载公钥
  85. Key publicKey = loadPublicKey();
  86. /** 得到Cipher对象,来实现对源数据的RSA加密 */
  87. Cipher cipher = Cipher.getInstance(ALGORITHM);
  88. cipher.init(Cipher.ENCRYPT_MODE, publicKey);
  89. byte[] b = plain.getBytes();
  90. /** 执行加密操作 */
  91. byte[] b1 = cipher.doFinal(b);
  92. BASE64Encoder encoder = new BASE64Encoder();
  93. return encoder.encode(b1);
  94. }
  95. /**
  96. * 从文件加载公钥
  97. */
  98. public static PublicKey loadPublicKey() throws Exception
  99. {
  100. PublicKey publicKey=null;
  101. ObjectInputStream ois = null;
  102. try
  103. {
  104. log.info("PUBLIC_KEY_FILE 读取:{}", PUBLIC_KEY_FILE);
  105. /** 读出文件中的公钥 */
  106. ois = new ObjectInputStream(
  107. new FileInputStream(PUBLIC_KEY_FILE));
  108. publicKey = (PublicKey) ois.readObject();
  109. } catch (Exception e)
  110. {
  111. throw e;
  112. } finally
  113. {
  114. IOUtil.closeQuietly(ois);
  115. }
  116. return publicKey;
  117. }
  118. //方法:对密文解密,使用私钥解密
  119. public static String decrypt(String crypto) throws Exception
  120. {
  121. PrivateKey privateKey = loadPrivateKey();
  122. /** 得到Cipher对象,对已用公钥加密的数据进行RSA解密 */
  123. Cipher cipher = Cipher.getInstance(ALGORITHM);
  124. cipher.init(Cipher.DECRYPT_MODE, privateKey);
  125. BASE64Decoder decoder = new BASE64Decoder();
  126. byte[] b1 = decoder.decodeBuffer(crypto);
  127. /** 执行解密操作 */
  128. byte[] b = cipher.doFinal(b1);
  129. return new String(b);
  130. }
  131. /**
  132. * 从文件加载私钥
  133. * @throws Exception
  134. */
  135. public static PrivateKey loadPrivateKey() throws Exception
  136. {
  137. PrivateKey privateKey;
  138. ObjectInputStream ois = null;
  139. try
  140. {
  141. log.info("PRIVATE_KEY_FILE 读取:{}", PRIVATE_KEY_FILE);
  142. /** 读出文件中的私钥 */
  143. ois = new ObjectInputStream(
  144. new FileInputStream(PRIVATE_KEY_FILE));
  145. privateKey = (PrivateKey) ois.readObject();
  146. } catch (Exception e)
  147. {
  148. e.printStackTrace();
  149. throw e;
  150. } finally
  151. {
  152. IOUtil.closeQuietly(ois);
  153. }
  154. return privateKey;
  155. }
  156. public static void main(String[] args) throws Exception
  157. {
  158. //生成密钥对
  159. generateKeyPair();
  160. //待加密内容
  161. String plain = "疯狂创客圈 Java 高并发研习社群";
  162. //公钥加密
  163. String dest = encrypt(plain);
  164. log.info("{} 使用公钥加密后:\n{}", plain, dest);
  165. //私钥解密
  166. String decrypted = decrypt(dest);
  167. log.info(" 使用私钥解密后:\n{}", decrypted);
  168. }
  169. }

12.2.4 数字签名原理与实战

下面是一段使用JSHA512withRSA算法实现数字签名的Java演示代码:

  1. package com.crazymakercircle.secure.crypto;
  2. //省略import
  3. /**
  4. * RSA签名演示
  5. */
  6. @Slf4j
  7. public class RSASignDemo
  8. {
  9. /**
  10. * RSA签名
  11. *
  12. * @param data 待签名的字符串
  13. * @param priKey RSA私钥字符串
  14. * @return 签名结果
  15. * @throws Exception 签名失败则抛出异常
  16. */
  17. public byte[] rsaSign(byte[] data, PrivateKey priKey)
  18. throws SignatureException
  19. {
  20. try
  21. {
  22. Signature signature = Signature.getInstance("SHA512withRSA");
  23. signature.initSign(priKey);
  24. signature.update(data);
  25. byte[] signed = signature.sign();
  26. return signed;
  27. } catch (Exception e)
  28. {
  29. throw new SignatureException("RSAcontent = " + data
  30. + "; charset = ", e);
  31. }
  32. }
  33. /**
  34. * RSA验签
  35. * @param data 被签名的内容
  36. * @param sign 签名后的结果
  37. * @param pubKey RSA公钥
  38. * @return 验签结果
  39. */
  40. public boolean verify(byte[] data, byte[] sign, PublicKey pubKey)
  41. throws SignatureException
  42. {
  43. try
  44. {
  45. Signature signature = Signature.getInstance("SHA512withRSA");
  46. signature.initVerify(pubKey);
  47. signature.update(data);
  48. return signature.verify(sign);
  49. } catch (Exception e)
  50. {
  51. e.printStackTrace();
  52. throw new SignatureException("RSA验证签名[content = " + data+
  53. "; charset = " + "; signature = " + sign + "]发生异常!", e);
  54. }
  55. }
  56. /**
  57. * 私钥
  58. */
  59. private PrivateKey privateKey;
  60. /**
  61. * 公钥
  62. */
  63. private PublicKey publicKey;
  64. /**
  65. * 加密过程
  66. * @param publicKey 公钥
  67. * @param plainTextData 明文数据
  68. * @throws Exception 加密过程中的异常信息
  69. */
  70. public byte[] encrypt(PublicKey publicKey, byte[] plainTextData)
  71. throws Exception
  72. {
  73. if (publicKey == null)
  74. {
  75. throw new Exception("加密公钥为空, 请设置");
  76. }
  77. Cipher cipher = null;
  78. try
  79. {
  80. cipher = Cipher.getInstance("RSA");
  81. cipher.init(Cipher.ENCRYPT_MODE, publicKey);
  82. byte[] output = cipher.doFinal(plainTextData);
  83. return output;
  84. } catch (NoSuchAlgorithmException e)
  85. {
  86. throw new Exception("无此加密算法");
  87. }
  88. }
  89. /**
  90. * 解密过程
  91. * @param privateKey 私钥
  92. * @param cipherData 密文数据
  93. * @return 明文
  94. * @throws Exception 解密过程中的异常信息
  95. */
  96. public byte[] decrypt(PrivateKey privateKey, byte[] cipherData)…{
  97. if (privateKey == null)
  98. {
  99. throw new Exception("解密私钥为空, 请设置");
  100. }
  101. Cipher cipher = null;
  102. try
  103. {
  104. cipher = Cipher.getInstance("RSA");
  105. cipher.init(Cipher.DECRYPT_MODE, privateKey);
  106. byte[] output = cipher.doFinal(cipherData);
  107. return output;
  108. } catch (NoSuchAlgorithmException e)
  109. {
  110. throw new Exception("无此解密算法");
  111. }
  112. }
  113. /**
  114. * Main 测试方法
  115. * @param args
  116. */
  117. public static void main(String[] args) throws Exception
  118. {
  119. RSASignDemo RSASignDemo = new RSASignDemo();
  120. //加载公钥
  121. RSASignDemo.publicKey = RSAEncrypt.loadPublicKey();
  122. //加载私钥
  123. RSASignDemo.privateKey = RSAEncrypt.loadPrivateKey();
  124. //测试字符串
  125. String sourceText = "疯狂创客圈 Java 高并发社群";
  126. try
  127. {
  128. log.info("加密前的字符串为:{}", sourceText);
  129. //公钥加密
  130. byte[] cipher = RSASignDemo.encrypt(
  131. RSASignDemo.publicKey, sourceText.getBytes());
  132. //私钥解密
  133. byte[] decryptText = RSASignDemo.decrypt(
  134. RSASignDemo.privateKey, cipher);
  135. log.info("私钥解密的结果是:{}", new String(decryptText));
  136. //字符串生成签名
  137. byte[] rsaSign = RSASignDemo.rsaSign(
  138. sourceText.getBytes(), RSASignDemo.privateKey);
  139. //签名验证
  140. Boolean succeed = RSASignDemo.verify(sourceText.getBytes(),
  141. rsaSign, RSASignDemo.publicKey);
  142. log.info("字符串签名为:\n{}", byteToHex(rsaSign));
  143. log.info("签名验证结果是:{}", succeed);
  144. String fileName =
  145. IOUtil.getResourcePath("/system.properties");
  146. byte[] fileBytes = readFileByBytes(fileName);
  147. //文件签名验证
  148. byte[] fileSign =
  149. RSASignDemo.rsaSign(fileBytes, RSASignDemo.privateKey);
  150. log.info("文件签名为:\n{}" , byteToHex(fileSign));
  151. //文件签名保存
  152. String signPath =
  153. SystemConfig.getKeystoreDir() + "/fileSign.sign";
  154. ByteUtil.saveFile(fileSign,signPath );
  155. Boolean verifyOK = RSASignDemo.verify(
  156. fileBytes, fileSign, RSASignDemo.publicKey);
  157. log.info("文件签名验证结果是:{}", verifyOK);
  158. //读取验证文件
  159. byte[] read = readFileByBytes(signPath);
  160. log.info("读取文件签名:\n{}" , byteToHex(read));
  161. verifyOK= RSASignDemo.verify(
  162. fileBytes, read, RSASignDemo.publicKey);
  163. log.info("读取文件签名验证结果是:{}", verifyOK);
  164. } catch (Exception e)
  165. {
  166. System.err.println(e.getMessage());
  167. }
  168. }
  169. }

12.3 SSL/TLS运行过程

SSL/TLS协议运行的基本流程如下:

  1. 客户端向服务端索要并验证公钥。
  2. 双方协商生成“对话密钥”。
  3. 双方采用“对话密钥”进行加密通信。

前两步又称为“握手阶段”,每一个TLS连接都会以握手开始。“握手阶段”涉及四次通信,并且所有通信都是明文的。在握手过程中,客户端和服务端将进行以下四个主要阶段:

  1. 交换各自支持的加密套件和参数,经过协商后,双方就加密套件和参数达成一致。
  2. 验证对方(主要指服务端)的证书,或使用其他方式进行服务端身份验证
  3. 对将用于保护会话的共享主密钥达成一致
  4. 验证握手消息是否被第三方修改

    12.3.1 SSL/TLS第一阶段握手

    SSL/TLS“握手”第一个阶段的工作为:由客户端发一个Client Hello报文给服务端,并且第一个阶段只有这一个数据帧(报文)。Client Hello数据帧的内容大致包括以下信息:

  5. 客户端支持的SSL/TLS协议版本,比如TLS 1.2版。

  6. 一个客户端生成的随机数,这是握手过程中的第一个随机数,称之为Random_C
  7. 客户端支持的签名算法、加密方法、摘要算法(比如RSA公钥签名算法)。
  8. 客户端支持的压缩方法

    12.3.2 SSL/TLS第二阶段握手

    SSL/TLS握手第二个阶段的工作为:服务端对客户端的Client Hello请求进行响应。在收到客户端请求(Client Hello)后,服务端向客户端发出回应,这个阶段的服务端回应帧(报文)一般包含4个回复帧:Server Hello帧、Certificate帧、Server Key Exchange帧、Server Hello Done帧。

    12.3.3 SSL/TLS第三阶段握手

    12.3.4 SSL/TLS第四阶段握手

    12.4 详解Keytool工具

    SSL/TSL在握手过程中,客户端需要服务端提供身份证书(也叫数字证书),有的场景下甚至要求客户端也提供身份证书。安全数字证书主要包含自己的身份信息(如所有人的名称),以及对外的公钥。

    12.4.1 数字证书与身份识别

    image.png

    12.4.2 存储密钥与证书文件格式

    12.4.3 使用Keytool工具管理密钥和证书

    12.5 使用Java程序管理密钥与证书

    12.5.1 使用Java操作数据证书所涉及的核心类

    12.5.2 使用Java程序创建密钥与仓库

    ```java keytool -genkey -alias server -keypass 123456 -keyalg RSA -keysize 2048 -validity 365 -keystore f:\server.jks -storepass 123456 -dname “CN=server”
  1. 这里实现了一个KeyStoreHelper帮助类,用于帮助创建密钥和证书,并且保存到密钥仓库文件,其代码节选如下:
  2. ```java
  3. package com.crazymakercircle.keystore;
  4. //省略import
  5. public class KeyStoreHelper
  6. {
  7. private static final byte[] CRLF = new byte[]{'\r', '\n'};
  8. /**
  9. * 存储密钥仓库的文件
  10. */
  11. private String keyStoreFile;
  12. /**
  13. * 获取KeyStore信息所需的密码
  14. */
  15. private String storePass;
  16. /**
  17. * 设置指定别名条目的密码,也就是私钥原始密码
  18. */
  19. private String keyPass;
  20. /**
  21. * 每个KeyStore都关联一个独一无二的别名,这个别名通常不区分大小写
  22. */
  23. private String alias;
  24. /**
  25. * 指定证书拥有者信息
  26. * 例如:"CN=名字与姓氏,OU=组织单位名称,O=组织名称,L=城市或区域名称,ST=州或省份名称,C=用两字母代表的国家或地区代码"
  27. */
  28. private String dname ;
  29. KeyStore keyStore;
  30. private static String keyType = "JKS";
  31. public KeyStoreHelper(String keyStoreFile, String storePass,
  32. String keyPass, String alias, String dname)
  33. {
  34. this.keyStoreFile = keyStoreFile;
  35. this.storePass = storePass;
  36. this.keyPass = keyPass;
  37. this.alias = alias;
  38. this.dname = dname;
  39. }
  40. /**
  41. * 创建密钥和证书并且保存到密钥仓库文件
  42. */
  43. public void createKeyEntry() throws Exception
  44. {
  45. KeyStore keyStore = loadStore();
  46. CertHelper certHelper = new CertHelper(dname);
  47. /**
  48. * 生成证书
  49. */
  50. Certificate cert = certHelper.genCert();
  51. cert.verify(certHelper.getKeyPair().getPublic());
  52. PrivateKey privateKey = certHelper.getKeyPair().getPrivate();
  53. //访问仓库时需要用到仓库密码
  54. char[] caPasswordArray = storePass.toCharArray();
  55. /**
  56. * 设置密钥和证书到密钥仓库
  57. */
  58. keyStore.setKeyEntry(alias, privateKey,
  59. caPasswordArray, new Certificate[]{cert});
  60. FileOutputStream fos = null;
  61. try
  62. {
  63. fos = new java.io.FileOutputStream(keyStoreFile);
  64. /**
  65. * 密钥仓库保存到文件
  66. */
  67. keyStore.store(fos, caPasswordArray);
  68. } finally
  69. {
  70. closeQuietly(fos);
  71. }
  72. }
  73. /**
  74. * 从文件加载KeyStore密钥仓库
  75. */
  76. public KeyStore loadStore() throws Exception
  77. {
  78. log.debug("keyStoreFile: {}", keyStoreFile);
  79. if (!new File(keyStoreFile).exists())
  80. {
  81. createEmptyStore();
  82. }
  83. KeyStore ks = KeyStore.getInstance(keyType);
  84. java.io.FileInputStream fis = null;
  85. try
  86. {
  87. fis = new java.io.FileInputStream(keyStoreFile);
  88. ks.load(fis, storePass.toCharArray());
  89. } finally
  90. {
  91. closeQuietly(fis);
  92. }
  93. return ks;
  94. }
  95. /**
  96. * 建立一个空的KeyStore仓库
  97. */
  98. private void createEmptyStore() throws Exception
  99. {
  100. KeyStore keyStore = KeyStore.getInstance(keyType);
  101. File parentFile = new File(keyStoreFile).getParentFile();
  102. if (!parentFile.exists())
  103. {
  104. parentFile.mkdirs();
  105. }
  106. java.io.FileOutputStream fos = null;
  107. keyStore.load(null, storePass.toCharArray());
  108. try
  109. {
  110. fos = new java.io.FileOutputStream(keyStoreFile);
  111. keyStore.store(fos, storePass.toCharArray());
  112. } finally
  113. {
  114. closeQuietly(fos);
  115. }
  116. }
  117. //…
  118. }

使用此KeyStoreHelper类完成创建服务端(如Netty服务器)密钥并且保存到服务端密钥仓库文件,其代码如下:

  1. package com.crazymakercircle.secure.Test.keyStore;
  2. //省略import
  3. public class ServerKeyStoreTester
  4. {
  5. /**
  6. * 存储密钥的文件
  7. */
  8. private String keyStoreFile=
  9. SystemConfig.getKeystoreDir() + "/server.jks";
  10. /**
  11. * 访问KeyStore时所需的密码
  12. */
  13. private String storePass = "123456";
  14. /**
  15. * 设置指定别名条目的密码,也就是私钥密码
  16. */
  17. private String keyPass = "123456";
  18. /**
  19. * 每个KeyStore都关联一个独一无二的别名,这个别名通常不区分字母大小写
  20. */
  21. private String alias= "server_cert";
  22. /**
  23. * 指定证书拥有者信息
  24. * 例如:"CN=名字与姓氏,OU=组织单位名称,O=组织名称,L=城市或区域名称,ST=州或省份名称,C=用两字母代表的国家或地区代码"
  25. */
  26. private String dname = "C=CN,ST=Province,L=city,O=crazymaker,OU=crazymaker.com,CN=server";
  27. /**
  28. * 创建密钥和证书并且保存到密钥仓库文件
  29. */
  30. @Test
  31. public void testCreateKey() throws Exception
  32. {
  33. KeyStoreHelper keyStoreHelper = new KeyStoreHelper(keyStoreFile,
  34. storePass, keyPass, alias, dname);
  35. //创建密钥和证书
  36. keyStoreHelper.createKeyEntry();
  37. }
  38. /**
  39. * 在服务端仓库,打印仓库的所有证书
  40. */
  41. @Test
  42. public void testPrintEntries() throws Exception
  43. {
  44. String dir = SystemConfig.getKeystoreDir();
  45. log.debug(" client dir = " + dir);
  46. KeyStoreHelper keyStoreHelper = new KeyStoreHelper(
  47. keyStoreFile, storePass, keyPass, alias, dname);
  48. //打印仓库的所有证书
  49. keyStoreHelper.doPrintEntries();
  50. }
  51. //…
  52. }

12.5.3 使用Java程序导出证书文件

  1. keytool -export -alias server -keystore f:/server.jks -storepass 123456 -file server.cer

在帮助类KeyStoreHelper中使用Java代码实现数字证书文件(“.cer”文件)导出的代码,其方法名称为exportCert,代码如下:

  1. package com.crazymakercircle.keystore;
  2. //省略import
  3. public class KeyStoreHelper
  4. {
  5. //省略成员属性
  6. /**
  7. * 导出证书
  8. * @param outDir 导出的目标目录
  9. */
  10. public boolean exportCert(String outDir) throws Exception
  11. {
  12. assert (StringUtils.isNotEmpty(alias));
  13. assert (StringUtils.isNotEmpty(keyPass));
  14. KeyStore ks = loadStore();
  15. 调用此KeyStoreHelper类的exportCert()方法,导出创建服务端(如Netty服务器)密钥的数字证书,其测试用例代码如下:
  16. package com.crazymakercircle.secure.Test.keyStore;
  17. //省略import
  18. @Slf4j
  19. public class ServerKeyStoreTester
  20. {
  21. /**
  22. * 服务端密钥仓库测试用例
  23. */
  24. @Test
  25. public void testExportCert() throws Exception
  26. {
  27. String dir = SystemConfig.getKeystoreDir();
  28. log.debug("dir = " + dir);
  29. KeyStoreHelper keyStoreHelper = new KeyStoreHelper(keyStoreFile,
  30. storePass, keyPass, alias, dname);
  31. boolean ok = keyStoreHelper.exportCert(dir);
  32. log.debug("Export Cert ok = " + ok);
  33. }
  34. }

12.5.4 使用Java程序将数字证书导入信任仓库

  1. keytool -import -trustcacerts -alias server -file server.cer -keystore f:/client.jks -storepass 123456

还是在KeyStoreHelper类中使用Java实现导入数字证书到信任仓库的代码,其方法的名称为importCert,代码如下:

  1. package com.crazymakercircle.keystore;
  2. //省略import
  3. public class KeyStoreHelper
  4. {
  5. //省略成员属性
  6. /**
  7. * 导入数字证书到信任仓库
  8. */
  9. public void importCert(String importAlias, String certPath)…{
  10. if (null == keyStore)
  11. {
  12. keyStore = loadStore();
  13. }
  14. InputStream inStream = null;
  15. if (certPath != null)
  16. {
  17. inStream = new FileInputStream(certPath);
  18. }
  19. //将证书按照别名增加到仓库中
  20. boolean succeed = addTrustedCert(importAlias, inStream);
  21. if (succeed)
  22. {
  23. log.debug("导入成功");
  24. } else
  25. {
  26. log.error("导入失败");
  27. }
  28. }
  29. /**
  30. * 将证书按照别名增加到仓库中
  31. */
  32. private boolean addTrustedCert(String alias, InputStream in)
  33. throws Exception
  34. {
  35. if (alias == null)
  36. {
  37. throw new Exception("Must.specify.alias");
  38. }
  39. //如果别名已经存在,则抛出异常
  40. if (keyStore.containsAlias(alias))
  41. {
  42. throw new Exception("别名已经存在");
  43. }
  44. //从输入流中读取到证书
  45. X509Certificate cert = null;
  46. try
  47. {
  48. cert = (X509Certificate) generateCertificate(in);
  49. } catch (ClassCastException | CertificateException ce)
  50. {
  51. throw new Exception("证书读取失败");
  52. }
  53. //根据别名进行设置
  54. keyStore.setCertificateEntry(alias, cert);
  55. //写回到仓库文件
  56. char[] caPasswordArray = storePass.toCharArray();
  57. java.io.FileOutputStream fos = null;
  58. try
  59. {
  60. fos = new java.io.FileOutputStream(keyStoreFile);
  61. keyStore.store(fos, caPasswordArray);
  62. } finally
  63. {
  64. closeQuietly(fos);
  65. }
  66. return true;
  67. }
  68. }
  69. 调用KeyStoreHelper类的importCert()方法把创建服务端的数字证书导入到客户端的密钥仓库。接下来进行一下自测,其测试用例代码如下:
  70. package com.crazymakercircle.keystore;
  71. //省略import
  72. /**
  73. * 客户端密钥仓库测试类
  74. **/
  75. @Slf4j
  76. @Data
  77. public class ClientKeyStoreTester
  78. {
  79. //省略成员属性
  80. /**
  81. * 在客户端仓库导入服务器的证书
  82. */
  83. @Test
  84. public void testImportServerCert() throws Exception
  85. {
  86. String dir = SystemConfig.getKeystoreDir();
  87. log.debug(" client dir = " + dir);
  88. KeyStoreHelper keyStoreHelper = new KeyStoreHelper(
  89. keyStoreFile, storePass, keyPass, alias, dname);
  90. /**
  91. * 服务器证书的文件
  92. */
  93. String importAlias = "server_cert";
  94. String certPath = SystemConfig.getKeystoreDir() +
  95. "/" + importAlias + ".cer";
  96. //导入服务器证书
  97. keyStoreHelper.importCert(importAlias, certPath);
  98. }
  99. /**
  100. * 在客户端仓库打印仓库的所有证书
  101. */
  102. @Test
  103. public void testPrintEntries() throws Exception
  104. {
  105. String dir = SystemConfig.getKeystoreDir();
  106. log.debug(" client dir = " + dir);
  107. KeyStoreHelper keyStoreHelper = new KeyStoreHelper(
  108. keyStoreFile, storePass, keyPass, alias, dname);
  109. //打印仓库的所有证书
  110. keyStoreHelper.doPrintEntries();
  111. }
  112. }

12.6 OIO通信中的SSL/TLS使用实战