在物聯(lián)網(wǎng)IOT平臺設(shè)備接入到云端設(shè)備安全和數(shù)據(jù)安全置關(guān)重要戴甩,目前比較大型的IOT云平臺终吼,比如亞馬遜,微軟昂勉,阿里脂凶,騰訊都有自己的設(shè)備接入方案宪睹,雖然流程不盡相同,但都是使用以下技術(shù)來保證設(shè)備接入和數(shù)據(jù)傳輸?shù)陌踩浴?/p>
- 使用證書來進行設(shè)備和云端的雙向認(rèn)證
- 使用HTTPS來進行數(shù)據(jù)加密傳輸
這些都離不開證書相關(guān)的技術(shù)蚕钦,我最近也在做一個有關(guān)這方面的項目亭病,因此把自己實現(xiàn)和學(xué)習(xí)到的一些東西記錄下來,希望能加深自己的理解嘶居。
什么是CA
CA證書就是電子商務(wù)認(rèn)證授權(quán)機構(gòu)罪帖,也稱為電子商務(wù)認(rèn)證中心,是負(fù)責(zé)發(fā)放和管理數(shù)字證書的權(quán)威機構(gòu)邮屁,并作為電子商務(wù)交易中受信任的第三方整袁,承擔(dān)公鑰體系中公鑰的合法性檢驗的責(zé)任。
證書的內(nèi)容包括:電子簽證機關(guān)的信息佑吝、公鑰用戶信息坐昙、公鑰、權(quán)威機構(gòu)的簽字和有效期等等芋忿。目前炸客,證書的格式和驗證方法普遍遵循X.509 國際標(biāo)準(zhǔn)。證書能用來認(rèn)證發(fā)送者的身份戈钢,證書也能保證信息不會被篡改痹仙。
CA證書的簽發(fā)和認(rèn)證流程
證書的生成和驗證過程簡單的概括就是用CA的私鑰給用戶的證書簽名,然后在證書的簽名字段中填充這個簽名值殉了,瀏覽器在驗證這個證書的時候就是使用CA的公鑰進行驗簽开仰。完整的流程如下:
第一步,服務(wù)方S向第三方機構(gòu)CA提交公鑰、組織信息抖所、個人信息(域名)等信息并申請認(rèn)證;注意不含私鑰梨州,這個文件是CSR(Certificate Servcie Requst)。私鑰保留在服務(wù)器端田轧。
第二步暴匠,CA通過線上、線下等多種手段驗證申請者提供信息的真實性傻粘,如組織是否存在每窖、企業(yè)是否合法,是否擁有域名的所有權(quán)等;
第三步弦悉,如信息審核通過窒典,CA會向申請者簽發(fā)認(rèn)證文件-證書,這個證書可以保留在服務(wù)器端或者客戶端稽莉,可以公開給任何客戶端使用瀑志。證書包含以下信息:
- 申請者公鑰、申請者的組織信息和個人信息污秆、簽發(fā)機構(gòu)CA的信息劈猪、有效時間、證書序列號等信息的明文良拼,同時包含一個簽名;
- 簽名的產(chǎn)生算法:首先战得,使用散列函數(shù)計算公開的明文信息的信息摘要,然后庸推,采用CA的私鑰對信息摘要進行加密常侦,密文即簽名;信息摘要可以防止信息被篡改。CA的私鑰加密能驗證證書的合法性和頒發(fā)機構(gòu)的身份贬媒。
第三步聋亡,客戶端 C 向服務(wù)器 S 發(fā)出請求時,S 返回證書文件;如果不是瀏覽器的客戶端际乘,比如JAVA客戶端杀捻,也可以把證書放在客戶端。
第四步蚓庭,客戶端 C讀取證書中的相關(guān)的明文信息,采用相同的散列函數(shù)計算得到信息摘要仅仆,然后器赞,利用對應(yīng)CA的公鑰解密簽名數(shù)據(jù),對比證書的信息摘要墓拜,如果一致港柜,則可以確認(rèn)證書的合法性,即公鑰合法;
第五步,客戶端然后驗證證書相關(guān)的域名信息夏醉、有效時間等信息;
第六步爽锥,客戶端會內(nèi)置信任CA的證書信息(包含公鑰),如果CA不被信任畔柔,則找不到對應(yīng) CA的證書氯夷,證書也會被判定非法。
在這個過程注意幾點:
a.申請證書不需要提供私鑰靶擦,確保私鑰永遠只能服務(wù)器掌握;
b.證書的合法性仍然依賴于非對稱加密算法腮考,證書主要是增加了服務(wù)器信息以及簽名;
c.內(nèi)置 CA 對應(yīng)的證書稱為根證書,頒發(fā)者和使用者相同玄捕,自己為自己簽名踩蔚,即自簽名證書,自簽名證書可能會存在中間人攻擊枚粘。
d.證書=公鑰(服務(wù)方生成密碼對中的公鑰)+申請者與頒發(fā)者信息+簽名(用CA機構(gòu)生成的密碼對的私鑰進行簽名);
即便有人截取服務(wù)器A證書馅闽,再發(fā)給客戶端,想冒充服務(wù)器A馍迄,也無法實現(xiàn)福也。因為證書和url的域名是綁定的。
什么是單向認(rèn)證
大多數(shù)情況下認(rèn)證可能都是單向的柬姚,只需要客戶端確認(rèn)服務(wù)端是可靠的拟杉,而服務(wù)端不管客戶端是否可靠。平常我們訪問一個https網(wǎng)頁時量承,這就是一個典型的單向認(rèn)證場景搬设,瀏覽器會驗證web服務(wù)器的證書,如果不是一個有效的CA證書則無法訪問撕捍,如果是一個不可信任的CA證書拿穴,瀏覽器也會給出提示,但是服務(wù)器不會驗證每個瀏覽器.
什么是雙向認(rèn)證
雙向認(rèn)證相對于單向認(rèn)證忧风,即客戶端需要確認(rèn)服務(wù)端是否可信默色,服務(wù)端也需要確認(rèn)客戶端是否可信。雙方都要驗證對方的證書狮腿。在我之前描述的IOT設(shè)備接入到云平臺腿宰,從安全考慮,肯定需要雙向認(rèn)證缘厢。即云端要驗證IOT設(shè)備的證書吃度,IOT設(shè)備也要驗證云端證書。詳細的雙向認(rèn)證贴硫,后面會根據(jù)實例詳細講解椿每。
證書生成方法
證書生成工具一般有兩個OpenSSL和KeyTool伊者,這兩個工具的最大區(qū)別是keytool沒辦法簽發(fā)證書,而openssl能夠進行簽發(fā)和證書鏈的管理间护。因此亦渗,keytool 簽發(fā)的所謂證書只是一種 自簽名證書,它是JDK提供給我們弄些 JDK 能認(rèn)識的證書的汁尺。在Java編程中法精,可以使用BouncyCastle來生成證書。下面會有詳細演示均函。
雙向認(rèn)證實例(自簽證書)
單向認(rèn)證是客戶端根據(jù)ca根證書驗證服務(wù)端提供的服務(wù)端證書和私鑰亿虽;雙向認(rèn)證還要服務(wù)端根據(jù)ca根證書驗證客戶端證書和私鑰,因此雙向認(rèn)證需要生成兩套證書苞也。下面以一個實例來演示自簽證書的生成和驗證過程洛勉,使用openssl工具和BouncyCastle,Nginx來演示雙向認(rèn)證流程如迟。
openssl來生成證書
第一步收毫,產(chǎn)生根證書
- 生成根證書私鑰
openssl genrsa -out ca.key 4096
- 生成根證書
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt
填寫相應(yīng)的信息,注意common name殷勘,可以取一個名字此再,比如ca
第二步,生成服務(wù)端證書
- 生成服務(wù)端私鑰
openssl genrsa -out server.key 4096
- 生成服務(wù)端證書請求CSR
openssl req -new -key server.key -out server.csr
此處同樣注意Common Name玲销,如果是域名綁定输拇,請?zhí)顚懹蛎绻荌P填寫服務(wù)器IP
- 根據(jù)根證書私鑰和服務(wù)端證書請求生成服務(wù)端證書
openssl x509 -req -sha256 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -out server.crt
第三步贤斜,生成客戶端證書
客戶端證書生成步驟和服務(wù)端基本一樣策吠,需要注意的就是在生成簽發(fā)請求的時候填寫的信息中,comm name可以是客戶端Ip瘩绒,其他信息最好是和服務(wù)端的不一樣猴抹。
第四步,導(dǎo)出客戶端證書到PKCS12
客戶端證書比服務(wù)端稍微多一步的就是锁荔,需要對客戶端證書和私鑰進行打包處理蟀给,這里方便安裝以后后續(xù)訪問時候攜帶,一般都是使用pkcs12進行打包阳堕,并在瀏覽器里導(dǎo)入跋理。
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12
證書文件格式大概有以下幾種:
- .DER .CER,文件是二進制格式恬总,只保存證書前普,不保存私鑰。
- .PEM越驻,一般是文本格式,可保存證書,可保存私鑰缀旁。
- .CRT记劈,可以是二進制格式,可以是文本格式并巍,與 .DER 格式相同目木,不保存私鑰。
- .PFX .P12懊渡,二進制格式刽射,同時包含證書和私鑰,一般有密碼保護剃执。
- .JKS誓禁,二進制格式,同時包含證書和私鑰肾档,一般有密碼保護摹恰。
在Java程序中,也有兩個用于保存證書的兩個文件trustStore和keyStore怒见,這兩個文件也有不同的作用俗慈,都有密碼保護。如果要保存秘鑰和證書遣耍,應(yīng)該使用KeyStore闺阱,并且該文件要保持私密不外泄,不要傳播該文件;如果要保存你信任的來自他人的公鑰和證書舵变,應(yīng)該使用TrustStore酣溃,而不是KeyStore;關(guān)于它們的作用,也很好理解棋傍,當(dāng)客戶端向服務(wù)器端發(fā)送一個https請求救拉,客戶端需要驗證服務(wù)器,因此服務(wù)器需要向客戶端提供認(rèn)證以便客戶端確認(rèn)這個服務(wù)器是否可信瘫拣。服務(wù)器向客戶端提供的認(rèn)證信息就是自身的證書和公鑰亿絮,而這些信息,包括對應(yīng)的私鑰麸拄,服務(wù)器就是通過KeyStore來保存的派昧。當(dāng)服務(wù)器提供的證書和公鑰到了客戶端,客戶端就要生成一個TrustStore文件保存這些來自服務(wù)器證書和公鑰拢切。
而在瀏覽器中蒂萎,這些信息就是通過在瀏覽器中導(dǎo)入證書來實現(xiàn)對服務(wù)器的自簽證書的驗證,就是類似于TrustStore淮椰。導(dǎo)入pk12文件來實現(xiàn)客戶端向服務(wù)器發(fā)送證書這一過程五慈,類似于服務(wù)器的KeyStore
第五步纳寂,在Nginx里配置雙向認(rèn)證
為了演示證書的驗證過程,可以在web服務(wù)上配置證書泻拦,每種web server有不同的配置方式毙芜,以Nginx為例,需要做如下配置:
- 配置服務(wù)端認(rèn)證
server {
server_name www.example.com;
ssl on;
ssl_certificate /usr/local/nginx/ssl/server.crt;
ssl_certificate_key /usr/local/nginx/ssl/server.key;
...
}
默認(rèn)的 listen 80為 listen 443 ssl争拐;
server_name指向之前生成服務(wù)端證書時指向的域名www.example.com腋粥;
使用 ssl on開啟ssl安全認(rèn)證功能;
ssl_certificate指定服務(wù)端證書的地址架曹,如/usr/local/nginx/ssl/server.crt;
ssl_certificate_key指定服務(wù)端私鑰地址隘冲,如/usr/local/nginx/ssl/server.key;
-配置客戶端認(rèn)證
server {
ssl_client_certificate /usr/local/nginx/ssl/ca.crt;
ssl_verify_client on;
}
瀏覽器請求時會帶上客戶端的證書,服務(wù)器會使用ca.crt證書來驗證客戶端的身份绑雄,作用類似于瀏覽器中安裝了根證書展辞,來驗證服務(wù)器證書。同樣服務(wù)器中配置了根證書來驗證瀏覽器绳慎。這一過程總結(jié)下來如下:
發(fā)送方配置公鑰和私鑰并發(fā)送證書給接收方纵竖。接收方配置根證書來驗證發(fā)送方的證書有效性。當(dāng)發(fā)送方是服務(wù)器時杏愤,比如上例中的Nginx靡砌,因此配置了服務(wù)器證書和私鑰,而且配置了驗證客戶端的ca證書珊楼。當(dāng)發(fā)送方時瀏覽器時通殃,需要導(dǎo)入PK12文件,里面包括了私鑰和證書厕宗,請求時帶上客戶端證書画舌,同時需要配置驗證服務(wù)器的根證書,如果該根證書不是被CA機構(gòu)頒發(fā)已慢,就必須通過導(dǎo)入根證書的方式導(dǎo)入到瀏覽器曲聂。當(dāng)發(fā)送方時java客戶端時,需要配置keystore佑惠,里面包括了證書和私鑰朋腋,請求時會帶上證書,同時配置了truststore來驗證服務(wù)器的證書的有效性膜楷。這就是雙向認(rèn)證旭咽。
Java中證書的生成
在Java中可以使用BouncyCastle來實現(xiàn)這套證書的頒發(fā)流程。測試的庫是 BouncyCastle 1.64赌厅。
引入庫文件
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.64</version>
</dependency>
證書工具類
工具類里提供了以下功能:
- 如何使用BouncyCastle生成X509證書
- 如何生成公鑰和私鑰
- 如果從PEM文件中讀取私鑰
- 如果從證書文件中獲取證書信息
- 如何生成CSR
- 如何從DER文件中讀取私鑰
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v1CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.util.PrivateKeyFactory;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.PEMWriter;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
import org.bouncycastle.util.io.pem.PemObject;
import javax.xml.bind.DatatypeConverter;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Slf4j
public class SecurityUtil {
private static SecureRandom random = new SecureRandom();
public SecurityUtil(){
}
public static X509Certificate getX509Certificate(File certificatePem) throws IOException, CertificateException {
FileInputStream fin = new FileInputStream(certificatePem);
CertificateFactory f = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate)f.generateCertificate(fin);
fin.close();
return certificate;
}
/**
* Create a X509 V1 {@link Certificate}
*
* @param pair {@link KeyPair}
* @param numberOfDays Number of days the certificate will be valid
* @param DN The DN of the subject
* @return
* @throws CertificateException
*/
public Certificate createX509V1Certificate(KeyPair pair, int numberOfDays, String DN) throws CertificateException {
try {
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
AsymmetricKeyParameter privateKeyAsymKeyParam = PrivateKeyFactory.createKey(pair.getPrivate().getEncoded());
SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(pair.getPublic().getEncoded());
ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(privateKeyAsymKeyParam);
Date startDate = new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000);
Date endDate = new Date(System.currentTimeMillis() + numberOfDays * 24 * 60 * 60 * 1000);
X500Name name = new X500Name(DN);
BigInteger serialNum = createSerialNumber();
X509v1CertificateBuilder v1CertGen = new X509v1CertificateBuilder(name, serialNum, startDate, endDate, name,
subPubKeyInfo);
X509CertificateHolder certificateHolder = v1CertGen.build(sigGen);
return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certificateHolder);
} catch (CertificateException e1) {
throw e1;
} catch (Exception e) {
throw new CertificateException(e);
}
}
/**
* Create a certificate signing request
*
* @throws CertificateException
*/
public byte[] createCSR(String dn, KeyPair keyPair) throws CertificateException {
X500Name name = new X500Name(dn);
try {
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA512WITHRSA");
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
AsymmetricKeyParameter privateKeyAsymKeyParam = PrivateKeyFactory.createKey(keyPair.getPrivate().getEncoded());
ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(privateKeyAsymKeyParam);
SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
PKCS10CertificationRequestBuilder builder = new PKCS10CertificationRequestBuilder(name, subPubKeyInfo);
PKCS10CertificationRequest csr = builder.build(sigGen);
return csr.getEncoded();
} catch (Exception e) {
throw new CertificateException(e);
}
}
/**
* Get the CSR as a PEM formatted String
*
* @param csrEncoded
* @return
* @throws IOException
*/
public String getPEM(byte[] csrEncoded) throws IOException {
String type = "CERTIFICATE REQUEST";
PemObject pemObject = new PemObject(type, csrEncoded);
StringWriter str = new StringWriter();
PEMWriter pemWriter = new PEMWriter(str);
pemWriter.writeObject(pemObject);
pemWriter.close();
str.close();
return str.toString();
}
/**
* Generate a Key Pair
*
* @param algo (RSA, DSA etc)
* @return
* @throws GeneralSecurityException
*/
public KeyPair generateKeyPair(String algo) throws GeneralSecurityException {
KeyPairGenerator kpg = KeyPairGenerator.getInstance(algo);
return kpg.genKeyPair();
}
/**
* Create a random serial number
*
* @return
* @throws GeneralSecurityException
*/
public BigInteger createSerialNumber() throws GeneralSecurityException {
BigInteger bi = new BigInteger(4, random);
return bi;
}
private static RSAPrivateKey generatePrivateKeyFromDER(byte[] keyBytes)
throws InvalidKeySpecException, NoSuchAlgorithmException {
final PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
final KeyFactory factory = KeyFactory.getInstance("RSA");
return (RSAPrivateKey) factory.generatePrivate(spec);
}
private static X509Certificate generateCertificateFromDER(byte[] certBytes) throws CertificateException {
final CertificateFactory factory = CertificateFactory.getInstance("X.509");
return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(certBytes));
}
public static KeyPair getKeyFromClassPath(String filename) throws CertificateException {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
InputStream stream = loader.getResourceAsStream("cert/" + filename);
if (stream == null) {
throw new CertificateException("Could not read private key from classpath:" + "certificates/" + filename);
}
BufferedReader br = new BufferedReader(new InputStreamReader(stream));
try {
PEMParser pp = new PEMParser(br);
PEMKeyPair pemKeyPair = (PEMKeyPair) pp.readObject();
KeyPair kp = new JcaPEMKeyConverter().getKeyPair(pemKeyPair);
pp.close();
return kp;
} catch (IOException ex) {
throw new CertificateException("Could not read private key from classpath", ex);
}
}
/**
* Create a KeyStore from standard PEM files
*
* @param privateKeyPem the private key PEM file
* @param certificatePem the certificate(s) PEM file
* @param the password to set to protect the private key
*/
public static KeyStore createKeyStore(File privateKeyPem, File certificatePem, final String password)
throws Exception, KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
final X509Certificate[] cert = createCertificates(certificatePem);
final KeyStore keystore = KeyStore.getInstance("JKS");
keystore.load(null);
// Import private key
final PrivateKey key = createPrivateKey(privateKeyPem);
keystore.setKeyEntry(privateKeyPem.getName(), key, password.toCharArray(), cert);
return keystore;
}
private static X509Certificate[] createCertificates(File certificatePem) throws Exception {
final List<X509Certificate> result = new ArrayList<X509Certificate>();
final BufferedReader r = new BufferedReader(new FileReader(certificatePem));
String s = r.readLine();
if (s == null || !s.contains("BEGIN CERTIFICATE")) {
r.close();
throw new IllegalArgumentException("No CERTIFICATE found");
}
StringBuilder b = new StringBuilder();
while (s != null) {
if (s.contains("END CERTIFICATE")) {
String hexString = b.toString();
final byte[] bytes = DatatypeConverter.parseBase64Binary(hexString);
X509Certificate cert = generateCertificateFromDER(bytes);
result.add(cert);
b = new StringBuilder();
} else {
if (!s.startsWith("----")) {
b.append(s);
}
}
s = r.readLine();
}
r.close();
return result.toArray(new X509Certificate[result.size()]);
}
public static PrivateKey createPrivateKey(File privateKeyPemFile) throws Exception {
String privateKeyPem = initPemFile(privateKeyPemFile);
privateKeyPem = privateKeyPem.replace("-----BEGIN RSA PRIVATE KEY-----\n", "");
privateKeyPem = privateKeyPem.replace("-----END RSA PRIVATE KEY-----\n", "");
privateKeyPem = privateKeyPem.replace(" ", "").replace("\n", "");
byte[] encoded = Base64.decodeBase64(privateKeyPem.getBytes(StandardCharsets.UTF_8));
KeyFactory kf = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
return (RSAPrivateKey) kf.generatePrivate(keySpec);
}
private static String initPemFile(File privateKeyPem) {
StringBuilder pemKey = new StringBuilder();
try (BufferedReader br = new BufferedReader(new FileReader(privateKeyPem))) {
String tempStr;
while ((tempStr = br.readLine()) != null) {
pemKey.append(tempStr).append("\n");
}
} catch (IOException e) {
log.error(e.getMessage(), e);
}
return pemKey.toString();
}
}
創(chuàng)建證書請求
有了上面的工具類穷绵,生成CSR就比較簡單,代碼如下:
SecurityUtil util = new SecurityUtil();
KeyPair pair = util.generateKeyPair("RSA");
String DN = "CN=helloworld";
byte[] csr = util.createCSR(DN, pair);
String pem = util.getPEM(csr);
生成X509證書
使用上面的工具類特愿,也可以很容易的根據(jù)CA證書的密鑰來生成X509證書仲墨,下面演示密鑰文件從文件中讀取勾缭。
PemReader reader = new PemReader(new StringReader(csrPEM));
PemObject pemObj = reader.readPemObject();
log.info("Parsed PEM type {}", pemObj.getType());
PKCS10CertificationRequest inputCSR = new
PKCS10CertificationRequest(pemObj.getContent());
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA512WITHRSA");
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
PrivateKey caPrivate = SecurityUtil.getKeyFromClassPath("cert/your_ca.key.pem").getPrivate();
X509Certificate x509Certificate = SecurityUtil.getX509Certificate(getFileFromResource("cert/your_ca.cert.pem"));
AsymmetricKeyParameter keyParameter = PrivateKeyFactory.createKey(caPrivate.getEncoded());
SubjectPublicKeyInfo keyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
String name = x509Certificate.getSubjectDN().getName();
X509v3CertificateBuilder myCertificateGenerator = new X509v3CertificateBuilder(
new X500Name(name), BigInteger.valueOf(666666666L), new Date(
System.currentTimeMillis()), new Date(
System.currentTimeMillis() + 30 * 365 * 24 * 60 * 60
* 1000), inputCSR.getSubject(), inputCSR.getSubjectPublicKeyInfo());
ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId)
.build(keyParameter);
X509CertificateHolder holder = myCertificateGenerator.build(sigGen);
Certificate eeX509CertificateStructure = holder.toASN1Structure();
CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");
InputStream is = new ByteArrayInputStream(eeX509CertificateStructure.getEncoded());
X509Certificate theCert = (X509Certificate) cf.generateCertificate(is);
is.close();
Java中雙向認(rèn)證的實現(xiàn)
在Nginx中配置了服務(wù)器端證書和客戶端證書認(rèn)證后,在Java中使用RestTemplate中可以驗證服務(wù)器證書和發(fā)送客戶端證書目养。會用到上面介紹的TrustStore和KeyStore兩個文件
生成trust store文件
keytool -keystore truststore.jks -keypass 131112 -storepass 131112 -alias DemoCA -import -trustcacerts -file ca.cer
生成key store文件
我們可以使用以下命令把pkcs12文件轉(zhuǎn)為keystore文件漫拭,pkcs里面包括了證書和私鑰。
keytool -importkeystore -deststorepass 123456 -destkeypass 123456 -destkeystore keystore.jks -srckeystore sh.pk12 -srcstoretype PKCS12 -srcstorepass 123456 -alias shkey
使用RestTemplate發(fā)送請求
private RestTemplate getRestTemplateClientAuthentication()
throws IOException, UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException,
KeyStoreException, KeyManagementException {
final String allPassword = "123456";
SSLContext sslContext = SSLContextBuilder
.create()
.loadKeyMaterial(ResourceUtils.getFile("classpath:keystore.jks"),
allPassword.toCharArray(), allPassword.toCharArray())
.loadTrustMaterial(ResourceUtils.getFile("classpath:truststore.jks"), allPassword.toCharArray())
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
HttpClient client = HttpClients.custom()
.setSSLContext(sslContext)
.build();
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(client);
RestTemplate restTemplate = new RestTemplate(requestFactory);
return restTemplate;
}
寫在最后
文章內(nèi)容有點多混稽,但涵蓋了關(guān)于證書認(rèn)證的方方面面,包括證書生成流程审胚,通過命令和代碼來創(chuàng)建證書匈勋,證書格式轉(zhuǎn)換,如何配置雙向認(rèn)證膳叨,以及任何使用Java代碼來實現(xiàn)這些功能洽洁。
參考文章
1.https://www.cnblogs.com/xdyixia/p/11610102.html
2.https://blog.csdn.net/tuzongxun/article/details/88647172