相關(guān)文章:
//-----------Java SSL begin----------------------
【ssl認(rèn)證、證書】SSL雙向認(rèn)證和SSL單向認(rèn)證的區(qū)別(示意圖)
【ssl認(rèn)證、證書】java中的ssl語法API說明(SSLContext)、與keytool 工具的聯(lián)系
【ssl認(rèn)證、證書】SSL雙向認(rèn)證java實(shí)戰(zhàn)、keytool創(chuàng)建證書
【ssl認(rèn)證、證書】Wireshark抓包分析
【ssl認(rèn)證、證書】 查看keystore文件內(nèi)容
//------------Java SSL end--------------------------
//-----------下面的是CA證書和openssl相關(guān)的知識(shí)--------------
【ssl認(rèn)證、證書】TLS/SSL雙向認(rèn)證概念、openssl genrsa示例
【ssl認(rèn)證、證書】openssl genrsa 命令詳解
【ssl認(rèn)證、證書】SSL 證書基本概念、證書格式、openssl和keytool的區(qū)別
1. 前言
在java中,我們經(jīng)常需要使用SSL/TLS連接,這種連接是加密連接,https之類的基于TCP/IP協(xié)議的應(yīng)用層協(xié)議加密傳輸,也經(jīng)?;谶@種加密連接,這種連接jdk底層已經(jīng)提供了默認(rèn)的實(shí)現(xiàn),并且是基于jdk中的keystroe證書庫實(shí)現(xiàn)的認(rèn)證。
在常用的http連接組件(如okhttp,httpcomponent)中,底層連接一般也是通過SocketFactroy建立的連接,通常不需要我們專門設(shè)置SocketFactory,使用的是jdk內(nèi)置的默認(rèn)實(shí)現(xiàn):
SocketFactory sf = SocketFactory.getDefault();
默認(rèn)的SocketFactory實(shí)現(xiàn)ssl連接時(shí),是基于{JAVA_HOME}/jre/lib/security/cacerts
這個(gè)默認(rèn)的已信任證書庫的實(shí)現(xiàn),因此當(dāng)遇到證書庫中不存在的證書時(shí),就會(huì)出現(xiàn)ssl連接異常的問題。
這個(gè)默認(rèn)的已信任證書庫如何使用,參見下文的<2.3.1.1 使用默認(rèn)的證書庫>章節(jié)
此時(shí)我們通常會(huì)通過創(chuàng)建一個(gè)忽略認(rèn)證校驗(yàn)的SocketFactory,并設(shè)置為默認(rèn)實(shí)現(xiàn):
SocketFactory.setDefault(sf);
或者將不信任的證書安裝到{JAVA_HOME}/jre/lib/security/cacerts
中解決問題。
但是實(shí)際上,設(shè)置默認(rèn)值,影響了整個(gè)系統(tǒng)的安全性,因?yàn)榇藭r(shí)意味著所有證書都信任,容易遭到攻擊,而將證書安裝到證書庫中,又改變了標(biāo)準(zhǔn)的jdk,此時(shí)相當(dāng)于應(yīng)用和jdk綁定了,不便于遷移或者jdk更新。
因此,為了解決上述兩個(gè)問題,我們應(yīng)該針對(duì)特定的服務(wù)使用特定的SocketFactory,這樣就可以將忽略證書校驗(yàn)的影響最小化。
java中的ssl語法與keytool 工具的聯(lián)系
標(biāo)準(zhǔn)的SSL協(xié)議在Java的落地,具體來說是用通過后面提到的SSLContext實(shí)現(xiàn)的,并且必須按照J(rèn)KS (Java KeyStone)格式提供證書,而keytool 正是生成JKS格式文件的工具,即使你用openssl工具創(chuàng)建了證書,也需要先利用相應(yīng)的工具,將其轉(zhuǎn)化為JKS格式,然后才能在Java中使用。
2. SSLContext的體系
結(jié)合 【ssl認(rèn)證、證書】SSL雙向認(rèn)證java實(shí)現(xiàn)、keytool創(chuàng)建證書 中的示例來看
java中所有基于TCP/IP協(xié)議的SSL/TLS網(wǎng)絡(luò)編程本質(zhì)都是基于SSLSocket這個(gè)對(duì)象,因此這個(gè)問題的核心在于如何讓組件的代碼使用我們定制化的方式創(chuàng)建SSLSocket對(duì)象。
常見的網(wǎng)絡(luò)編程組件都是通過SSLSocketFactory這個(gè)接口來創(chuàng)建SSLSocket的,因此要定制網(wǎng)絡(luò)編程組件創(chuàng)建的SSLSocket,我們只需要給組件提供定制的SSLSocketFactory即可。
以http組件為例,常見的http組件都支持我們?cè)O(shè)置定制化的SSLSocketFactory對(duì)象來代替默認(rèn)實(shí)現(xiàn)。
現(xiàn)在的問題在于,我們?nèi)绾蝿?chuàng)建一個(gè)定制化的SSLSocketFactory對(duì)象?
我們先來看看在jdk中的SSL體系結(jié)構(gòu):
-
① 通信核心類——SSLSocket和SSLServerSocket。對(duì)于使用過socket進(jìn)行通信開發(fā)的朋友比較好理解,它們對(duì)應(yīng)的就是Socket與ServerSocket,只是表示實(shí)現(xiàn)了SSL協(xié)議的Socket和ServerSocket,同時(shí)它們也是Socket與ServerSocket的子類。SSLSocket負(fù)責(zé)的事情包括設(shè)置加密套件、管理SSL會(huì)話、處理握手結(jié)束時(shí)間、設(shè)置客戶端模式或服務(wù)器模式。
-
② 客戶端與服務(wù)器端Socket工廠——SSLSocketFactory和SSLServerSocketFactory。在設(shè)計(jì)模式中工廠模式是專門用于生產(chǎn)出需要的實(shí)例,這里也是把SSLSocket、SSLServerSocket對(duì)象創(chuàng)建的工作交給這兩個(gè)工廠類。
-
③ SSL會(huì)話——SSLSession。安全通信握手過程需要一個(gè)會(huì)話,為了提高通信的效率,SSL協(xié)議允許多個(gè)SSLSocket共享同一個(gè)SSL會(huì)話,在同一個(gè)會(huì)話中,只有第一個(gè)打開的SSLSocket需要進(jìn)行SSL握手,負(fù)責(zé)生成密鑰及交換密鑰,其余SSLSocket都共享密鑰信息。
-
④ SSL上下文——SSLContext。它是對(duì)整個(gè)SSL/TLS協(xié)議的封裝,表示了安全套接字協(xié)議的實(shí)現(xiàn)。主要負(fù)責(zé)設(shè)置安全通信過程中的各種信息,例如跟證書相關(guān)的信息。并且負(fù)責(zé)構(gòu)建SSLSocketFactory、SSLServerSocketFactory和SSLEngine等工廠類。
-
⑤ SSL非阻塞引擎——SSLEngine。假如你要進(jìn)行NIO通信,那么將使用這個(gè)類,它讓通過過程支持非阻塞的安全通信。
-
⑥ 密鑰管理器——KeyManager。此接口負(fù)責(zé)選擇用于證實(shí)自己身份的安全證書,發(fā)給通信另一方。KeyManager對(duì)象由KeyManagerFactory工廠類生成。
-
⑦ 信任管理器——TrustManager。此接口負(fù)責(zé)判斷決定是否信任對(duì)方的安全證書,TrustManager對(duì)象由TrustManagerFactory工廠類生成。
圖我們可以看出,要?jiǎng)?chuàng)建一個(gè)SSLSocketFactory我們需要通過SSLContext這個(gè)對(duì)象來創(chuàng)建,而SSLContext這個(gè)對(duì)象又依賴TrustManager和KeyManager這兩個(gè)對(duì)象,這兩個(gè)對(duì)象都依賴KeyStore這個(gè)對(duì)象。
因此我們需要從KeyStore對(duì)象開始創(chuàng)建。
2.1 KeyStore
KeyStore對(duì)象實(shí)際上就是一個(gè)證書庫,我們前面提到的jdk內(nèi)置證書庫{JAVA_HOME}/jre/lib/security/cacerts
加載到內(nèi)存后,最終就是形成KeyStore對(duì)象。
KeyStore 是一個(gè)倉庫,但是后文會(huì)提到KeyManager(入?yún)⑹且粋€(gè)包含本端私鑰+本端證書的KeyStore)和TrustManager(入?yún)⑹且粋€(gè)包含對(duì)端證書的KeyStore),也就是說根據(jù)不同的協(xié)議,KeyStore 會(huì)實(shí)際存儲(chǔ)不同的內(nèi)容
證書庫的創(chuàng)建方式有多種:
2.1.1 通過證書庫文件創(chuàng)建:
String key="{JAVA_HOME}/jre/lib/security/cacerts";
String password = "123456";
KeyStore keystore=KeyStore.getInstance("JKS");
keystore.load(new FileInputStream(key),password.toCharArray());
在上面的代碼中,我們創(chuàng)建了一個(gè)使用{JAVA_HOME}/jre/lib/security/cacerts
文件作為證書庫源的證書庫對(duì)象。
證書庫文件可以通過jdk提供的keytool命令自行生成,這里不展開講。
2.1.2 隨機(jī)生成自簽名證書庫
隨機(jī)生成的證書沒有存儲(chǔ)到實(shí)際物理目錄,直接被導(dǎo)入到keyStore中:
String algorithm = "RSA";
String algorithmSign = "MD5WithRSA";
try {
CertAndKeyGen cak = new CertAndKeyGen(algorithm,algorithmSign);
cak.generate(1024);
X500Name subject = new X500Name("CN=localhost,o=netease");
X509Certificate certificate = cak.getSelfCertificate(subject,10);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
keyStore.setKeyEntry("local", cak.getPrivateKey(),password.toCharArray(), new Certificate[]{certificate});
return keyStore;
}catch (Exception e){
throw new RuntimeException(e);
}
這里我們使用RSA算法創(chuàng)建了一個(gè)證書生成器,并且指定隨機(jī)生成密鑰對(duì),長(zhǎng)度為1024,接著使用密鑰對(duì)生成一個(gè)證書對(duì)象X509Certificate。
然后我們通過keyStore.load(null);初始化了一個(gè)空的證書庫,最后將我們隨機(jī)生成的證書設(shè)置到證書庫中:
keyStore.setKeyEntry("local", cak.getPrivateKey(),password.toCharArray(), new Certificate[]{certificate});
2.2 KeyManager
KeyManager是密鑰管理器,主要用于驗(yàn)證證書有效性的,密鑰管理器對(duì)象通常有兩種方式創(chuàng)建。
這里需要強(qiáng)調(diào)一點(diǎn),KeyManager的入?yún)⑹且粋€(gè)包含本端私鑰+本端證書的KeyStore,當(dāng)然,由于訪問keyStone需要密碼,因此,也是比較安全的;而
TrustManager的入?yún)⑹且粋€(gè)僅包含對(duì)端證書的KeyStore
2.2.1 KeyManagerFactory工廠創(chuàng)建:
String password = ”123456“;
KeyStore keystore = null; // 省略keystore創(chuàng)建過程,可以參考前面的小結(jié)
KeyManagerFactory kmf=KeyManagerFactory.getInstance("SunX509");
kmf.init(keystore,password.toCharArray());
KeyManager[] kms = kmf.getKeyManagers();
這段代碼中,我們獲取了一個(gè)SunX509的密鑰管理器工廠,并使用自己指定的keystore和密碼進(jìn)行初始化,初始化之后使用工廠創(chuàng)建了一個(gè)密鑰管理器數(shù)組。
這個(gè)密鑰管理器數(shù)組即是根據(jù)我們的密鑰庫生成的,支持校驗(yàn)密鑰庫中的證書的密鑰管理器數(shù)組。
2.2.2 自己創(chuàng)建一個(gè)密鑰管理器數(shù)組:
這種方式相對(duì)少用,因?yàn)槊荑€管理器主要的功能是講自己的密鑰傳給連接的對(duì)方進(jìn)行校驗(yàn),通常我們只需要使用KeyManagerFactory配合KeyStore創(chuàng)建即可直接使用jdk的默認(rèn)實(shí)現(xiàn),很少有需要自己定制密鑰管理器的場(chǎng)景。
下面是 X509KeyManager
接口的匿名抽象類實(shí)現(xiàn),在此,你可以知道有那些抽象方法:
KeyManager[] kms = new KeyManager[]{
new X509KeyManager() {
@Override
public String[] getClientAliases(String s, Principal[] principals) {
return new String[0];
}
@Override
public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
return null;
}
@Override
public String[] getServerAliases(String s, Principal[] principals) {
return new String[0];
}
@Override
public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
return null;
}
@Override
public X509Certificate[] getCertificateChain(String s) {
return new X509Certificate[0];
}
@Override
public PrivateKey getPrivateKey(String s) {
return null;
}
}
};
2.3 TrustManager
TrustManager是校驗(yàn)管理器,一般用于校驗(yàn)連接的對(duì)方發(fā)來的證書
是否有效,通常也會(huì)有兩種方式創(chuàng)建.
TrustManager保存服務(wù)端的授權(quán)證書的副本,功能類似白名單,用于判斷對(duì)端傳送過來的證書是否合法,只有在tks的證書判定有效,否則直接拒絕連接。
2.3.1 使用TrustManagerFactory創(chuàng)建:
String password = ”123456“;
KeyStore keystore = null; // 省略keystore創(chuàng)建過程,可以參考前面的小結(jié)
TrustManagerFactory tmf=TrustManagerFactory.getInstance("SunX509");
tmf.init(keystore); //執(zhí)行init后,就可以調(diào)用下文的 tmf.getTrustManagers()方法
TrustManager[] tms = tmf.getTrustManagers();
這里我們使用TrustManagerFactory的實(shí)例和KeyStore創(chuàng)建了一個(gè)認(rèn)證管理器數(shù)組,這個(gè)數(shù)組可以用于校驗(yàn)連接的另一邊發(fā)來的所有證書,并且只信任KeyStore中信任的證書。
2.3.1.1 使用默認(rèn)的證書庫
tmf.init(null);時(shí),會(huì)自動(dòng)使用 {JAVA_HOME}/jre/lib/security/cacerts
作為信任證書庫
public javax.net.ssl.TrustManagerFactory {
public final void init(KeyStore ks) throws KeyStoreException{
factorySpi.engineInit(ks); //跟進(jìn)去
}
實(shí)現(xiàn)類 TrustManagerFactoryImpl:
public sun.security.ssl.TrustManagerFactoryImpl {
protected void engineInit(KeyStore ks) throws KeyStoreException{
if (ks == null) {
try {
trustManager = getInstance(TrustStoreManager.getTrustedCerts()); //跟進(jìn)去TrustStoreManager.getTrustedCerts()
......
}
public TrustStoreManager {
public static Set<X509Certificate> getTrustedCerts() throws Exception {
return tam.getTrustedCerts(TrustStoreDescriptor.createInstance()); //TrustStoreDescriptor.createInstance()
}
public TrustStoreDescriptor {
private static final String defaultStore =
defaultStorePath + fileSep + "cacerts"; //{JAVA_HOME}/jre/lib/security/cacerts
static TrustStoreDescriptor createInstance() {
return AccessController.doPrivileged(
new PrivilegedAction<TrustStoreDescriptor>() {
@Override
public TrustStoreDescriptor run() {
// Get the system properties for trust store.
String storePropName = System.getProperty(
"javax.net.ssl.trustStore", jsseDefaultStore);
String storePropType = System.getProperty(
"javax.net.ssl.trustStoreType",
KeyStore.getDefaultType());
String storePropProvider = System.getProperty(
"javax.net.ssl.trustStoreProvider", "");
String storePropPassword = System.getProperty(
"javax.net.ssl.trustStorePassword", "");
。。。。
if (!"NONE".equals(storePropName)) {
String[] fileNames =
new String[] {storePropName, defaultStore}; //defaultStore是前文定義的變量
2.3.2 自行創(chuàng)建一個(gè)信任全部證書的認(rèn)證管理器數(shù)據(jù):
通過下面的X509ExtendedTrustManager接口的匿名抽象類,我們可以知道有抽象方法用于對(duì)端證書的校驗(yàn):
TrustManager[] tms = new TrustManager[]{
X509TrustManager trustManager = new X509ExtendedTrustManager(){
public X509Certificate[] getAcceptedIssuers(){ return null; }
public void checkClientTrusted(X509Certificate[] certs, String authType){}
public void checkServerTrusted(X509Certificate[] certs, String authType){}
public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {}
public void checkServerTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {}
public void checkClientTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {}
public void checkServerTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {}
}
}
這里我們直接創(chuàng)建了一個(gè)X509TrustManager對(duì)象作為認(rèn)證管理器數(shù)組的元素,并且這個(gè)對(duì)象對(duì)所有的證書都不進(jìn)行校驗(yàn)(沒拋異常,即認(rèn)為校驗(yàn)通過),即表示信任所有的證書。
這個(gè)是我們常見的針對(duì)自簽名證書的信任的實(shí)現(xiàn)方式,當(dāng)然這是不安全的。
2.4 SSLContext
終于到了SSLContext對(duì)象,前面我們創(chuàng)建的所有對(duì)象,最終都是為了構(gòu)造一個(gè)加密傳輸?shù)纳舷挛沫h(huán)境,這個(gè)環(huán)境就是SSLContext。
現(xiàn)在我們可以輕松的創(chuàng)建一個(gè)SSLContext對(duì)象:
KeyManager[] kms = null; // 省略創(chuàng)建過程
TrustManager[] tms = null; // 省略創(chuàng)建過程
SecureRandom sr = new SecureRandom();
SSLContext ssl=SSLContext.getInstance("TLS");
ssl.init(kms,tms,sr);
ssl對(duì)象通過密鑰管理器,認(rèn)證管理器和隨機(jī)數(shù)SecureRandom即可初始化完成,這里SecureRandom也可以為null。
值得注意的是,在這個(gè)安全上下文中,其實(shí)密鑰管理器和認(rèn)證管理器并不是必須的。
當(dāng)你的程序作為客戶端向服務(wù)端發(fā)起連接時(shí)(如http客戶端連接http服務(wù)端),如果服務(wù)器沒有要求認(rèn)證客戶端證書(通常http服務(wù)端不會(huì)要求認(rèn)證客戶端證書)時(shí),可以不傳kms,此時(shí)只需要你自己覺得你的程序是否需要認(rèn)證服務(wù)端證書,此時(shí)你只需要傳tms決定如何認(rèn)證服務(wù)端證書即可:
ssl.init(null,tms,sr);
當(dāng)你的程序作為服務(wù)端被客戶端發(fā)起連接時(shí),假設(shè)你不要求客戶端證書認(rèn)證(http服務(wù)端大多數(shù)不會(huì)要求認(rèn)證客戶端證書),此時(shí)只需要給客戶端發(fā)送你的證書即可,客戶端可能會(huì)校驗(yàn):
ssl.init(kms,null,sr);
當(dāng)你的程序和另一個(gè)程序連接的過程,雙方都要求證書認(rèn)證時(shí),才需要同時(shí)傳遞kms和tms:
ssl.init(kms,tms,sr);
2.5 SSLSocketFactory
現(xiàn)在所有的SSL上下文都構(gòu)建完成,SSLSocketFactory只需要直接通過SSLContext獲取即可:
SSLContext ssl=null;//省略創(chuàng)建過程
SSLSocketFactory sf = ssl.getSocketFactory();
到這里我們就創(chuàng)建了一個(gè)完全定制化的SSLSocketFactory,只要傳給對(duì)應(yīng)的組件作為socket工廠即可。
2.6 HttpsURLConnection
常見的第三方http組件確實(shí)可以通過自定義SSLSocketFactory實(shí)現(xiàn)指定請(qǐng)求的忽略證書,而非全局忽略證書,但是jdk內(nèi)置的HttpsURLConnection
就比較坑了,不支持針對(duì)連接單獨(dú)制定socket工廠
,而是直接使用SSLSocketFactory.getDefault()
獲取默認(rèn)的socket工廠,這就導(dǎo)致我們只能通過全局設(shè)置忽略證書的方式實(shí)現(xiàn)忽略證書校驗(yàn)。
不過這也不是完全無法改變,查看SSLSocketFactory.getDefault()源碼可以發(fā)現(xiàn)如下代碼:
public static synchronized ServerSocketFactory getDefault() {
String var0 = getSecurityProperty("ssl.SocketFactory.provider");
if (var0 != null) {
log("setting up default SSLSocketFactory");
try {
Class var1 = null;
try {
var1 = Class.forName(var0);
} catch (ClassNotFoundException var5) {
ClassLoader var3 = ClassLoader.getSystemClassLoader();
if (var3 != null) {
var1 = var3.loadClass(var0);
}
}
log("class " + var0 + " is loaded");
SSLSocketFactory var2 = (SSLSocketFactory)var1.newInstance();
log("instantiated an instance of class " + var0);
theFactory = var2;
return var2;
} catch (Exception var6) {
log("SSLSocketFactory instantiation failed: " + var6.toString());
theFactory = new DefaultSSLSocketFactory(var6);
return theFactory;
}
}
這里看到String var0 = getSecurityProperty("ssl.SocketFactory.provider");
提供了一種通過系統(tǒng)屬性指定socket工廠實(shí)現(xiàn)類的方式,因此我們可以通過ThreadLocal和自定義實(shí)現(xiàn)類的方式來使得針對(duì)單個(gè)連接使用我們的定制socket工廠:
先創(chuàng)建一個(gè)自定義的工廠:
public class CustomSocketFactory implement SSLSocketFactory{
}
通過threadLocal () :
//...
ThreadLocal<Boolean> useCustom = new InheritableThreadLocal<>();
HttpsURLConnect conn = null;//省略創(chuàng)建過程
String old = Security.getProperty("ssl.SocketFactory.provider");
try{
if(useCustom.get()){
// 指定socket工廠
Security.setProperty("ssl.SocketFactory.provider", CustomSocketFactory.class.getName());
}
conn.connect();
}finally{
// 恢復(fù)socket工廠
Security.setProperty("ssl.SocketFactory.provider", old); //貌似也不對(duì),萬一是并發(fā)的請(qǐng)求,
//那么修改該環(huán)境變量的,沒有恢復(fù)的話,會(huì)影響其他的線程讀取該環(huán)境變量
}
//...
在本文中所有的類都基于jdk8的實(shí)現(xiàn),沒有使用外部依賴,如果使用外部依賴的話,可能實(shí)現(xiàn)起來會(huì)更加簡(jiǎn)單。文章來源:http://www.zghlxwxcb.cn/news/detail-686295.html
參考
java中的ssl連接
Java SSL實(shí)現(xiàn)使用詳解
SSL/TLS通信及其在Java中的實(shí)踐文章來源地址http://www.zghlxwxcb.cn/news/detail-686295.html
到了這里,關(guān)于【ssl認(rèn)證、證書】java中的ssl語法API說明(SSLContext)、與keytool 工具的聯(lián)系的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!