關(guān)于證書的那些事

在物聯(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的公鑰進行驗簽开仰。完整的流程如下:


1169376-20191009111543085-161187219.png

第一步,服務(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市菲嘴,隨后出現(xiàn)的幾起案子饿自,更是在濱河造成了極大的恐慌,老刑警劉巖龄坪,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昭雌,死亡現(xiàn)場離奇詭異,居然都是意外死亡健田,警方通過查閱死者的電腦和手機烛卧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來妓局,“玉大人总放,你說我怎么就攤上這事『门溃” “怎么了局雄?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長存炮。 經(jīng)常有香客問我炬搭,道長,這世上最難降的妖魔是什么僵蛛? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任尚蝌,我火速辦了婚禮,結(jié)果婚禮上充尉,老公的妹妹穿的比我還像新娘飘言。我一直安慰自己,他們只是感情好驼侠,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布姿鸿。 她就那樣靜靜地躺著谆吴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苛预。 梳的紋絲不亂的頭發(fā)上句狼,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機與錄音热某,去河邊找鬼腻菇。 笑死,一個胖子當(dāng)著我的面吹牛昔馋,可吹牛的內(nèi)容都是我干的筹吐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼秘遏,長吁一口氣:“原來是場噩夢啊……” “哼丘薛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起邦危,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤洋侨,失蹤者是張志新(化名)和其女友劉穎倦蚪,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體陵且,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年滩报,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脓钾。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖昌妹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情握截,我是刑警寧澤飞崖,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站谨胞,受9級特大地震影響固歪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一牢裳、第九天 我趴在偏房一處隱蔽的房頂上張望逢防。 院中可真熱鬧,春花似錦蒲讯、人聲如沸忘朝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽局嘁。三九已至,卻和暖如春晦墙,著一層夾襖步出監(jiān)牢的瞬間导狡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工偎痛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人独郎。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓踩麦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親氓癌。 傳聞我的和親對象是個殘疾皇子谓谦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容