開發時我們常常使用JDK自帶的keytool工具來創建自簽發的證書,並保存到密鑰庫文件中。如果要把一個密鑰庫導入到另一個密鑰庫(比如到另一台機器上安裝,同時又不想用覆蓋文件的方式),那該怎么操作呢?
比如,我們從aaa.jks里把別名為tomcat的內容導入到bbb.jks里。一個錯覺是先從aaa.jks導出證書、再導入到bbb.jks里。
為說明錯誤情況,我們從頭做起。先生成別名為tomcat的證書,並保存到aaa.jks里:
keytool -keystore aaa.jks -genkey -keyalg RSA -alias tomcat
然后把證書導出到tomcat.cert文件:
keytool -keystore aaa.jks -export -file tomcat.cert -alias tomcat
接着把tomcat.cert導入到bbb.jks里:
keytool -keystore bbb.jks -import -file tomcat.cert -alias tomcat
為驗證這種做法的錯誤性,我們可分別用aaa.jks和bbb.jks來啟動Tomcat服務器,看看能不能啟動成功。由於我們僅僅是出於驗證的目的,因此無需在Tomcat的webapps目錄里放進應用的war包文件。
先用aaa.jks來啟動Tomcat。修改Tomcat的conf\server.xml文件為:
<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" clientAuth="false" sslProtocol="TLS" keystoreFile="aaa.jks" keystorePass="changeit" />
請把上面的aaa.jks輸入為實際的全路徑名(比如d:\aaa.jks)。完成后啟動Tomcat,此時Tomcat應能夠成功啟動。
確認成功后請關閉Tomcat,接下來我們把上面的aaa.jks換為bbb.jks,保存后重新啟動Tomcat。Tomcat將報異常:
嚴重: Failed to initialize connector [Connector[HTTP/1.1-8443]]
org.apache.catalina.LifecycleException: Failed to initialize component [Connector[HTTP/1.1-8443]]
at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:106)
at org.apache.catalina.core.StandardService.initInternal(StandardService.java:559)
at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:102)
at org.apache.catalina.core.StandardServer.initInternal(StandardServer.java:814)
at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:102)
at org.apache.catalina.startup.Catalina.load(Catalina.java:633)
at org.apache.catalina.startup.Catalina.load(Catalina.java:658)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.catalina.startup.Bootstrap.load(Bootstrap.java:281)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:450)
Caused by: org.apache.catalina.LifecycleException: Protocol handler initialization failed
at org.apache.catalina.connector.Connector.initInternal(Connector.java:983)
at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:102)
... 12 more
Caused by: java.io.IOException: Alias name tomcat does not identify a key entry
at org.apache.tomcat.util.net.jsse.JSSESocketFactory.getKeyManagers(JSSESocketFactory.java:567)
at org.apache.tomcat.util.net.jsse.JSSESocketFactory.getKeyManagers(JSSESocketFactory.java:505)
at org.apache.tomcat.util.net.jsse.JSSESocketFactory.init(JSSESocketFactory.java:449)
at org.apache.tomcat.util.net.jsse.JSSESocketFactory.createSocket(JSSESocketFactory.java:158)
at org.apache.tomcat.util.net.JIoEndpoint.bind(JIoEndpoint.java:393)
at org.apache.tomcat.util.net.AbstractEndpoint.init(AbstractEndpoint.java:610)
at org.apache.coyote.AbstractProtocol.init(AbstractProtocol.java:429)
at org.apache.coyote.http11.AbstractHttp11JsseProtocol.init(AbstractHttp11JsseProtocol.java:119)
at org.apache.catalina.connector.Connector.initInternal(Connector.java:981)
... 13 more
為什么用bbb.jks啟動不了呢?為此我們分別檢查一下aaa.jks和bbb.jks里的內容。
下面是aaa.jks里的內容:
keytool -keystore aaa.jks -list -alias tomcat 輸入keystore密碼: tomcat, 2012-12-2, PrivateKeyEntry, 認證指紋 (MD5): 20:E1:74:4B:0B:35:33:FF:BE:2D:9D:B5:31:AB:3B:DE
下面是bbb.jks里的內容:
keytool -keystore bbb.jks -list -alias tomcat 輸入keystore密碼: tomcat, 2012-12-2, trustedCertEntry, 認證指紋 (MD5): 20:E1:74:4B:0B:35:33:FF:BE:2D:9D:B5:31:AB:3B:DE
從上面兩個輸出中可見其差別,一個是PrivateKeyEntry,另一個是trustedCertEntry。
其實,密鑰庫里保存了兩類信息,一類是私鑰,另一類是證書。證書里只有公鑰。上面導出的tomcat.cert文件為證書文件,里面沒有私鑰。因此當我們再導入到bbb.jks時,導進去的只有證書、沒有對應的私鑰。而服務器需要用私鑰與客戶端的公鑰通訊,因此Tomcat報了上面的異常。
那么我們該如何正確操作呢?方法有很多,最常用的方法是不用keytool來生成證書和私鑰,而改用openssl工具。不過本文的目的是只用keytool來操作。
其實操作很簡單:
keytool -importkeystore -deststorepass changeit -destkeypass changeit -destkeystore bbb.jks -deststoretype jks -srckeystore aaa.jks -srcstoretype jks -srcstorepass changeit -alias tomcat
這個bbb.jks就包含了別名為tomcat的私鑰和證書了。如果不放心可再用啟動Tomcat的方法去驗證一下。
最后要說明的是,如果在keytool中不指定storetype、srcstoretype、deststoretype參數則也默認為jks。