第17课:如何从

第17课:如何从 JKS 中导出 SSL 证书

作为一个 Java 程序员,如果需要配置 Tomcat 的 HTTPS 或者利用中间件(比如 TIBCO 公司的中间件产品)去调用第三方的 HTTPS 的 API 的时候,用的最多的私钥和证书存储容器就是 JKS。Java 的 JDK 提供了一个非常好的超级核武器级别的工具来生成和管理 SSL 的证书和秘钥,这就是如雷贯耳的 Keytool 工具。Keytool 就是 Java 版的私钥和证书的管理工具。安装 JDK 并配置环境变量之后就能使用,其提供了证书请求、导出证书、生成密钥对、打印证书、导入证书等强大功能。

Keytool 工具简述

为了给后面的步骤和描述做个铺垫,先对 Keytool 的用法进行一个简单的介绍。Keytool 提供了下面的 16 个子命令来进行对证书和私钥的操作和管理。

enter image description here

每个命令都有一些参数,查看这16个子命令的每个参数方法:

keytool + 子命令 + -help

就能查看其相应的子命令每个参数的用法,比如查看 certreq 的子命令的参数方法如下:

enter image description here

以后大家遇到子命令不会用的时候,就可以使用笔者教给大家的这个方法进行尝试,就能快速上手这个子命令。

Keytool 不能导出私钥

值得一提的时候,使用 Keytool 有一个限制,就是不能导出 JKS(Java KeyStore)里面的私钥,但是在有的时候,我们却需要从 JKS 导出私钥。

比如,我们在申请 Tomcat 的证书请求的时候(CSR),就会先生成私钥并放在 JKS 里,在生成证书请求并让第三方的 CA 签署后,再次导入到 JKS 里面和私钥配成对,从而配置 Tomcat 的 HTTPS。但是由于业务或者测试的需要,准备在当前的服务器上使用 Nginx 搭建一个基于 HTTPS 的负载均衡,Nginx 的基于 HTTPS 的负载均衡准备使用和 Tomcat 配置一样的服务器的公钥和私钥。但是 Ngnix 的配置文件不支持 JKS 的格式,而且必须把私钥和服务器证书显示的导出成 Basecode64 编码的格式,下图就是 Ngnix 配置 HTTPS 配置文件的一个示例。

enter image description here

令人遗憾的是 JDK 的 Keytool 工具不提供导出 JKS 私钥的方法和命令,这个时候,我们应该怎么办呢?有很多方法,如下两种方法:使用 OpenSSL 或者 Java 代码。

借助 OpenSSL 导出 SSL 证书和其对应的私钥

假设我们已经安装好了 OpenSSL,因为 OpenSSL 能很方便的处理 P12 格式的私钥和证书存储文件,所以我们把 JKS 里面的所有私钥和公钥全部导出到一个后缀名为 p12 的证书和私钥存储文件中,如下图:

keytool -importkeystore -srckeystore mykeystore.jks -destkeystore mykeystore.p12 -deststoretype PKCS12

注意,-importkeystore 只在 JDK 1.6 以后的版本才有这个参数。

导出 p12 后,剩下的事情就交给 OpenSSL 工具吧。

1. 查看 p12 里面的私钥和公钥

通过 OpenSSL 下面的命令,就可以查看其 p12 文件里面的所有的私钥和公钥,其中 mykeystore.p12 是我们从 JKS 导出的 p12 格式的文件。

openssl pkcs12 -in mykeystore.p12

2. 从 p12 里面导出公钥

如果想导出里面的公钥的话,可以使用下面的命令,其中 mykeystore.p12 是我们从 JKS 导出的 p12 格式的文件,nokeys 就是说不要导出私钥,-out 的后面的 cert.pem 就是从 p12 里面能够导出的公钥。

openssl pkcs12 -in mykeystore.p12 -nokeys -out cert.pem

3. 从 p12 里面导出私钥

如果想导出 p12 里面的私钥的话,可以使用下面的类似命令,nodes 不是节点的意思,而是“No DES”的缩写,意思就是导出一个不加密的私钥,其中 mykeystore.p12 是我们从 JKS 导出的 p12 格式的文件,key.pem 就是我们将导出的私钥,而且是没有密码加密的私钥。

openssl pkcs12 -in mykeystore.p12 -nodes -nocerts -out key.pem

借助 Java 代码导出 SSL 证书和其对应的私钥

有的朋友可能更喜欢代码的方式,直接从 JDK 里面把私钥导出来,以体现自己是一名专业的 Java 程序员,通过 Java 代码的方式如下:

package com.talkdocter.ssl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.security.Key;
import java.security.KeyStore;
import sun.misc.BASE64Encoder;

class StringUtils{
  public static boolean isEmpty(CharSequence cs)
  {
    return (cs == null) || (cs.length() == 0);
  }
 }

public class JKSService {


  private String jksFilePath;
  private String keyStorePassword;
  private String keyAlias;
  private String keyExportFilePath;

  public JKSService(String  jksFilePath, String keyStorePassword,String keyAlias, String keyExportFilePath){
    this.jksFilePath=jksFilePath;
    this.keyStorePassword=keyStorePassword;
    this.keyAlias=keyAlias;
    this.keyExportFilePath=keyExportFilePath;
  }



  public void exportKey() throws Exception{

    KeyStore keyStore= KeyStore.getInstance("JCEKS");
    BASE64Encoder encoder=new BASE64Encoder();

    File jksFile= new File(jksFilePath);
    if(!jksFile.exists() || jksFile.isDirectory()){
      throw new IllegalArgumentException("The Keystore file can't be empty!");
    }

    if(StringUtils.isEmpty(keyStorePassword)){
      throw new IllegalArgumentException("The pasword can't be empty!");
    }

    keyStore.load(new FileInputStream(new File(jksFilePath)), keyStorePassword.toCharArray());
    Key key=keyStore.getKey(keyAlias, keyStorePassword.toCharArray());

    String encodedKey=encoder.encode(key.getEncoded());

    File keyExportFile= new File(keyExportFilePath);

    FileWriter fileWriter=new FileWriter(keyExportFile);
    fileWriter.write("---BEGIN PRIVATE KEY ---\n");
    fileWriter.write(encodedKey);
    fileWriter.write("---END PRIVATE KEY ---\n");
    fileWriter.close();

  }

  public String getJksFilePath() {
    return jksFilePath;
  }

  public void setJksFilePath(String jksFilePath) {
    this.jksFilePath = jksFilePath;
  }

  public String getKeyStorePassword() {
    return keyStorePassword;
  }

  public void setKeyStorePassword(String keyStorePassword) {
    this.keyStorePassword = keyStorePassword;
  }

  public String getKeyAlias() {
    return keyAlias;
  }

  public void setKeyAlias(String keyAlias) {
    this.keyAlias = keyAlias;
  }



  public String getKeyExportFilePath() {
    return keyExportFilePath;
  }



  public void setKeyExportFilePath(String keyExportFilePath) {
    this.keyExportFilePath = keyExportFilePath;
  }


}

需要注意的是,默认情况下 KeyStore 的密码和包含私钥 Key 的密码可以设置成一致,所以上面的代码我就没有单独把包含私钥的密码单独作为一个参数列出来了。上面的代码只是用来导出 JKS 中的私钥,如果要导出公钥的话,可以使用 keyStore.getCertificate(alias) 方法。就不再赘述,读者可以自行完成。

那么如何知道私钥的别名呢?可以使用下面的 Keytool 命令进行查看。

keytool -list -keystore mykeystore.jks

enter image description here

测试代码如下:

package com.talkdocter.ssl;

import java.io.File;

import org.junit.Assert;
import org.junit.Test;

public class JKSServiceTest {

  @Test
  public void testExportKeyFromKeyStore(){
    String jksFilePath="C:\\ssldemo\\tomcat\\mykeystore.jks";
    String keyStorePassword="password";
    String keyAlias="tomcat";
    String keyExportFilePath="C:\\ssldemo\\tomcat\\tomcatkey.jks";
    File keystoreExportFile=new File(keyExportFilePath);
    if(keystoreExportFile.exists()){
      keystoreExportFile.delete();
    }
    JKSService jksService =new JKSService(jksFilePath, keyStorePassword, keyAlias, keyExportFilePath);
    try {
      jksService.exportKey();
    } catch (Exception e) {
      e.printStackTrace();
    }
    Assert.assertTrue(new File(keyExportFilePath).exists());
    Assert.assertTrue(new File(keyExportFilePath).getTotalSpace()>0);
  }


}

总结

本章内容首先简单介绍了 Keytool 工具的功能和基本的命令用法,紧接着指出了 Keytool 的功能的一个局限性,并用两种方法进行了弥补:通过先从 JKS 导出 p12 格式的文件,然后用 OpenSSL 对 P12 格式的文件进行处理,并导出私钥;另外一种方式,专门针对喜欢代码的 Java 程序员,使用 JDK 原生的类库,不借助于任何第三方的开源类库,用 Java 代码实现了从 JKS 里面导出私钥的功能。夜已深,该睡了,祝大家学习愉快。

上一篇
下一篇
目录