今天來點實際工作中的硬通貨!
與計費系統打交道,少不了用到加密/解密實現。為了安全起見,通過非對稱加密交換對稱加密金鑰更是不可或缺。那麼需要通過什麼載體傳遞非對稱演算法公鑰/私鑰資訊?數字證照是公鑰的載體,而金鑰庫可以包含公鑰、私鑰資訊。
JKS和PKCS#12都是比較常用的兩種金鑰庫格式/標準。對於前者,搞Java開發,尤其是接觸過HTTPS平臺的朋友,並不陌生。JKS檔案(通常為*.jks或*.keystore,副檔名無關)可以通過Java原生工具——KeyTool生成;而後者PKCS#12檔案(通常為*.p12或*.pfx,意味個人資訊交換檔案),則是通過更為常用的OpenSSL工具產生。
當然,這兩者之間是可以通過匯入/匯出的方式進行轉換的!當然,這種轉換需要通過KeyTool工具進行!
迴歸正題,計費同事遇到一個難題:合作方交給他們一個*.pfx檔案,需要他們從中提取金鑰,然後進行加密互動。其實,通過Java直接操作金鑰庫檔案(或個人資訊交換檔案)對於一般Java開發人員來說,這都是個冷門。不接觸數字安全,根本不知所云。況且,Java原生的金鑰庫檔案格式為JKS,如何操作*.pfx檔案?金鑰庫操作需要獲知金鑰庫別名,*.pfx別名是什麼?!接下來就解決這些問題!
方案:
- 通過keytool金鑰庫匯入命令importkeystore,將金鑰庫格式由PKCS#12轉換為JKS。
- 檢索新生成的金鑰庫檔案,提取別名資訊。
- 由金鑰庫檔案匯出數字證照(這裡將用到別名)。
- 通過程式碼提取公鑰/私鑰、簽名演算法等
先看格式轉換:
- echo 格式轉換
- keytool -importkeystore -v -srckeystore zlex.pfx -srcstoretype pkcs12 -srcstorepass 123456 -destkeystore zlex.keystore -deststoretype jks -deststorepass 123456
-importkeystore匯入金鑰庫,通過格式設定,我們可以將PKCS#12檔案轉換為JKS格式。
-v顯示詳情
-srckeystore源金鑰庫,這裡是zlex.pfx
-srcstoretype源金鑰庫格式,這裡為pkcs12
-srcstorepass源金鑰庫密碼,這裡為123456
-destkeystore目標金鑰庫,這裡為zlex.keystore
-deststoretype目標金鑰庫格式,這裡為jks,預設值也如此
-deststorepass目標金鑰庫密碼,這裡為123456
通過這個操作,我們能夠獲得所需的金鑰庫檔案zlex.keystore。
這時,我們已經獲得了金鑰庫檔案,只要確定對應的別名資訊,就可以提取公鑰/私鑰,以及數字證照,進行加密互動了!
- echo 檢視證照
- keytool -list -keystore zlex.keystore -storepass 123456 -v
-list列舉金鑰庫
-keystore金鑰庫,這裡是zlex.keystore
-storepass金鑰庫密碼,這裡是123456
-v顯示詳情
這裡需要細緻觀察一下別名資訊!!!就是紅框中的數字1!!!
現在,我們把證照匯出!
- echo 匯出證照
- keytool -exportcert -alias 1 -keystore zlex.keystore -file zlex.crt -storepass 123456
-exportcert匯出證照
-alias別名,這裡是1
-keystore金鑰庫,這裡是zlex.keystore
-file證照檔案,這裡是zlex.crt
-storepass金鑰庫密碼,這裡是123456
現在證照也匯出了,我們可以提取公鑰/私鑰,進行加密/解密,簽名/驗證操作了!當然,即便沒有證照,我們也能夠通過金鑰庫(JKS格式)檔案獲得證照,以及公鑰/私鑰、簽名演算法等。
補充程式碼, 其實就是對Java加密技術(八)的修改!
- /**
- * 2010-8-11
- */
- 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 java.util.Date;
- import javax.crypto.Cipher;
- /**
- * 證照操作類
- *
- * @author <a href="mailto:zlex.dongliang@gmail.com">樑棟</a>
- * @since 1.0
- */
- public class CertificateCoder {
- /**
- * Java金鑰庫(Java Key Store,JKS)KEY_STORE
- */
- public static final String KEY_STORE = "JKS";
- public static final String X509 = "X.509";
- /**
- * 由 KeyStore獲得私鑰
- *
- * @param keyStorePath
- * @param keyStorePassword
- * @param alias
- * @param aliasPassword
- * @return
- * @throws Exception
- */
- private static PrivateKey getPrivateKey(String keyStorePath,
- String keyStorePassword, String alias, String aliasPassword)
- throws Exception {
- KeyStore ks = getKeyStore(keyStorePath, keyStorePassword);
- PrivateKey key = (PrivateKey) ks.getKey(alias,
- aliasPassword.toCharArray());
- return key;
- }
- /**
- * 由 Certificate獲得公鑰
- *
- * @param certificatePath
- * @return
- * @throws Exception
- */
- private static PublicKey getPublicKey(String certificatePath)
- throws Exception {
- Certificate certificate = getCertificate(certificatePath);
- PublicKey key = certificate.getPublicKey();
- return key;
- }
- /**
- * 獲得Certificate
- *
- * @param certificatePath
- * @return
- * @throws Exception
- */
- private static Certificate getCertificate(String certificatePath)
- throws Exception {
- CertificateFactory certificateFactory = CertificateFactory
- .getInstance(X509);
- FileInputStream in = new FileInputStream(certificatePath);
- Certificate certificate = certificateFactory.generateCertificate(in);
- in.close();
- return certificate;
- }
- /**
- * 獲得Certificate
- *
- * @param keyStorePath
- * @param keyStorePassword
- * @param alias
- * @return
- * @throws Exception
- */
- private static Certificate getCertificate(String keyStorePath,
- String keyStorePassword, String alias) throws Exception {
- KeyStore ks = getKeyStore(keyStorePath, keyStorePassword);
- Certificate certificate = ks.getCertificate(alias);
- return certificate;
- }
- /**
- * 獲得KeyStore
- *
- * @param keyStorePath
- * @param password
- * @return
- * @throws Exception
- */
- private static KeyStore getKeyStore(String keyStorePath, String password)
- throws Exception {
- FileInputStream is = new FileInputStream(keyStorePath);
- KeyStore ks = KeyStore.getInstance(KEY_STORE);
- ks.load(is, password.toCharArray());
- is.close();
- return ks;
- }
- /**
- * 私鑰加密
- *
- * @param data
- * @param keyStorePath
- * @param keyStorePassword
- * @param alias
- * @param aliasPassword
- * @return
- * @throws Exception
- */
- public static byte[] encryptByPrivateKey(byte[] data, String keyStorePath,
- String keyStorePassword, String alias, String aliasPassword)
- throws Exception {
- // 取得私鑰
- PrivateKey privateKey = getPrivateKey(keyStorePath, keyStorePassword,
- alias, aliasPassword);
- // 對資料加密
- Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
- cipher.init(Cipher.ENCRYPT_MODE, privateKey);
- return cipher.doFinal(data);
- }
- /**
- * 私鑰解密
- *
- * @param data
- * @param keyStorePath
- * @param alias
- * @param keyStorePassword
- * @param aliasPassword
- * @return
- * @throws Exception
- */
- public static byte[] decryptByPrivateKey(byte[] data, String keyStorePath,
- String alias, String keyStorePassword, String aliasPassword)
- throws Exception {
- // 取得私鑰
- PrivateKey privateKey = getPrivateKey(keyStorePath, keyStorePassword,
- alias, aliasPassword);
- // 對資料加密
- Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
- cipher.init(Cipher.DECRYPT_MODE, privateKey);
- return cipher.doFinal(data);
- }
- /**
- * 公鑰加密
- *
- * @param data
- * @param certificatePath
- * @return
- * @throws Exception
- */
- public static byte[] encryptByPublicKey(byte[] data, String certificatePath)
- throws Exception {
- // 取得公鑰
- PublicKey publicKey = getPublicKey(certificatePath);
- // 對資料加密
- Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
- cipher.init(Cipher.ENCRYPT_MODE, publicKey);
- return cipher.doFinal(data);
- }
- /**
- * 公鑰解密
- *
- * @param data
- * @param certificatePath
- * @return
- * @throws Exception
- */
- public static byte[] decryptByPublicKey(byte[] data, String certificatePath)
- throws Exception {
- // 取得公鑰
- PublicKey publicKey = getPublicKey(certificatePath);
- // 對資料加密
- Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
- cipher.init(Cipher.DECRYPT_MODE, publicKey);
- return cipher.doFinal(data);
- }
- /**
- * 驗證Certificate
- *
- * @param certificatePath
- * @return
- */
- public static boolean verifyCertificate(String certificatePath) {
- return verifyCertificate(new Date(), certificatePath);
- }
- /**
- * 驗證Certificate是否過期或無效
- *
- * @param date
- * @param certificatePath
- * @return
- */
- public static boolean verifyCertificate(Date date, String certificatePath) {
- boolean status = true;
- try {
- // 取得證照
- Certificate certificate = getCertificate(certificatePath);
- // 驗證證照是否過期或無效
- status = verifyCertificate(date, certificate);
- } catch (Exception e) {
- status = false;
- }
- return status;
- }
- /**
- * 驗證證照是否過期或無效
- *
- * @param date
- * @param certificate
- * @return
- */
- private static boolean verifyCertificate(Date date, Certificate certificate) {
- boolean status = true;
- try {
- X509Certificate x509Certificate = (X509Certificate) certificate;
- x509Certificate.checkValidity(date);
- } catch (Exception e) {
- status = false;
- }
- return status;
- }
- /**
- * 簽名
- *
- * @param keyStorePath
- * @param alias
- * @param keyStorePassword
- * @param aliasPassword
- * @return
- * @throws Exception
- */
- public static byte[] sign(byte[] sign, String keyStorePath, String alias,
- String keyStorePassword, String aliasPassword) throws Exception {
- // 獲得證照
- X509Certificate x509Certificate = (X509Certificate) getCertificate(
- keyStorePath, keyStorePassword, alias);
- // 取得私鑰
- PrivateKey privateKey = getPrivateKey(keyStorePath, keyStorePassword,
- alias, aliasPassword);
- // 構建簽名
- Signature signature = Signature.getInstance(x509Certificate
- .getSigAlgName());
- signature.initSign(privateKey);
- signature.update(sign);
- return signature.sign();
- }
- /**
- * 驗證簽名
- *
- * @param data
- * @param sign
- * @param certificatePath
- * @return
- * @throws Exception
- */
- public static boolean verify(byte[] data, byte[] sign,
- String certificatePath) throws Exception {
- // 獲得證照
- X509Certificate x509Certificate = (X509Certificate) getCertificate(certificatePath);
- // 獲得公鑰
- PublicKey publicKey = x509Certificate.getPublicKey();
- // 構建簽名
- Signature signature = Signature.getInstance(x509Certificate
- .getSigAlgName());
- signature.initVerify(publicKey);
- signature.update(data);
- return signature.verify(sign);
- }
- /**
- * 驗證Certificate
- *
- * @param keyStorePath
- * @param keyStorePassword
- * @param alias
- * @return
- */
- public static boolean verifyCertificate(Date date, String keyStorePath,
- String keyStorePassword, String alias) {
- boolean status = true;
- try {
- Certificate certificate = getCertificate(keyStorePath,
- keyStorePassword, alias);
- status = verifyCertificate(date, certificate);
- } catch (Exception e) {
- status = false;
- }
- return status;
- }
- /**
- * 驗證Certificate
- *
- * @param keyStorePath
- * @param keyStorePassword
- * @param alias
- * @return
- */
- public static boolean verifyCertificate(String keyStorePath,
- String keyStorePassword, String alias) {
- return verifyCertificate(new Date(), keyStorePath, keyStorePassword,
- alias);
- }
- }
相信上述程式碼已經幫朋友們解決了相當多的問題!
給出測試類:
- import static org.junit.Assert.*;
- import java.util.Date;
- import org.apache.commons.codec.binary.Hex;
- import org.junit.Test;
- /**
- * 證照操作驗證類
- *
- * @author <a href="mailto:zlex.dongliang@gmail.com">樑棟</a>
- * @version 1.0
- * @since 1.0
- */
- public class CertificateCoderTest {
- private String certificatePath = "zlex.crt";
- private String keyStorePath = "zlex.keystore";
- private String keyStorePassword = "123456";
- private String aliasPassword = "123456";
- private String alias = "1";
- @Test
- public void test() 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, keyStorePassword, aliasPassword);
- String outputStr = new String(decrypt);
- System.err.println("加密前: " + inputStr + "\n\r" + "解密後: " + outputStr);
- // 驗證資料一致
- assertArrayEquals(data, decrypt);
- // 驗證證照有效
- assertTrue(CertificateCoder.verifyCertificate(certificatePath));
- }
- @Test
- public void testSign() throws Exception {
- System.err.println("私鑰加密——公鑰解密");
- String inputStr = "sign";
- byte[] data = inputStr.getBytes();
- byte[] encodedData = CertificateCoder.encryptByPrivateKey(data,
- keyStorePath, keyStorePassword, alias, aliasPassword);
- byte[] decodedData = CertificateCoder.decryptByPublicKey(encodedData,
- certificatePath);
- String outputStr = new String(decodedData);
- System.err.println("加密前: " + inputStr + "\n\r" + "解密後: " + outputStr);
- assertEquals(inputStr, outputStr);
- System.err.println("私鑰簽名——公鑰驗證簽名");
- // 產生簽名
- byte[] sign = CertificateCoder.sign(encodedData, keyStorePath, alias,
- keyStorePassword, aliasPassword);
- System.err.println("簽名:\r" + Hex.encodeHexString(sign));
- // 驗證簽名
- boolean status = CertificateCoder.verify(encodedData, sign,
- certificatePath);
- System.err.println("狀態:\r" + status);
- assertTrue(status);
- }
- @Test
- public void testVerify() throws Exception {
- System.err.println("金鑰庫證照有效期驗證");
- boolean status = CertificateCoder.verifyCertificate(new Date(),
- keyStorePath, keyStorePassword, alias);
- System.err.println("證照狀態:\r" + status);
- assertTrue(status);
- }
- }
第一個測試方法,用於提取公鑰/私鑰進行加密/解密操作。
第二個測試方法,用於提取簽名演算法進行簽名/驗證操作。
第三個測試方法,用於測試金鑰庫該別名對應的證照,當前日期下,是否有效。
OK,任務完成,金鑰成功提取,剩下的都是程式碼基本功了!