終極武器——數字證書

數字證書也稱電子證書,由數字證書頒發認證機構(CA)簽發才具備可認證性。數字證書採用了公鑰基礎設施(PKI),使用了相應的加密算法確保網絡應用安全性:

  • 非對稱加密算法用於對數據進行加密/解密操作,確保數據的機密性。
  • 數字簽名算法用於數據進行簽名/驗證操作,確保數據的完整性和抗否性。
  • 消息摘要算法用於對數字證書本身做摘要處理,確保數字證書完整性。

數字證書常用算法

1、非對稱加密算法:RSADSA(無法完成加密/解密實現,這樣的數字證書不包括數據加密/解密功能)
2、簽名算法:SHA1withRSAsha1RSA
3、消息摘要算法:SHA1

數字證書文件編碼格式

1、CER(規範編碼格式),是BER(基本編碼格式)的一個變種,使用變長模式。
2、DER(卓越編碼格式),也是BER的一個變種,並使用定長模式。

公鑰基礎設施(PKI)

所有證書都符合PKI制定的X.509標準,如:PKCS(公鑰加密標準)
PKCS常用標準如下:

公鑰加密標準 描述信息 文件名後綴
PKCS#7 密碼消息語法標準 .p7b、.p7c、.spc
PKCS#10 證書請求語法標準 .p10、.csr
PKCS#12 個人信息交換語法標準 .p12、.pfx

以上標準主要用於證書的申請和更新等操作。例如:PKCS#10文件用於證書籤發申請,PKCS#12文件可作爲JAVA中的密鑰庫或信任庫直接而使用。

數字證書模型

1、數字證書頒發流程

 

數字證書頒發流程

2、數字證書服務請求與響應

數字證書服務請求與響應

數字證書管理

KeyTool證書管理

KeyTool是JAVA中的數字證書管理工具,用於數字證書的申請、導入、導出和撤銷等操作。KeyTool與本地密鑰庫相關聯,將私鑰存於密鑰庫,公鑰則以數字證書輸出。

1、構建自簽名證書

下面演示先生成一個自簽名證書,然後導出數字證書,最後打印數字證書。使用keytool工具導出的證書,是一個自簽名的X.509第三版類型根證書,並以Base64編碼保存。但是,沒有經過CA機構認證,幾乎不具備任何法律效力。

詳細參數說明:

生成本地數字證書

D:\MyData\majx2>keytool -genkeypair -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -validity 36000 -alias www.crazyxing.com -keystore crazyxing.keystore -dname "CN=www.crazyxing.com,OU=crazyxing,O=crazyxing,L=GZ,ST=GD,C=CN"
輸入密鑰庫口令:
再次輸入新口令:
輸入 <www.crazyxing.com> 的密鑰口令
        (如果和密鑰庫口令相同, 按回車):

Warning:
JKS 密鑰庫使用專用格式。建議使用 "keytool -importkeystore -srckeystore crazyxing.keystore -destkeystore crazyxing.keystore -deststoretype pkcs12" 遷移到行業標準格式 PKCS12。
命令參數 命令說明
-genkeypair 表示生成密鑰
-keyalg 指定密鑰算法,這裏制定RSA
-keysize 指定密鑰長度,默認1024,這裏制定2048
-sigalg 指定數字簽名算法,這裏指定SHA1withRSA算法
-validity 指定證書有效期,這裏指定爲36000天
-alias 指定別名,這裏www.crazyxing.com
-keystore 指定密鑰庫存儲位置,這裏是crazyxing.keystore
-storepass 指定密碼
-dname 指定你個人用戶信息

導出數字證書

D:\MyData\majx2>keytool -exportcert -alias www.crazyxing.com -keystore crazyxing.keystore -file crazyxing.cer -rfc
輸入密鑰庫口令:
存儲在文件 <crazyxing.cer> 中的證書

Warning:
JKS 密鑰庫使用專用格式。建議使用 "keytool -importkeystore -srckeystore crazyxing.keystore -destkeystore crazyxing.keystore -deststoretype pkcs12" 遷移到行業標準格式 PKCS12。

D:\MyData\majx2>keytool -printcert -file crazyxing.cer
所有者: CN=www.crazyxing.com, OU=crazyxing, O=crazyxing, L=GZ, ST=GD, C=CN
發佈者: CN=www.crazyxing.com, OU=crazyxing, O=crazyxing, L=GZ, ST=GD, C=CN
序列號: 1b2162b5
有效期爲 Sat Mar 09 22:11:16 CST 2019 至 Fri Oct 01 22:11:16 CST 2117
證書指紋:
         MD5:  A4:50:BA:56:C5:84:9A:2F:EE:73:5E:53:D2:AD:F8:4A
         SHA1: 17:FF:93:47:A6:C0:26:58:86:60:6E:8C:0A:BE:03:FE:07:53:A2:D7
         SHA256: E5:09:24:87:25:F1:C2:CE:4F:F7:D8:D6:CA:07:B8:88:01:70:A4:F1:D9:31:51:B0:C5:D2:F1:9D:F1:D8:FD:22
簽名算法名稱: SHA1withRSA
主體公共密鑰算法: 2048 位 RSA 密鑰
版本: 3

擴展:

#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 40 DA 0E 81 94 6C 51 41   57 4A 56 CD 21 D6 8D 5E  @....lQAWJV.!..^
0010: 38 48 96 20                                        8H.
]
]
命令參數 命令說明
-exportcert 表示證書導出操作
-alias 指定別名,這裏爲www.crazyxing.com
-keystore 指定密鑰庫文件,這裏crazyxing.keystore
-file 指定導出文件路徑,這裏爲crazyxing.cer
-rfc 指定爲Base64編碼格式輸出
-storepass 指定密碼

遷移到行業標準格式 PKCS12

D:\MyData\majx2>keytool -importkeystore -srckeystore crazyxing.keystore -destkeystore crazyxing.keystore -deststoretype pkcs12
輸入源密鑰庫口令:
已成功導入別名 www.crazyxing.com 的條目。
已完成導入命令: 1 個條目成功導入, 0 個條目失敗或取消

Warning:
已將 "crazyxing.keystore" 遷移到 Non JKS/JCEKS。將 JKS 密鑰庫作爲 "crazyxing.keystore.old" 進行了備份。

D:\MyData\majx2>keytool -exportcert -alias www.crazyxing.com -keystore crazyxing.keystore -file crazyxing.p12 -rfc
輸入密鑰庫口令:
存儲在文件 <crazyxing.p12> 中的證書

D:\MyData\majx2>keytool -printcert -file crazyxing.p12
所有者: CN=www.crazyxing.com, OU=crazyxing, O=crazyxing, L=GZ, ST=GD, C=CN
發佈者: CN=www.crazyxing.com, OU=crazyxing, O=crazyxing, L=GZ, ST=GD, C=CN
序列號: 1b2162b5
有效期爲 Sat Mar 09 22:11:16 CST 2019 至 Fri Oct 01 22:11:16 CST 2117
證書指紋:
         MD5:  A4:50:BA:56:C5:84:9A:2F:EE:73:5E:53:D2:AD:F8:4A
         SHA1: 17:FF:93:47:A6:C0:26:58:86:60:6E:8C:0A:BE:03:FE:07:53:A2:D7
         SHA256: E5:09:24:87:25:F1:C2:CE:4F:F7:D8:D6:CA:07:B8:88:01:70:A4:F1:D9:31:51:B0:C5:D2:F1:9D:F1:D8:FD:22
簽名算法名稱: SHA1withRSA
主體公共密鑰算法: 2048 位 RSA 密鑰
版本: 3

擴展:

#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 40 DA 0E 81 94 6C 51 41   57 4A 56 CD 21 D6 8D 5E  @....lQAWJV.!..^
0010: 38 48 96 20                                        8H.
]
]

2、構建CA簽發證書

獲取CA機構認證的數字證書,需要將數字證書籤發申請(CSR)導出,經由CA機構認證並頒發,同時將認證後的證書導入本地鑰匙庫和信任庫。

導出數字證書籤發申請

D:\MyData\majx2>keytool -certreq -alias www.crazyxing.com -keystore crazyxing.keystore -file crazyxing.pfx -v
輸入密鑰庫口令:
存儲在文件 <crazyxing.pfx> 中的認證請求
將此提交給您的 CA
命令參數 命令說明
-certreq 表示數字證書申請操作
-alias 指定別名,這裏爲www.crazyxing.com
-keystore 指定密鑰庫文件,這裏crazyxing.keystore
-flie 指定導出文件路徑,這裏crazyxing.pfx(文件後綴,參考PKCS常用標準)
-v 詳細信息
-storepass 指定密碼

導入數字證書

D:\MyData\majx2>keytool -delete -alias www.crazyxing.com -keystore crazyxing.keystore
輸入密鑰庫口令:

D:\MyData\majx2>keytool -importcert -trustcacerts -alias www.crazyxing.com -file crazyxing.p12 -keystore crazyxing.keystore
輸入密鑰庫口令:
所有者: CN=www.crazyxing.com, OU=crazyxing, O=crazyxing, L=GZ, ST=GD, C=CN
發佈者: CN=www.crazyxing.com, OU=crazyxing, O=crazyxing, L=GZ, ST=GD, C=CN
序列號: 1b2162b5
有效期爲 Sat Mar 09 22:11:16 CST 2019 至 Fri Oct 01 22:11:16 CST 2117
證書指紋:
         MD5:  A4:50:BA:56:C5:84:9A:2F:EE:73:5E:53:D2:AD:F8:4A
         SHA1: 17:FF:93:47:A6:C0:26:58:86:60:6E:8C:0A:BE:03:FE:07:53:A2:D7
         SHA256: E5:09:24:87:25:F1:C2:CE:4F:F7:D8:D6:CA:07:B8:88:01:70:A4:F1:D9:31:51:B0:C5:D2:F1:9D:F1:D8:FD:22
簽名算法名稱: SHA1withRSA
主體公共密鑰算法: 2048 位 RSA 密鑰
版本: 3

擴展:

#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 40 DA 0E 81 94 6C 51 41   57 4A 56 CD 21 D6 8D 5E  @....lQAWJV.!..^
0010: 38 48 96 20                                        8H.
]
]

是否信任此證書? [否]:  是
證書已添加到密鑰庫中

D:\MyData\majx2>keytool -list -alias www.crazyxing.com -keystore crazyxing.keystore
輸入密鑰庫口令:
www.crazyxing.com, 2019-3-9, trustedCertEntry,
證書指紋 (SHA1): 17:FF:93:47:A6:C0:26:58:86:60:6E:8C:0A:BE:03:FE:07:53:A2:D7
命令參數 命令說明
-importcert 表示導入數字證書
-trustcacerts 表示將數字證書導入信任庫
-alias 指定別名 www.crazyxing.com
-file 指定導入證書的文件路徑,這裏爲crazyxing.p12
-keystore 指定密鑰庫文件,這裏爲crazyxing.keystore
-storepass 指定密碼

3、證書的使用

先實現一個認證工具:

import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.crypto.Cipher;

public abstract class CertificateCoder {

    /**
     * 類型證書X509
     */
    public static final String CERT_TYPE = "X.509";

    /**
     * 由KeyStore獲得私鑰
     * 
     * @param keyStorePath
     *            密鑰庫路徑
     * @param alias
     *            別名
     * @param password
     *            密碼
     * @return PrivateKey 私鑰
     * @throws Exception
     */
    private static PrivateKey getPrivateKeyByKeyStore(String keyStorePath,
            String alias, String password) throws Exception {

        // 獲得密鑰庫
        KeyStore ks = getKeyStore(keyStorePath, password);

        // 獲得私鑰
        return (PrivateKey) ks.getKey(alias, password.toCharArray());

    }

    /**
     * 由Certificate獲得公鑰
     * 
     * @param certificatePath
     *            證書路徑
     * @return PublicKey 公鑰
     * @throws Exception
     */
    private static PublicKey getPublicKeyByCertificate(String certificatePath)
            throws Exception {

        // 獲得證書
        Certificate certificate = getCertificate(certificatePath);

        // 獲得公鑰
        return certificate.getPublicKey();

    }

    /**
     * 獲得Certificate
     * 
     * @param certificatePath
     *            證書路徑
     * @return Certificate 證書
     * @throws Exception
     */
    private static Certificate getCertificate(String certificatePath)
            throws Exception {

        // 實例化證書工廠
        CertificateFactory certificateFactory = CertificateFactory
                .getInstance(CERT_TYPE);

        // 取得證書文件流
        FileInputStream in = new FileInputStream(certificatePath);

        // 生成證書
        Certificate certificate = certificateFactory.generateCertificate(in);

        // 關閉證書文件流
        in.close();

        return certificate;
    }

    /**
     * 獲得Certificate
     * 
     * @param keyStorePath
     *            密鑰庫路徑
     * @param alias
     *            別名
     * @param password
     *            密碼
     * @return Certificate 證書
     * @throws Exception
     */
    private static Certificate getCertificate(String keyStorePath,
            String alias, String password) throws Exception {

        // 獲得密鑰庫
        KeyStore ks = getKeyStore(keyStorePath, password);

        // 獲得證書
        return ks.getCertificate(alias);
    }

    /**
     * 獲得KeyStore
     * 
     * @param keyStorePath
     *            密鑰庫路徑
     * @param password
     *            密碼
     * @return KeyStore 密鑰庫
     * @throws Exception
     */
    private static KeyStore getKeyStore(String keyStorePath, String password)
            throws Exception {

        // 實例化密鑰庫
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());

        // 獲得密鑰庫文件流
        FileInputStream is = new FileInputStream(keyStorePath);

        // 加載密鑰庫
        ks.load(is, password.toCharArray());

        // 關閉密鑰庫文件流
        is.close();

        return ks;
    }

    /**
     * 私鑰加密
     * 
     * @param data
     *            待加密數據
     * @param keyStorePath
     *            密鑰庫路徑
     * @param alias
     *            別名
     * @param password
     *            密碼
     * @return byte[] 加密數據
     * @throws Exception
     */
    public static byte[] encryptByPrivateKey(byte[] data, String keyStorePath,
            String alias, String password) throws Exception {

        // 取得私鑰
        PrivateKey privateKey = getPrivateKeyByKeyStore(keyStorePath, alias,
                password);

        // 對數據加密
        Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);

        return cipher.doFinal(data);

    }

    /**
     * 私鑰解密
     * 
     * @param data
     *            待解密數據
     * @param keyStorePath
     *            密鑰庫路徑
     * @param alias
     *            別名
     * @param password
     *            密碼
     * @return byte[] 解密數據
     * @throws Exception
     */
    public static byte[] decryptByPrivateKey(byte[] data, String keyStorePath,
            String alias, String password) throws Exception {

        // 取得私鑰
        PrivateKey privateKey = getPrivateKeyByKeyStore(keyStorePath, alias,
                password);

        // 對數據加密
        Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateKey);

        return cipher.doFinal(data);

    }

    /**
     * 公鑰加密
     * 
     * @param data
     *            待加密數據
     * @param certificatePath
     *            證書路徑
     * @return byte[] 加密數據
     * @throws Exception
     */
    public static byte[] encryptByPublicKey(byte[] data, String certificatePath)
            throws Exception {

        // 取得公鑰
        PublicKey publicKey = getPublicKeyByCertificate(certificatePath);

        // 對數據加密
        Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);

        return cipher.doFinal(data);

    }

    /**
     * 公鑰解密
     * 
     * @param data
     *            待解密數據
     * @param certificatePath
     *            證書路徑
     * @return byte[] 解密數據
     * @throws Exception
     */
    public static byte[] decryptByPublicKey(byte[] data, String certificatePath)
            throws Exception {

        // 取得公鑰
        PublicKey publicKey = getPublicKeyByCertificate(certificatePath);

        // 對數據加密
        Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, publicKey);

        return cipher.doFinal(data);

    }

    /**
     * 簽名
     * 
     * @param keyStorePath
     *            密鑰庫路徑
     * @param alias
     *            別名
     * @param password
     *            密碼
     * @return byte[] 簽名
     * @throws Exception
     */
    public static byte[] sign(byte[] sign, String keyStorePath, String alias,
            String password) throws Exception {

        // 獲得證書
        X509Certificate x509Certificate = (X509Certificate) getCertificate(
                keyStorePath, alias, password);

        // 構建簽名,由證書指定簽名算法
        Signature signature = Signature.getInstance(x509Certificate
                .getSigAlgName());

        // 獲取私鑰
        PrivateKey privateKey = getPrivateKeyByKeyStore(keyStorePath, alias,
                password);

        // 初始化簽名,由私鑰構建
        signature.initSign(privateKey);

        signature.update(sign);

        return signature.sign();
    }

    /**
     * 驗證簽名
     * 
     * @param data
     *            數據
     * @param sign
     *            簽名
     * @param certificatePath
     *            證書路徑
     * @return boolean 驗證通過爲真
     * @throws Exception
     */
    public static boolean verify(byte[] data, byte[] sign,
            String certificatePath) throws Exception {

        // 獲得證書
        X509Certificate x509Certificate = (X509Certificate) getCertificate(certificatePath);

        // 由證書構建簽名
        Signature signature = Signature.getInstance(x509Certificate
                .getSigAlgName());

        // 由證書初始化簽名,實際上是使用了證書中的公鑰
        signature.initVerify(x509Certificate);

        signature.update(data);

        return signature.verify(sign);

    }

}

下面是相應的單元測試:

import org.apache.commons.codec.binary.Hex;
import org.junit.Test;
import static org.junit.Assert.*;

public class CertificateCoderTest {

    private String password = "123456";
    
    private String alias = "www.crazyxing.com";
    
    private String certificatePath = "D:/MyData/majx2/crazyxing.cer";
    
    private String keyStorePath = "D:/MyData/majx2/crazyxing.keystore.old";

    /**
     * 公鑰加密——私鑰解密
     * 
     * @throws Exception
     */
    @Test
    public void test1() throws Exception {

        System.err.println("公鑰加密——私鑰解密");
        String inputStr = "Ceritifcate";
        byte[] data = inputStr.getBytes();

        // 公鑰加密
        byte[] encrypt = CertificateCoder.encryptByPublicKey(data,
                certificatePath);

        // 私鑰解密
        byte[] decrypt = CertificateCoder.decryptByPrivateKey(encrypt,
                keyStorePath, alias, password);
        String outputStr = new String(decrypt);

        System.err.println("加密前:\n" + inputStr);

        System.err.println("解密後:\n" + outputStr);

        // 驗證數據一致
        assertArrayEquals(data, decrypt);

    }

    /**
     * 私鑰加密——公鑰解密
     * 
     * @throws Exception
     */
    @Test
    public void test2() throws Exception {

        System.err.println("私鑰加密——公鑰解密");

        String inputStr = "sign";
        byte[] data = inputStr.getBytes();

        // 私鑰加密
        byte[] encodedData = CertificateCoder.encryptByPrivateKey(data,
                keyStorePath, alias, password);

        // 公鑰加密
        byte[] decodedData = CertificateCoder.decryptByPublicKey(encodedData,
                certificatePath);

        String outputStr = new String(decodedData);

        System.err.println("加密前:\n" + inputStr);
        System.err.println("解密後:\n" + outputStr);

        // 校驗
        assertEquals(inputStr, outputStr);
    }

    /**
     * 簽名驗證
     * 
     * @throws Exception
     */
    @Test
    public void testSign() throws Exception {

        String inputStr = "簽名";
        byte[] data = inputStr.getBytes();
        System.err.println("私鑰簽名——公鑰驗證");

        // 產生簽名
        byte[] sign = CertificateCoder
                .sign(data, keyStorePath, alias, password);
        System.err.println("簽名:\n" + Hex.encodeHexString(sign));

        // 驗證簽名
        boolean status = CertificateCoder.verify(data, sign, certificatePath);
        System.err.println("狀態:\n" + status);
        
        // 校驗
        assertTrue(status);

    }

}

OpenSSL證書管理

下面示例生成雙向認證所需的全部證書。

安裝OpenSSL工具

1、首先下載OpenSSL

鏈接:https://pan.baidu.com/s/1gqXnHLpXgqw2ZdQ-Ftgdgw 提取碼:x3dq 

2、給OpenSSL設置環境變量

 

設置環境變量

設置Path

3、修改ca證書路徑
編輯%OpenSSL_HOME%\bin\openssl.cfg配置文件,設置ca路徑

 

修改配置文件

4、構建CA目錄結構

D:\MyData\majx2>mkdir demoCA

D:\MyData\majx2>cd demoCA
# 構建已發行證書存放目錄
D:\MyData\majx2\demoCA>mkdir certs
# 構建新證書存放目錄
D:\MyData\majx2\demoCA>mkdir newcerts
# 構建私鑰存放目錄
D:\MyData\majx2\demoCA>mkdir private
# 構建證書吊銷列表存放目錄
D:\MyData\majx2\demoCA>mkdir cr1
# 構建索引文件
D:\MyData\majx2\demoCA>echo 0>index.txt
# 構建序列號文件
D:\MyData\majx2\demoCA>echo 01>serial

構建根證書

1、構建隨機數

D:\MyData\majx2\demoCA>openssl rand -out private/.rand 1000
命令參數 命令說明
-rand 隨機數命令
-out 輸出文件路徑,這裏將隨機數文件輸出到private目錄下

2、構建根證書
openssl通常使用PEM(隱私增強郵件)編碼格式保存私鑰

D:\MyData\majx2\demoCA>openssl genrsa -aes256 -out private/ca.key.pem 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
...............................................+++++
...................+++++
e is 65537 (0x010001)
Enter pass phrase for private/ca.key.pem:
Verifying - Enter pass phrase for private/ca.key.pem:
命令參數 命令說明
genrsa 產生RSA密鑰命令
-aes256 使用AES算法(256位密鑰)對產生的私鑰加密。可選算法包括DES/DESede/IDEA/AES
-out 輸出路徑,這裏是private/ca.key.pem

3、生成根證書籤發申請

D:\MyData\majx2\demoCA>openssl req -new -key private/ca.key.pem -out private/ca.csr -subj "/C=CN/ST=GD/L=GZ/O=crazyxing/OU=crazyxing/CN=*.crazyxing.com"
Enter pass phrase for private/ca.key.pem:
命令參數 命令說明
req 生成證書籤發申請命令
-new 表示新請求
-key 密鑰,這裏pirvate/ca.key.pem文件
-out 輸出路徑,這裏private/ca.csr文件
-subj 指定用戶信息,這裏使用泛域名"*.crazyxing.com"作爲用戶名

4、簽發根證書
得到根證書申請文件後,可以發送給CA機構簽發,也可以自行簽發根證書

D:\MyData\majx2\demoCA>openssl x509 -req -days 10000 -sha1 -extensions v3_ca -signkey private/ca.key.pem -in private/ca.csr -out certs/ca.cer
Signature ok
subject=C = CN, ST = GD, L = GZ, O = crazyxing, OU = crazyxing, CN = *.crazyxing.com
Getting Private key
Enter pass phrase for private/ca.key.pem:
命令參數 命令說明
x509 簽發X.509格式證書命令
-req 表示證書輸入請求
-days 表示有效天數,這裏10000天
-sha1 表示證書摘要算法,這裏爲SHA1算法
-extensions 表示按OpenSSL配置文件v3_ca項添加擴展
-signkey 表示自簽名密鑰,這裏private/ca.key.pem
-in 表示輸入文件,這裏private/ca.csr
-out 表示輸出文件,這裏certs/ca.cer

5、根證書轉換
OpenSSL產生的數字證書不能在Java語言中直接使用,需要將其轉化成PKCS#12編碼格式

D:\MyData\majx2\demoCA>openssl pkcs12 -export -cacerts -inkey private/ca.key.pem -in certs/ca.cer -out certs/ca.p12
Enter pass phrase for private/ca.key.pem:
Enter Export Password:
Verifying - Enter Export Password:

D:\MyData\majx2\demoCA>keytool -list -keystore certs/ca.p12 -storetype pkcs12 -v -storepass 123456
密鑰庫類型: PKCS12
密鑰庫提供方: SunJSSE

您的密鑰庫包含 1 個條目

別名: 1
創建日期: 2019-3-10
條目類型: PrivateKeyEntry
證書鏈長度: 1
證書[1]:
所有者: CN=*.crazyxing.com, OU=crazyxing, O=crazyxing, L=GZ, ST=GD, C=CN
發佈者: CN=*.crazyxing.com, OU=crazyxing, O=crazyxing, L=GZ, ST=GD, C=CN
序列號: 6fad9a9cb9fb61a60c976403ee5fe42228c956fa
有效期爲 Sun Mar 10 09:29:11 CST 2019 至 Thu Jul 26 09:29:11 CST 2046
證書指紋:
         MD5:  4D:FA:6B:F3:A9:91:16:37:34:C1:7E:E3:66:1B:3C:A3
         SHA1: F8:52:09:30:17:02:07:CF:82:45:4C:92:66:7A:85:73:C6:BE:40:4D
         SHA256: 03:5B:03:ED:C8:15:9F:B5:76:3F:F6:F9:43:EB:7D:4A:ED:B5:6F:88:73:0D:C2:7C:3C:CB:08:6A:04:05:56:F4
簽名算法名稱: SHA1withRSA
主體公共密鑰算法: 2048 位 RSA 密鑰
版本: 1


*******************************************
*******************************************
命令參數 命令說明
pkcs12 PKCS#12編碼格式證書命令
-export 表示導出證書
-cacerts 表示僅導出CA證書
-inkey 表示輸入密鑰,這裏爲private/ca.key.pem
-in 表示輸入文件,這裏爲certs/ca.cer
-out 表示輸出文件,這裏爲certs/ca.p12

構建服務器證書

1、構建服務器私鑰

D:\MyData\majx2\demoCA>openssl genrsa -aes256 -out private/server.key.pem 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
.......................................+++++
.................................................................................................................................................+++++
e is 65537 (0x010001)
Enter pass phrase for private/server.key.pem:
Verifying - Enter pass phrase for private/server.key.pem:
命令參數 命令說明
genrsa 產生RSA密鑰命令
-aes256 使用AES算法(256位密鑰)對產生的私鑰加密。可選算法包括DES/DESede/IDEA/AES
-out 輸出路徑,這裏是private/server.key.pem

2、生成服務器證書籤發申請

D:\MyData\majx2\demoCA>openssl req -new -key private/server.key.pem -out private/server.csr -subj "/C=CN/ST=GD/L=GZ/O=crazyxing/OU=crazyxing/CN=www.crazyxing.com"
Enter pass phrase for private/server.key.pem:
命令參數 命令說明
req 生成證書籤發申請命令
-new 表示新請求
-key 密鑰,這裏pirvate/server.key.pem文件
-out 輸出路徑,這裏private/server.csr文件
-subj 指定用戶信息,這裏使用泛域名"www.crazyxing.com"作爲用戶名

3、簽發服務器證書

D:\MyData\majx2\demoCA>openssl x509 -req -days 3650 -sha1 -extensions v3_req -CA certs/ca.cer -CAkey private/ca.key.pem -CAserial ca.sr1 -CAcreateserial -in private/server.csr -out certs/server.cer
Signature ok
subject=C = CN, ST = GD, L = GZ, O = crazyxing, OU = crazyxing, CN = www.crazyxing.com
Getting CA Private Key
Enter pass phrase for private/ca.key.pem:
命令參數 命令說明
x509 簽發X.509格式證書命令
-req 表示證書輸入請求
-days 表示有效天數,這裏10000天
-sha1 表示證書摘要算法,這裏爲SHA1算法
-extensions 表示按OpenSSL配置文件v3_ca項添加擴展
-CA 表示CA證書,這裏certs/ca.cer
-CAkey 表示CA證書密鑰,這裏private/ca.key.pem
-CAserial 表示CA證書序列號文件,這裏ca.sr1
-CAcreateserial 表示創建CA證書序列號
-in 表示輸入文件,這裏private/ca.csr
-out 表示輸出文件,這裏certs/ca.cer

4、服務器證書轉換

D:\MyData\majx2\demoCA>openssl pkcs12 -export -clcerts -inkey private/server.key.pem -in certs/server.cer -out certs/server.p12
Enter pass phrase for private/server.key.pem:
Enter Export Password:
Verifying - Enter Export Password:
命令參數 命令說明
pkcs12 PKCS#12編碼格式證書命令
-export 表示導出證書
-clcerts 表示僅導出客戶證書
-inkey 表示輸入密鑰,這裏爲private/server.key.pem
-in 表示輸入文件,這裏爲certs/server.cer
-out 表示輸出文件,這裏爲certs/server.p12

構建客戶端證書

1、產生客戶密鑰

D:\MyData\majx2\demoCA>openssl genrsa -aes256 -out private/client.key.pem 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
................................................................................................+++++
.................+++++
e is 65537 (0x010001)
Enter pass phrase for private/client.key.pem:
Verifying - Enter pass phrase for private/client.key.pem:
命令參數 命令說明
genrsa 產生RSA密鑰命令
-aes256 使用AES算法(256位密鑰)對產生的私鑰加密。可選算法包括DES/DESede/IDEA/AES
-out 輸出路徑,這裏是private/server.key.pem

2、生成客戶證書籤發申請

D:\MyData\majx2\demoCA>openssl req -new -key private/client.key.pem -out private/client.csr -subj "/C=CN/ST=GD/L=GZ/O=crazyxing/OU=crazyxing/CN=crazyxing"
Enter pass phrase for private/client.key.pem:
命令參數 命令說明
req 生成證書籤發申請命令
-new 表示新請求
-key 密鑰,這裏pirvate/client.key.pem文件
-out 輸出路徑,這裏private/client.csr文件
-subj 指定用戶信息,這裏使用"crazyxing"作爲用戶名

3、簽發客戶證書

D:\MyData\majx2\demoCA>openssl ca -days 3650 -in private/client.csr -out certs/client.cer -cert certs/ca.cer -keyfile private/ca.key.pem
Using configuration from C:\Program Files\Common Files\SSL/openssl.cnf
Enter pass phrase for private/ca.key.pem:
Check that the request matches the signature
Signature ok
Certificate Details:
        Serial Number: 1 (0x1)
        Validity
            Not Before: Mar 10 02:44:01 2019 GMT
            Not After : Mar  7 02:44:01 2029 GMT
        Subject:
            countryName               = CN
            stateOrProvinceName       = GD
            organizationName          = crazyxing
            organizationalUnitName    = crazyxing
            commonName                = crazyxing
        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:FALSE
            Netscape Comment:
                OpenSSL Generated Certificate
            X509v3 Subject Key Identifier:
                AB:6F:ED:7E:D7:15:C2:6B:8C:F4:C7:5E:20:5A:15:A0:5C:DF:ED:8E
            X509v3 Authority Key Identifier:
                DirName:/C=CN/ST=GD/L=GZ/O=crazyxing/OU=crazyxing/CN=*.crazyxing.com
                serial:6F:AD:9A:9C:B9:FB:61:A6:0C:97:64:03:EE:5F:E4:22:28:C9:56:FA

Certificate is to be certified until Mar  7 02:44:01 2029 GMT (3650 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
命令參數 命令說明
ca 簽發證書命令
-days 表示有效天數,這裏3650天
-in 表示輸入文件,這裏private/client.csr
-out 表示輸出文件,這裏certs/client.cer
-cert 表示證書文件,這裏爲certs/ca.cer
-keyfile 表示根證書密鑰文件,這裏爲private/ca.key.pem

4、客戶證書轉換

D:\MyData\majx2\demoCA>openssl pkcs12 -export -inkey private/client.key.pem -in certs/client.cer -out certs/client.p12
Enter pass phrase for private/client.key.pem:
Enter Export Password:
Verifying - Enter Export Password:
命令參數 命令說明
pkcs12 PKCS#12編碼格式證書命令
-export 表示導出證書
-clcerts 表示僅導出客戶證書
-inkey 表示輸入密鑰,這裏爲private/client.key.pem
-in 表示輸入文件,這裏爲certs/client.cer
-out 表示輸出文件,這裏爲certs/client.p12

證書使用

先給出一個證書工具類

import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.crypto.Cipher;

public abstract class CertificateCoder {

    /**
     * 證書類型X509
     */
    public static final String CERT_TYPE = "X.509";

    /**
     * 密鑰庫類型PCKS12
     */
    private static final String STORE_TYPE = "PKCS12";

    /**
     * 由KeyStore獲得私鑰
     * 
     * @param keyStorePath
     *            密鑰庫路徑
     * @param alias
     *            別名
     * @param password
     *            密碼
     * @return PrivateKey 私鑰
     * @throws Exception
     */
    public static PrivateKey getPrivateKeyByKeyStore(String keyStorePath,
            String alias, String password) throws Exception {

        // 獲得密鑰庫
        KeyStore ks = getKeyStore(keyStorePath, password);

        // 獲得私鑰
        return (PrivateKey) ks.getKey(alias, password.toCharArray());

    }

    /**
     * 由Certificate獲得公鑰
     * 
     * @param certificatePath
     *            證書路徑
     * @return PublicKey 公鑰
     * @throws Exception
     */
    public static PublicKey getPublicKeyByCertificate(String certificatePath)
            throws Exception {

        // 獲得證書
        Certificate certificate = getCertificate(certificatePath);

        // 獲得公鑰
        return certificate.getPublicKey();

    }

    /**
     * 獲得Certificate
     * 
     * @param certificatePath
     *            證書路徑
     * @return Certificate 證書
     * @throws Exception
     */
    private static X509Certificate getCertificate(String certificatePath)
            throws Exception {

        // 實例化證書工廠
        CertificateFactory certificateFactory = CertificateFactory
                .getInstance(CERT_TYPE);

        // 取得證書文件流
        FileInputStream in = new FileInputStream(certificatePath);

        // 生成證書
        Certificate certificate = certificateFactory.generateCertificate(in);

        // 關閉證書文件流
        in.close();

        return (X509Certificate) certificate;
    }

    /**
     * 獲得KeyStore
     * 
     * @param keyStorePath
     *            密鑰庫路徑
     * @param password
     *            密碼
     * @return KeyStore 密鑰庫
     * @throws Exception
     */
    private static KeyStore getKeyStore(String keyStorePath, String password)
            throws Exception {

        // 實例化密鑰庫
        KeyStore ks = KeyStore.getInstance(STORE_TYPE);

        // 獲得密鑰庫文件流
        FileInputStream in = new FileInputStream(keyStorePath);
        // 加載密鑰庫
        ks.load(in, password.toCharArray());

        // 關閉密鑰庫文件流
        in.close();

        return ks;
    }

    /**
     * 私鑰加密
     * 
     * @param data
     *            待加密數據
     * @param keyStorePath
     *            密鑰庫路徑
     * @param alias
     *            別名
     * @param password
     *            密碼
     * @return byte[] 加密數據
     * @throws Exception
     */
    public static byte[] encryptByPrivateKey(byte[] data, String keyStorePath,
            String alias, String password) throws Exception {

        // 取得私鑰
        PrivateKey privateKey = getPrivateKeyByKeyStore(keyStorePath, alias,
                password);

        // 對數據加密
        Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);

        return cipher.doFinal(data);

    }

    /**
     * 私鑰解密
     * 
     * @param data
     *            待解密數據
     * @param keyStorePath
     *            密鑰庫路徑
     * @param alias
     *            別名
     * @param password
     *            密碼
     * @return byte[] 解密數據
     * @throws Exception
     */
    public static byte[] decryptByPrivateKey(byte[] data, String keyStorePath,
            String alias, String password) throws Exception {

        // 取得私鑰
        PrivateKey privateKey = getPrivateKeyByKeyStore(keyStorePath, alias,
                password);

        // 對數據加密
        Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateKey);

        return cipher.doFinal(data);

    }

    /**
     * 公鑰加密
     * 
     * @param data
     *            待加密數據
     * @param certificatePath
     *            證書路徑
     * @return byte[] 加密數據
     * @throws Exception
     */
    public static byte[] encryptByPublicKey(byte[] data, String certificatePath)
            throws Exception {

        // 取得公鑰
        PublicKey publicKey = getPublicKeyByCertificate(certificatePath);

        // 對數據加密
        Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);

        return cipher.doFinal(data);

    }

    /**
     * 公鑰解密
     * 
     * @param data
     *            待解密數據
     * @param certificatePath
     *            證書路徑
     * @return byte[] 解密數據
     * @throws Exception
     */
    public static byte[] decryptByPublicKey(byte[] data, String certificatePath)
            throws Exception {

        // 取得公鑰
        PublicKey publicKey = getPublicKeyByCertificate(certificatePath);

        // 對數據加密
        Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, publicKey);

        return cipher.doFinal(data);

    }

    /**
     * 簽名
     * 
     * @param keyStorePath
     *            密鑰庫路徑
     * @param alias
     *            別名
     * @param password
     *            密碼
     * @return byte[] 簽名
     * @throws Exception
     */
    public static byte[] sign(byte[] sign, String keyStorePath, String alias,
            String password, String certificatePath) throws Exception {

        // 獲得證書
        X509Certificate x509Certificate = getCertificate(certificatePath);

        // 構建簽名,由證書指定簽名算法
        Signature signature = Signature.getInstance(x509Certificate
                .getSigAlgName());

        // 獲取私鑰
        PrivateKey privateKey = getPrivateKeyByKeyStore(keyStorePath, alias,
                password);

        // 初始化簽名,由私鑰構建
        signature.initSign(privateKey);

        signature.update(sign);

        return signature.sign();
    }

    /**
     * 驗證簽名
     * 
     * @param data
     *            數據
     * @param sign
     *            簽名
     * @param certificatePath
     *            證書路徑
     * @return boolean 驗證通過爲真
     * @throws Exception
     */
    public static boolean verify(byte[] data, byte[] sign,
            String certificatePath) throws Exception {

        // 獲得證書
        X509Certificate x509Certificate = (X509Certificate) getCertificate(certificatePath);

        // 由證書構建簽名
        Signature signature = Signature.getInstance(x509Certificate
                .getSigAlgName());

        // 由證書初始化簽名,實際上是使用了證書中的公鑰
        signature.initVerify(x509Certificate);

        signature.update(data);

        return signature.verify(sign);

    }

}

下面是相應的單元測試

import org.apache.commons.codec.binary.Hex;
import org.junit.Test;
import static org.junit.Assert.*;

public class CertificateCoderTest {

    private String password = "123456";

    private String alias = "1";

    private String certificatePath = "D:/MyData/majx2/demoCA/certs/ca.cer";

    private String keyStorePath = "D:/MyData/majx2/demoCA/certs/ca.p12";

    /**
     * 公鑰加密——私鑰解密
     * 
     * @throws Exception
     */
    @Test
    public void test1() {

        try {
            System.err.println("公鑰加密——私鑰解密");
            String inputStr = "Ceritifcate";
            byte[] data = inputStr.getBytes();

            // 公鑰加密
            byte[] encrypt = CertificateCoder.encryptByPublicKey(data,
                    certificatePath);

            // 私鑰解密
            byte[] decrypt = CertificateCoder.decryptByPrivateKey(encrypt,
                    keyStorePath, alias, password);

            String outputStr = new String(decrypt);

            System.err.println("加密前:\n" + inputStr);

            System.err.println("解密後:\n" + outputStr);

            // 驗證數據一致
            assertArrayEquals(data, decrypt);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            fail(e.getMessage());
        }

    }

    /**
     * 私鑰加密——公鑰解密
     * 
     * @throws Exception
     */
    @Test
    public void test2() {

        System.err.println("私鑰加密——公鑰解密");

        String inputStr = "sign";
        byte[] data = inputStr.getBytes();

        try {
            // 私鑰加密
            byte[] encodedData = CertificateCoder.encryptByPrivateKey(data,
                    keyStorePath, alias, password);

            // 公鑰加密
            byte[] decodedData = CertificateCoder.decryptByPublicKey(
                    encodedData, certificatePath);

            String outputStr = new String(decodedData);

            System.err.println("加密前:\n" + inputStr);
            System.err.println("解密後:\n" + outputStr);

            // 校驗
            assertEquals(inputStr, outputStr);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            fail(e.getMessage());
        }
    }

    /**
     * 簽名驗證
     * 
     * @throws Exception
     */
    @Test
    public void testSign() {

        try {
            String inputStr = "簽名";
            byte[] data = inputStr.getBytes();
            System.err.println("私鑰簽名——公鑰驗證");

            // 產生簽名
            byte[] sign = CertificateCoder.sign(data, keyStorePath, alias,
                    password,certificatePath);
            System.err.println("簽名:\n" + Hex.encodeHexString(sign));

            // 驗證簽名
            boolean status = CertificateCoder.verify(data, sign,
                    certificatePath);
            System.err.println("狀態:\n" + status);

            // 校驗
            assertTrue(status);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            fail(e.getMessage());
        }

    }

}

參考:

Java證書工具keytool用法總結

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章