openssl建立私钥,获取公钥,建立证书都是比较简单的,就几个指令,很快就能够搞定,之因此说简单,是由于证书里面的基本参数配置不须要咱们组装,只须要将命令行里面须要的几个参数配置进去便可。可是呢,用java代码,原生建立证书,其实须要咱们了解的内容就要稍微多点,去填充建立证书里面的所须要的参数,逐行填充。html
openssl证书的格式默认是PEM的,即Privacy Enhanced Mail,说白了,就是将建立后的证书元素数据通过Base64编码,而后添加相似----BEGIN CERTIFICATE----、----END CERTIFICATE----这样的头和尾,是ASCII编码的纯字符串格式。java
如今完整的介绍一下java建立PEM证书的逻辑。都遵循X509的协议,建立证书,和openssl逻辑有点不一样的是,java建立证书不须要构建CSR(Certificate Sign Request)这个步骤,一样须要建立私钥/公钥,建立CA证书,构建设备证书。算法
本博文是对前叙的博文的一个补充 MQTT研究之EMQ:【JAVA代码构建X509证书】,内容涉及的话题相同,可是角度有些不同。安全
1. 私钥/公钥建立dom
/** * 建立私钥和公钥的数据,以一个map的形式返回。 * * @param keySize 私钥的长度 * @param keyAlgo 建立私钥的算法,例如RSA,DSA等 * @return map 私钥和公钥对信息 */ public static Map<String, String> createKeys(int keySize, String keyAlgo){ //为RSA算法建立一个KeyPairGenerator对象 KeyPairGenerator kpg; try{ kpg = KeyPairGenerator.getInstance(keyAlgo); }catch(NoSuchAlgorithmException e) { throw new IllegalArgumentException("No such algorithm-->[" + keyAlgo + "]"); } //初始化KeyPairGenerator对象,密钥长度 kpg.initialize(keySize); //生成密匙对 KeyPair keyPair = kpg.generateKeyPair(); //获得公钥 Key publicKey = keyPair.getPublic(); String publicKeyStr = Base64.encodeBase64URLSafeString(publicKey.getEncoded()); //获得私钥 Key privateKey = keyPair.getPrivate(); String privateKeyStr = Base64.encodeBase64URLSafeString(privateKey.getEncoded()); Map<String, String> keyPairMap = new HashMap<String, String>(); keyPairMap.put(PUBLIC_KEY, publicKeyStr); keyPairMap.put(PRIVATE_KEY, privateKeyStr); return keyPairMap; }
2. 建立CA自签名证书ide
/** * 建立根证书, 并保存根证书到指定路径的文件中, crt和key分开存储文件。 * 建立SSL根证书的逻辑,很重要,此函数调用频次不高,建立根证书,也就是自签名证书。 * * @param algorithm 私钥安全算法,e.g. RSA * @param keySize 私钥长度,越长越安全,RSA要求不能小于512, e.g. 2048 * @param digestSignAlgo 信息摘要以及签名算法 e.g. SHA256withRSA * @param subj 证书全部者信息描述,e.g. CN=iotp,OU=tkcloud,O=TanKang,L=wuhan,S=hubei,C=CN * @param validDays 证书有效期天数,e.g. 3650即10年 * @param rootCACrtPath 根证书所要存入的全路径,e.g. /opt/certs/iot/rootCA.crt * @param rootCAKeyPath 根证书对应秘钥key所要存入的全路径,e.g. /opt/certs/iot/rootCA.key * @return 私钥和证书对的map对象 */ public static HashMap<String, Object> createRootCA(String algorithm, int keySize, String digestSignAlgo, String subj, long validDays, String rootCACrtPath, String rootCAKeyPath) { //参数分别为 公钥算法 签名算法 providerName(由于不知道确切的 只好使用null 既使用默认的provider) CertAndKeyGen cak = null; try { cak = new CertAndKeyGen(algorithm, digestSignAlgo,null); //生成一对key 参数为key的长度 对于rsa不能小于512 cak.generate(keySize); cak.setRandom(new SecureRandom()); //证书拥有者subject的描述name X500Name subject = new X500Name(subj); //给证书配置扩展信息 PublicKey publicKey = cak.getPublicKey(); PrivateKey privateKey = cak.getPrivateKey(); CertificateExtensions certExts = new CertificateExtensions(); certExts.set(SubjectKeyIdentifierExtension.NAME, new SubjectKeyIdentifierExtension((new KeyIdentifier(publicKey)).getIdentifier())); certExts.set(AuthorityKeyIdentifierExtension.NAME, new AuthorityKeyIdentifierExtension(new KeyIdentifier(publicKey), null, null)); //设置是否根证书 BasicConstraintsExtension bce = new BasicConstraintsExtension(true, -1); certExts.set(BasicConstraintsExtension.NAME, new BasicConstraintsExtension(false, bce.getExtensionValue())); //配置证书的有效期,并生成根证书(自签名证书) X509Certificate certificate = cak.getSelfCertificate(subject, new Date(),validDays * 24L * 60L * 60L, certExts); HashMap<String, Object> rootCA = new HashMap<>(); rootCA.put("key", privateKey); rootCA.put("crt", certificate); exportCrt(certificate, rootCACrtPath); exportKey(privateKey, rootCAKeyPath); return rootCA; } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { e.printStackTrace(); } catch (CertificateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (SignatureException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return null; }
3. 建立用户证书函数
/** * 建立X509的证书, 由ca证书完成签名。 * * subject,issuer都遵循X500Principle规范, * 即: X500Principal由可分辨名称表示,例如“CN = Duke,OU = JavaSoft,O = Sun Microsystems,C = US”。 * * @param ca 根证书对象 * @param caKey CA证书对应的私钥对象 * @param publicKey 待签发证书的公钥对象 * @param subj 证书拥有者的主题信息,签发者和主题拥有者名称都转写X500Principle规范,格式:CN=country,ST=state,L=Locality,OU=OrganizationUnit,O=Organization * @param validDays 证书有效期天数 * @param sginAlgo 证书签名算法, e.g. SHA256withRSA * * @return security 新建立获得的X509证书 */ public static X509Certificate createUserCert(X509Certificate ca, PrivateKey caKey, PublicKey publicKey, String subj, long validDays, String sginAlgo) { //获取ca证书 X509Certificate caCert = ca; X509CertInfo x509CertInfo = new X509CertInfo(); try { //设置证书的版本号 x509CertInfo.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3)); //设置证书的序列号,基于当前时间计算 x509CertInfo.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber((int) (System.currentTimeMillis() / 1000L))); /** * 下面这个设置算法ID的代码,是错误的,会致使证书验证失败,可是报错不是很明确。 若将生成的证书存为keystore,让后keytool转换 * 会出现异常。 * * 重点: AlgorithmId的参数设置要和后面的证书签名中用到的算法信息一致。 * * AlgorithmId algorithmId = new AlgorithmId(AlgorithmId.SHA256_oid); */ AlgorithmId algorithmId = AlgorithmId.get(sginAlgo); x509CertInfo.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algorithmId)); //设置证书的签发者信息 X500Name issuer = new X500Name(caCert.getIssuerX500Principal().toString()); x509CertInfo.set(X509CertInfo.ISSUER, issuer); //设置证书的拥有者信息 X500Name subject = new X500Name(subj); x509CertInfo.set(X509CertInfo.SUBJECT, subject); //设置证书的公钥 x509CertInfo.set(X509CertInfo.KEY, new CertificateX509Key(publicKey)); //设置证书有效期 Date beginDate = new Date(); Date endDate = new Date(beginDate.getTime() + validDays * 24 * 60 * 60 * 1000L); CertificateValidity cv = new CertificateValidity(beginDate, endDate); x509CertInfo.set(X509CertInfo.VALIDITY, cv); CertificateExtensions exts = new CertificateExtensions(); exts.set(SubjectKeyIdentifierExtension.NAME, new SubjectKeyIdentifierExtension((new KeyIdentifier(publicKey)).getIdentifier())); exts.set(AuthorityKeyIdentifierExtension.NAME, new AuthorityKeyIdentifierExtension(new KeyIdentifier(ca.getPublicKey()), null, null)); exts.set(BasicConstraintsExtension.NAME, new BasicConstraintsExtension(false,false,-1)); x509CertInfo.set(CertificateExtensions.NAME, exts); } catch (CertificateException cee) { cee.printStackTrace(); } catch (IOException eio) { eio.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } // 获取CA私钥 PrivateKey caPrivateKey = caKey; //用CA的私钥给当前证书进行签名,获取最终的下游证书(证书链的下一节点) X509CertImpl cert = new X509CertImpl(x509CertInfo); try { cert.sign(caPrivateKey, sginAlgo); } catch (InvalidKeyException | CertificateException | NoSuchAlgorithmException | NoSuchProviderException | SignatureException e3) { e3.printStackTrace(); } return cert; }
4. demo程序
1) 从根证书开始建立,包含自签名证书,设备证书以及服务节点证书工具
private static void demoGenFull(String basePath, String devName, String emqHost) throws InvalidKeySpecException, NoSuchAlgorithmException { String subjCA = "CN=IOTPlatform,OU=TanKang,O=TKCloud,L=Wuhan,S=Hubei,C=CN"; String rootCACrtPath = basePath + "sccCA0.crt"; String rootCAKeyPath = basePath + "sccCA0.key"; String subjDev = "OU=TanKang,O=TKCloud,L=Wuhan,ST=Hubei,C=CN,CN=IOTDevice" + devName; String devCrtPath = basePath + "sccDev" + devName + ".crt"; String devKeyPath = basePath + "sccDev" + devName + ".key"; String subjEmq = "OU=TanKang,O=TKCloud,L=Wuhan,ST=Hubei,C=CN,CN=" + emqHost; String emqCommName = emqHost.replace(".", "-"); String emqCrtPath = basePath + "sccEmq" + emqCommName + ".crt"; String emqKeyPath = basePath + "sccEmq" + emqCommName + ".key"; /** * 建立根证书,即自签名证书 */ HashMap<String, Object> rootCA = MySSL.createRootCA("RSA",2048, MSG_DIGEST_SIGN_ALGO, subjCA, 3650); X509Certificate caCrt = (X509Certificate) rootCA.get(CERTIFICATE); PrivateKey caKey = (PrivateKey)rootCA.get(PRIVATE_KEY); MySSL.exportCrt(caCrt, rootCACrtPath); MySSL.exportKey(caKey, rootCAKeyPath); /** * 建立公钥和私钥对,而后基于自签名证书签发设备证书,即客户端证书 */ Map<String, String> keyDev = MySSL.createKeys(2048, RSA_ALGORITHM); PublicKey devPubKey = MySSL.getPublicKey(keyDev.get(PUBLIC_KEY)); PrivateKey devPriKey = MySSL.getPrivateKey(keyDev.get(PRIVATE_KEY)); X509Certificate devCrt = MySSL.createUserCert(caCrt, caKey, devPubKey, subjDev, 3650, MSG_DIGEST_SIGN_ALGO); MySSL.exportCrt(devCrt, devCrtPath); MySSL.exportKey(devPriKey, devKeyPath); /** * 建立公钥和私钥对,而后基于自签名证书签发EMQ证书,即服务端证书。 */ Map<String, String> keyEmq = MySSL.createKeys(2048, RSA_ALGORITHM); PublicKey emqPubKey = MySSL.getPublicKey(keyEmq.get(PUBLIC_KEY)); PrivateKey emqPriKey = MySSL.getPrivateKey(keyEmq.get(PRIVATE_KEY)); X509Certificate emqCrt = MySSL.createUserCert(caCrt, caKey, emqPubKey, subjEmq, 3650, MSG_DIGEST_SIGN_ALGO); MySSL.exportCrt(emqCrt, emqCrtPath); MySSL.exportKey(emqPriKey, emqKeyPath); }
2) 根证书已经建立了,经过加载根证书的方式,签发设备证书以及服务端节点证书post
private static void demoGenUserCertWithExistedCA(String basePath, String devName, String emqHost) throws InvalidKeySpecException, NoSuchAlgorithmException { String rootCACrtPath = basePath + "sccCA0.crt"; String rootCAKeyPath = basePath + "sccCA0.key"; String subjDev = "OU=TanKang,O=TKCloud,L=Wuhan,ST=Hubei,C=CN,CN=IOTDevice" + devName; String devCrtPath = basePath + "sccDev" + devName + ".crt"; String devKeyPath = basePath + "sccDev" + devName + ".key"; String subjEmq = "OU=TanKang,O=TKCloud,L=Wuhan,ST=Hubei,C=CN,CN=" + emqHost; String emqCommName = emqHost.replace(".", "-"); String emqCrtPath = basePath + "sccEmq" + emqCommName + ".crt"; String emqKeyPath = basePath + "sccEmq" + emqCommName + ".key"; /** * 从指定的文件加载构建根证书以及对应的私钥 */ X509Certificate caCrt = MySSL.getCertficate(new File(rootCACrtPath)); PrivateKey caKey = MySSL.getPrivateKey(new File(rootCAKeyPath)); /** * 建立公钥和私钥对,而后基于自签名证书签发设备证书,即客户端证书 */ Map<String, String> keyDev = MySSL.createKeys(2048, RSA_ALGORITHM); PublicKey devPubKey = MySSL.getPublicKey(keyDev.get(PUBLIC_KEY)); PrivateKey devPriKey = MySSL.getPrivateKey(keyDev.get(PRIVATE_KEY)); X509Certificate devCrt = MySSL.createUserCert(caCrt, caKey, devPubKey, subjDev, 3650, MSG_DIGEST_SIGN_ALGO); MySSL.exportCrt(devCrt, devCrtPath); MySSL.exportKey(devPriKey, devKeyPath); /** * 建立公钥和私钥对,而后基于自签名证书签发EMQ证书,即服务端证书。 */ Map<String, String> keyEmq = MySSL.createKeys(2048, RSA_ALGORITHM); PublicKey emqPubKey = MySSL.getPublicKey(keyEmq.get(PUBLIC_KEY)); PrivateKey emqPriKey = MySSL.getPrivateKey(keyEmq.get(PRIVATE_KEY)); X509Certificate emqCrt = MySSL.createUserCert(caCrt, caKey, emqPubKey, subjEmq, 3650, MSG_DIGEST_SIGN_ALGO); MySSL.exportCrt(emqCrt, emqCrtPath); MySSL.exportKey(emqPriKey, emqKeyPath); }
其中,经过文件重构私钥的函数getPrivateKey(File file)的函数以下:编码
/** * 利用开源的工具类BC解析私钥,例如openssl私钥文件格式为pem,须要去除页眉页脚后才能被java读取 * * @param file 私钥文件 * @return 私钥对象 */ public static PrivateKey getPrivateKey(File file) { if (file == null) { return null; } PrivateKey privKey = null; PemReader pemReader = null; try { pemReader = new PemReader(new FileReader(file)); PemObject pemObject = pemReader.readPemObject(); byte[] pemContent = pemObject.getContent(); //支持从PKCS#1或PKCS#8 格式的私钥文件中提取私钥, PKCS#1的私钥,主要是openssl默认生成的编码格式 if (pemObject.getType().endsWith("RSA PRIVATE KEY")) { /* * 取得私钥 for PKCS#1 * openssl genrsa 默认生成的私钥就是PKCS1的编码 */ org.bouncycastle.asn1.pkcs.RSAPrivateKey asn1PrivateKey = org.bouncycastle.asn1.pkcs.RSAPrivateKey.getInstance(pemContent); RSAPrivateKeySpec rsaPrivateKeySpec = new RSAPrivateKeySpec(asn1PrivateKey.getModulus(), asn1PrivateKey.getPrivateExponent()); KeyFactory keyFactory= KeyFactory.getInstance(RSA_ALGORITHM); privKey= keyFactory.generatePrivate(rsaPrivateKeySpec); } else if (pemObject.getType().endsWith("PRIVATE KEY")) { /* * java建立的私钥,默认是PKCS#8格式 * * 经过openssl pkcs8 -topk8转换为pkcs8,例如(-nocrypt不作额外加密操做): * openssl pkcs8 -topk8 -in pri.key -out pri8.key -nocrypt * * 取得私钥 for PKCS#8 */ PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(pemContent); KeyFactory kf = KeyFactory.getInstance(RSA_ALGORITHM); privKey = kf.generatePrivate(privKeySpec); } } catch (FileNotFoundException e) { logger.error("read private key fail,the reason is the file not exist"); e.printStackTrace(); } catch (IOException e) { logger.error("read private key fail,the reason is :"+e.getMessage()); e.printStackTrace(); } catch (NoSuchAlgorithmException e) { logger.error("read private key fail,the reason is :"+e.getMessage()); e.printStackTrace(); } catch (InvalidKeySpecException e) { logger.error("read private key fail,the reason is :"+e.getMessage()); e.printStackTrace(); } finally { try { if (pemReader != null) { pemReader.close(); } } catch (IOException e) { logger.error(e.getMessage()); } } return privKey; }
这个补充的博文,不作过多解释,全部的代码,都浅显易懂,建立的证书等文件,经过openssl工具,当作PEM格式的文件进行查看或者其余操做,都是能够的。如有什么不清楚,请关注个人博客,给我留言,一块儿探讨!