JKS和PKCS#12

时间:2023-03-09 05:17:38
JKS和PKCS#12

今天来点实际工作中的硬通货!
与计费系统打交道,少不了用到加密/解密实现。为了安全起见,通过非对称加密交换对称加密密钥更是不可或缺。那么需要通过什么载体传递非对称算法公钥/私钥信息?数字证书是公钥的载体,而密钥库可以包含公钥、私钥信息。

JKSPKCS#12都是比较常用的两种密钥库格式/标准。对于前者,搞Java开发,尤其是接触过HTTPS平台的朋友,并不陌生。JKS文件(通常为*.jks或*.keystore,扩展名无关)可以通过Java原生工具——KeyTool生成;而后者PKCS#12文件(通常为*.p12或*.pfx,意味个人信息交换文件),则是通过更为常用的OpenSSL工具产生。

当然,这两者之间是可以通过导入/导出的方式进行转换的!当然,这种转换需要通过KeyTool工具进行!

回归正题,计费同事遇到一个难题:合作方交给他们一个*.pfx文件,需要他们从中提取密钥,然后进行加密交互。其实,通过Java直接操作密钥库文件(或个人信息交换文件)对于一般Java开发人员来说,这都是个冷门。不接触数字安全,根本不知所云。况且,Java原生的密钥库文件格式为JKS,如何操作*.pfx文件?密钥库操作需要获知密钥库别名,*.pfx别名是什么?!接下来就解决这些问题!JKS和PKCS#12



方案:

  1. 通过keytool密钥库导入命令importkeystore,将密钥库格式由PKCS#12转换为JKS。
  2. 检索新生成的密钥库文件,提取别名信息。
  3. 由密钥库文件导出数字证书(这里将用到别名)。
  4. 通过代码提取公钥/私钥、签名算法等

先看格式转换:

  1. echo 格式转换
  2. 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。

JKS和PKCS#12

这时,我们已经获得了密钥库文件,只要确定对应的别名信息,就可以提取公钥/私钥,以及数字证书,进行加密交互了!JKS和PKCS#12

  1. echo 查看证书
  2. keytool -list -keystore zlex.keystore -storepass 123456 -v

-list列举密钥库

-keystore密钥库,这里是zlex.keystore

-storepass密钥库密码,这里是123456

-v显示详情

JKS和PKCS#12

这里需要细致观察一下别名信息!!!就是红框中的数字1!!!





现在,我们把证书导出!

  1. echo 导出证书
  2. keytool -exportcert -alias 1 -keystore zlex.keystore -file zlex.crt -storepass 123456

-exportcert导出证书

-alias别名,这里是1

-keystore密钥库,这里是zlex.keystore

-file证书文件,这里是zlex.crt

-storepass密钥库密码,这里是123456

JKS和PKCS#12

现在证书也导出了,我们可以提取公钥/私钥,进行加密/解密,签名/验证操作了!当然,即便没有证书,我们也能够通过密钥库(JKS格式)文件获得证书,以及公钥/私钥、签名算法等。

补充代码, 其实就是对Java加密技术(八)的修改!JKS和PKCS#12

  1. /**
  2. * 2010-8-11
  3. */
  4. import java.io.FileInputStream;
  5. import java.security.KeyStore;
  6. import java.security.PrivateKey;
  7. import java.security.PublicKey;
  8. import java.security.Signature;
  9. import java.security.cert.Certificate;
  10. import java.security.cert.CertificateFactory;
  11. import java.security.cert.X509Certificate;
  12. import java.util.Date;
  13. import javax.crypto.Cipher;
  14. /**
  15. * 证书操作类
  16. *
  17. * @author <a href="mailto:zlex.dongliang@gmail.com">梁栋</a>
  18. * @since 1.0
  19. */
  20. public class CertificateCoder {
  21. /**
  22. * Java密钥库(Java Key Store,JKS)KEY_STORE
  23. */
  24. public static final String KEY_STORE = "JKS";
  25. public static final String X509 = "X.509";
  26. /**
  27. * 由 KeyStore获得私钥
  28. *
  29. * @param keyStorePath
  30. * @param keyStorePassword
  31. * @param alias
  32. * @param aliasPassword
  33. * @return
  34. * @throws Exception
  35. */
  36. private static PrivateKey getPrivateKey(String keyStorePath,
  37. String keyStorePassword, String alias, String aliasPassword)
  38. throws Exception {
  39. KeyStore ks = getKeyStore(keyStorePath, keyStorePassword);
  40. PrivateKey key = (PrivateKey) ks.getKey(alias,
  41. aliasPassword.toCharArray());
  42. return key;
  43. }
  44. /**
  45. * 由 Certificate获得公钥
  46. *
  47. * @param certificatePath
  48. * @return
  49. * @throws Exception
  50. */
  51. private static PublicKey getPublicKey(String certificatePath)
  52. throws Exception {
  53. Certificate certificate = getCertificate(certificatePath);
  54. PublicKey key = certificate.getPublicKey();
  55. return key;
  56. }
  57. /**
  58. * 获得Certificate
  59. *
  60. * @param certificatePath
  61. * @return
  62. * @throws Exception
  63. */
  64. private static Certificate getCertificate(String certificatePath)
  65. throws Exception {
  66. CertificateFactory certificateFactory = CertificateFactory
  67. .getInstance(X509);
  68. FileInputStream in = new FileInputStream(certificatePath);
  69. Certificate certificate = certificateFactory.generateCertificate(in);
  70. in.close();
  71. return certificate;
  72. }
  73. /**
  74. * 获得Certificate
  75. *
  76. * @param keyStorePath
  77. * @param keyStorePassword
  78. * @param alias
  79. * @return
  80. * @throws Exception
  81. */
  82. private static Certificate getCertificate(String keyStorePath,
  83. String keyStorePassword, String alias) throws Exception {
  84. KeyStore ks = getKeyStore(keyStorePath, keyStorePassword);
  85. Certificate certificate = ks.getCertificate(alias);
  86. return certificate;
  87. }
  88. /**
  89. * 获得KeyStore
  90. *
  91. * @param keyStorePath
  92. * @param password
  93. * @return
  94. * @throws Exception
  95. */
  96. private static KeyStore getKeyStore(String keyStorePath, String password)
  97. throws Exception {
  98. FileInputStream is = new FileInputStream(keyStorePath);
  99. KeyStore ks = KeyStore.getInstance(KEY_STORE);
  100. ks.load(is, password.toCharArray());
  101. is.close();
  102. return ks;
  103. }
  104. /**
  105. * 私钥加密
  106. *
  107. * @param data
  108. * @param keyStorePath
  109. * @param keyStorePassword
  110. * @param alias
  111. * @param aliasPassword
  112. * @return
  113. * @throws Exception
  114. */
  115. public static byte[] encryptByPrivateKey(byte[] data, String keyStorePath,
  116. String keyStorePassword, String alias, String aliasPassword)
  117. throws Exception {
  118. // 取得私钥
  119. PrivateKey privateKey = getPrivateKey(keyStorePath, keyStorePassword,
  120. alias, aliasPassword);
  121. // 对数据加密
  122. Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
  123. cipher.init(Cipher.ENCRYPT_MODE, privateKey);
  124. return cipher.doFinal(data);
  125. }
  126. /**
  127. * 私钥解密
  128. *
  129. * @param data
  130. * @param keyStorePath
  131. * @param alias
  132. * @param keyStorePassword
  133. * @param aliasPassword
  134. * @return
  135. * @throws Exception
  136. */
  137. public static byte[] decryptByPrivateKey(byte[] data, String keyStorePath,
  138. String alias, String keyStorePassword, String aliasPassword)
  139. throws Exception {
  140. // 取得私钥
  141. PrivateKey privateKey = getPrivateKey(keyStorePath, keyStorePassword,
  142. alias, aliasPassword);
  143. // 对数据加密
  144. Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
  145. cipher.init(Cipher.DECRYPT_MODE, privateKey);
  146. return cipher.doFinal(data);
  147. }
  148. /**
  149. * 公钥加密
  150. *
  151. * @param data
  152. * @param certificatePath
  153. * @return
  154. * @throws Exception
  155. */
  156. public static byte[] encryptByPublicKey(byte[] data, String certificatePath)
  157. throws Exception {
  158. // 取得公钥
  159. PublicKey publicKey = getPublicKey(certificatePath);
  160. // 对数据加密
  161. Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
  162. cipher.init(Cipher.ENCRYPT_MODE, publicKey);
  163. return cipher.doFinal(data);
  164. }
  165. /**
  166. * 公钥解密
  167. *
  168. * @param data
  169. * @param certificatePath
  170. * @return
  171. * @throws Exception
  172. */
  173. public static byte[] decryptByPublicKey(byte[] data, String certificatePath)
  174. throws Exception {
  175. // 取得公钥
  176. PublicKey publicKey = getPublicKey(certificatePath);
  177. // 对数据加密
  178. Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
  179. cipher.init(Cipher.DECRYPT_MODE, publicKey);
  180. return cipher.doFinal(data);
  181. }
  182. /**
  183. * 验证Certificate
  184. *
  185. * @param certificatePath
  186. * @return
  187. */
  188. public static boolean verifyCertificate(String certificatePath) {
  189. return verifyCertificate(new Date(), certificatePath);
  190. }
  191. /**
  192. * 验证Certificate是否过期或无效
  193. *
  194. * @param date
  195. * @param certificatePath
  196. * @return
  197. */
  198. public static boolean verifyCertificate(Date date, String certificatePath) {
  199. boolean status = true;
  200. try {
  201. // 取得证书
  202. Certificate certificate = getCertificate(certificatePath);
  203. // 验证证书是否过期或无效
  204. status = verifyCertificate(date, certificate);
  205. } catch (Exception e) {
  206. status = false;
  207. }
  208. return status;
  209. }
  210. /**
  211. * 验证证书是否过期或无效
  212. *
  213. * @param date
  214. * @param certificate
  215. * @return
  216. */
  217. private static boolean verifyCertificate(Date date, Certificate certificate) {
  218. boolean status = true;
  219. try {
  220. X509Certificate x509Certificate = (X509Certificate) certificate;
  221. x509Certificate.checkValidity(date);
  222. } catch (Exception e) {
  223. status = false;
  224. }
  225. return status;
  226. }
  227. /**
  228. * 签名
  229. *
  230. * @param keyStorePath
  231. * @param alias
  232. * @param keyStorePassword
  233. * @param aliasPassword
  234. * @return
  235. * @throws Exception
  236. */
  237. public static byte[] sign(byte[] sign, String keyStorePath, String alias,
  238. String keyStorePassword, String aliasPassword) throws Exception {
  239. // 获得证书
  240. X509Certificate x509Certificate = (X509Certificate) getCertificate(
  241. keyStorePath, keyStorePassword, alias);
  242. // 取得私钥
  243. PrivateKey privateKey = getPrivateKey(keyStorePath, keyStorePassword,
  244. alias, aliasPassword);
  245. // 构建签名
  246. Signature signature = Signature.getInstance(x509Certificate
  247. .getSigAlgName());
  248. signature.initSign(privateKey);
  249. signature.update(sign);
  250. return signature.sign();
  251. }
  252. /**
  253. * 验证签名
  254. *
  255. * @param data
  256. * @param sign
  257. * @param certificatePath
  258. * @return
  259. * @throws Exception
  260. */
  261. public static boolean verify(byte[] data, byte[] sign,
  262. String certificatePath) throws Exception {
  263. // 获得证书
  264. X509Certificate x509Certificate = (X509Certificate) getCertificate(certificatePath);
  265. // 获得公钥
  266. PublicKey publicKey = x509Certificate.getPublicKey();
  267. // 构建签名
  268. Signature signature = Signature.getInstance(x509Certificate
  269. .getSigAlgName());
  270. signature.initVerify(publicKey);
  271. signature.update(data);
  272. return signature.verify(sign);
  273. }
  274. /**
  275. * 验证Certificate
  276. *
  277. * @param keyStorePath
  278. * @param keyStorePassword
  279. * @param alias
  280. * @return
  281. */
  282. public static boolean verifyCertificate(Date date, String keyStorePath,
  283. String keyStorePassword, String alias) {
  284. boolean status = true;
  285. try {
  286. Certificate certificate = getCertificate(keyStorePath,
  287. keyStorePassword, alias);
  288. status = verifyCertificate(date, certificate);
  289. } catch (Exception e) {
  290. status = false;
  291. }
  292. return status;
  293. }
  294. /**
  295. * 验证Certificate
  296. *
  297. * @param keyStorePath
  298. * @param keyStorePassword
  299. * @param alias
  300. * @return
  301. */
  302. public static boolean verifyCertificate(String keyStorePath,
  303. String keyStorePassword, String alias) {
  304. return verifyCertificate(new Date(), keyStorePath, keyStorePassword,
  305. alias);
  306. }
  307. }

相信上述代码已经帮朋友们解决了相当多的问题!JKS和PKCS#12

给出测试类:

  1. import static org.junit.Assert.*;
  2. import java.util.Date;
  3. import org.apache.commons.codec.binary.Hex;
  4. import org.junit.Test;
  5. /**
  6. * 证书操作验证类
  7. *
  8. * @author <a href="mailto:zlex.dongliang@gmail.com">梁栋</a>
  9. * @version 1.0
  10. * @since 1.0
  11. */
  12. public class CertificateCoderTest {
  13. private String certificatePath = "zlex.crt";
  14. private String keyStorePath = "zlex.keystore";
  15. private String keyStorePassword = "123456";
  16. private String aliasPassword = "123456";
  17. private String alias = "1";
  18. @Test
  19. public void test() throws Exception {
  20. System.err.println("公钥加密——私钥解密");
  21. String inputStr = "Ceritifcate";
  22. byte[] data = inputStr.getBytes();
  23. byte[] encrypt = CertificateCoder.encryptByPublicKey(data,
  24. certificatePath);
  25. byte[] decrypt = CertificateCoder.decryptByPrivateKey(encrypt,
  26. keyStorePath, alias, keyStorePassword, aliasPassword);
  27. String outputStr = new String(decrypt);
  28. System.err.println("加密前: " + inputStr + "\n\r" + "解密后: " + outputStr);
  29. // 验证数据一致
  30. assertArrayEquals(data, decrypt);
  31. // 验证证书有效
  32. assertTrue(CertificateCoder.verifyCertificate(certificatePath));
  33. }
  34. @Test
  35. public void testSign() throws Exception {
  36. System.err.println("私钥加密——公钥解密");
  37. String inputStr = "sign";
  38. byte[] data = inputStr.getBytes();
  39. byte[] encodedData = CertificateCoder.encryptByPrivateKey(data,
  40. keyStorePath, keyStorePassword, alias, aliasPassword);
  41. byte[] decodedData = CertificateCoder.decryptByPublicKey(encodedData,
  42. certificatePath);
  43. String outputStr = new String(decodedData);
  44. System.err.println("加密前: " + inputStr + "\n\r" + "解密后: " + outputStr);
  45. assertEquals(inputStr, outputStr);
  46. System.err.println("私钥签名——公钥验证签名");
  47. // 产生签名
  48. byte[] sign = CertificateCoder.sign(encodedData, keyStorePath, alias,
  49. keyStorePassword, aliasPassword);
  50. System.err.println("签名:\r" + Hex.encodeHexString(sign));
  51. // 验证签名
  52. boolean status = CertificateCoder.verify(encodedData, sign,
  53. certificatePath);
  54. System.err.println("状态:\r" + status);
  55. assertTrue(status);
  56. }
  57. @Test
  58. public void testVerify() throws Exception {
  59. System.err.println("密钥库证书有效期验证");
  60. boolean status = CertificateCoder.verifyCertificate(new Date(),
  61. keyStorePath, keyStorePassword, alias);
  62. System.err.println("证书状态:\r" + status);
  63. assertTrue(status);
  64. }
  65. }

第一个测试方法,用于提取公钥/私钥进行加密/解密操作。

第二个测试方法,用于提取签名算法进行签名/验证操作。

第三个测试方法,用于测试密钥库该别名对应的证书,当前日期下,是否有效。

JKS和PKCS#12





OK,任务完成,密钥成功提取,剩下的都是代码基本功了!JKS和PKCS#12