openssl创建私钥,获取公钥,创建证书都是比较简单的,就几个指令,很快就可以搞定,之所以说简单,是因为证书里面的基本参数配置不需要我们组装,只需要将命令行里面需要的几个参数配置进去即可。但是呢,用java代码,原生创建证书,其实需要我们了解的内容就要稍微多点,去填充创建证书里面的所需要的参数,逐行填充。

openssl证书的格式默认是PEM的,即Privacy Enhanced Mail,说白了,就是将创建后的证书元素数据经过Base64编码,然后添加类似----BEGIN CERTIFICATE----、----END CERTIFICATE----这样的头和尾,是ASCII编码的纯字符串格式。

现在完整的介绍一下java创建PEM证书的逻辑。都遵循X509的协议,创建证书,和openssl逻辑有点不同的是,java创建证书不需要构建CSR(Certificate Sign Request)这个步骤,同样需要创建私钥/公钥,创建CA证书,构建设备证书。

本博文是对前叙的博文的一个补充 MQTT研究之EMQ:【JAVA代码构建X509证书】,内容涉及的话题相同,但是角度有些不一样。

1. 私钥/公钥创建

/**
* 创建私钥和公钥的数据,以一个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自签名证书

/**
* 创建根证书, 并保存根证书到指定路径的文件中, 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, -);
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 * * * * 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,-));
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",, MSG_DIGEST_SIGN_ALGO, subjCA, );
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(, 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, , MSG_DIGEST_SIGN_ALGO);
MySSL.exportCrt(devCrt, devCrtPath);
MySSL.exportKey(devPriKey, devKeyPath); /**
* 创建公钥和私钥对,然后基于自签名证书签发EMQ证书,即服务端证书。
*/
Map<String, String> keyEmq = MySSL.createKeys(, 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, , MSG_DIGEST_SIGN_ALGO);
MySSL.exportCrt(emqCrt, emqCrtPath);
MySSL.exportKey(emqPriKey, emqKeyPath);
}

2) 根证书已经创建了,通过加载根证书的方式,签发设备证书以及服务端节点证书

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(, 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, , MSG_DIGEST_SIGN_ALGO);
MySSL.exportCrt(devCrt, devCrtPath);
MySSL.exportKey(devPriKey, devKeyPath); /**
* 创建公钥和私钥对,然后基于自签名证书签发EMQ证书,即服务端证书。
*/
Map<String, String> keyEmq = MySSL.createKeys(, 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, , 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格式的文件进行查看或者其他操作,都是可以的。若有什么不清楚,请关注我的博客,给我留言,一起探讨!

MQTT研究之EMQ:【JAVA代码构建X509证书【续集】】的更多相关文章

  1. MQTT研究之EMQ:【JAVA代码构建X509证书】

    这篇帖子,不会过多解释X509证书的基础理论知识,也不会介绍太多SSL/TLS的基本信息,重点介绍如何用java实现SSL协议需要的X509规范的证书. 之前的博文,介绍过用openssl创建证书,并 ...

  2. MQTT研究之EMQ:【SSL证书链验证】

    1. 创建证书链(shell脚本) 客户端证书链关系: rootCA-->chainca1-->chainca2-->chainca3 ca caCert1 caCert2 caCe ...

  3. MQTT研究之EMQ:【CoAP协议应用开发】

    本博文的重点是尝试CoAP协议的应用开发,其中包含CoAP协议中一个重要的开源工具libcoap的安装和遇到的问题调研.当然,为了很好的将EMQ的CoAP协议网关用起来,也调研了下EMQ体系下,CoA ...

  4. MQTT研究之EMQ:【EMQ之HTTP认证/访问控制】

    今天进行验证的逻辑是EMQ的http的Auth以及ACL的逻辑. 首先,参照HTTP插件认证配置的说明文档进行基本的配置, 我的配置内容如下: ##-------------------------- ...

  5. MQTT研究之EMQ:【CoAP协议的ECC证书研究】

    今天研究的内容,是CoAP这个协议在EMQ消息队列的支持,CoAP是一个受限资源的协议,基于UDP实现的多用于物联网环境的通信协议.相关介绍不多说,可以看RFC. CoAP协议下,基于DTLS通信,同 ...

  6. MQTT研究之EMQ:【wireshark抓包分析】

    基于上篇博文[SSL双向验证]的环境基础,进行消息的具体梳理. 环境基础信息: . 单台Linux CentOS7.2系统,安装一个EMQTTD的实例broker. . emq的版本2.3.11. . ...

  7. MQTT研究之EMQ:【eclipse的paho之java客户端使用注意事项】

    这里,简单记录一下自己在最近项目中遇到的paho的心得,这里也涵盖EMQX的问题. 1. cleanSession 这个标识,是确保client和server之间是否持久化状态的一个标志,不管是cli ...

  8. MQTT研究之EMQ:【基础研究】

    EMQ版本V2, emqttd-centos7-v2.3.11-1.el7.centos.x86_64.rpm 下载地址:http://emqtt.com/downloads/2318/centos7 ...

  9. MQTT研究之EMQ:【SSL双向验证】

    EMQ是当前MQTT中,用于物联网领域中比较出色的一个broker,今天我这里要记录和分享的是关于SSL安全通信的配置和注意细节. 环境: 1. 单台Linux CentOS7.2系统,安装一个EMQ ...

随机推荐

  1. Linux----Github环境搭建

    前面介绍了在Windows环境上安转GitHub环境,原本以为打包成jar,发布到Linux不需要再安转Git,但是因为我们使用了Config-Server配置中心,远程配置来启动,所以需要在Linu ...

  2. eclipse安装反编译插件(附jad下载)

    eclipse安装反编译插件(附jad下载) 博客分类: eclipse   一.eclipse反编译插件Jadclipse jadclips插件网站:    http://jadclipse.sou ...

  3. clickhouse在Linux上的安装部署

    $ sudo apt-get install clustershell #输入你的管理员密码 $ cd /etc/clustershell $ sudo gedit groups #在文件中添加如下内 ...

  4. redis 脑裂等极端情况分析

    脑裂真的是一个很头疼的问题(ps: 脑袋都裂开了,能不疼吗?),看下面的图: 一.哨兵(sentinel)模式下的脑裂 如上图,1个master与3个slave组成的哨兵模式(哨兵独立部署于其它机器) ...

  5. [ 转 ] RESTful

    一.什么是RESTful 定义: REST全程是Representational State Transfer,表述性状态转移.它首次出现在2000年Roy Fielding的博士论文中,Roy Fi ...

  6. Ionic 2 + 手动搭建开发环境教程 【转】

    ionic简介 为什么选用ionic: 彻底开源且免费 性能优异 基于红的发紫的AngularJs 漂亮的UI 强大的命令行(基于更热门的nodejs) 开发团队非常活跃. ngCordova,将主流 ...

  7. Windows 2008 asp.net 配置

    目录 1.     前言.. 32.     部署环境.. 32.1         服务器环境信息.. 33.     磁盘阵列配置.. 44.     安装操作系统.. 45.     安装软件. ...

  8. IDEA常用快捷键总结

    Intellij IDEA中有很多快捷键让人爱不释手,stackoverflow上也有一些有趣的讨论.每个人都有自己的最爱,想排出个理想的榜单还真是困难.以前也整理过Intellij的快捷键,这次就按 ...

  9. Win32线程——等待另一个线程结束

    转载: https://blog.csdn.net/yss28/article/details/53646627 <Win32多线程程序设计>–Jim Beveridge & Ro ...

  10. 学习笔记--python中使用多进程、多线程加速文本预处理

    一.任务描述 最近尝试自行构建skip-gram模型训练word2vec词向量表.其中有一步需要统计各词汇的出现频率,截取出现频率最高的10000个词汇进行保留,形成常用词词典.对于这个问题,我建立了 ...