MQTT研究之EMQ:【JAVA代码构建X509证书【续集】】
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证书【续集】】的更多相关文章
- MQTT研究之EMQ:【JAVA代码构建X509证书】
这篇帖子,不会过多解释X509证书的基础理论知识,也不会介绍太多SSL/TLS的基本信息,重点介绍如何用java实现SSL协议需要的X509规范的证书. 之前的博文,介绍过用openssl创建证书,并 ...
- MQTT研究之EMQ:【SSL证书链验证】
1. 创建证书链(shell脚本) 客户端证书链关系: rootCA-->chainca1-->chainca2-->chainca3 ca caCert1 caCert2 caCe ...
- MQTT研究之EMQ:【CoAP协议应用开发】
本博文的重点是尝试CoAP协议的应用开发,其中包含CoAP协议中一个重要的开源工具libcoap的安装和遇到的问题调研.当然,为了很好的将EMQ的CoAP协议网关用起来,也调研了下EMQ体系下,CoA ...
- MQTT研究之EMQ:【EMQ之HTTP认证/访问控制】
今天进行验证的逻辑是EMQ的http的Auth以及ACL的逻辑. 首先,参照HTTP插件认证配置的说明文档进行基本的配置, 我的配置内容如下: ##-------------------------- ...
- MQTT研究之EMQ:【CoAP协议的ECC证书研究】
今天研究的内容,是CoAP这个协议在EMQ消息队列的支持,CoAP是一个受限资源的协议,基于UDP实现的多用于物联网环境的通信协议.相关介绍不多说,可以看RFC. CoAP协议下,基于DTLS通信,同 ...
- MQTT研究之EMQ:【wireshark抓包分析】
基于上篇博文[SSL双向验证]的环境基础,进行消息的具体梳理. 环境基础信息: . 单台Linux CentOS7.2系统,安装一个EMQTTD的实例broker. . emq的版本2.3.11. . ...
- MQTT研究之EMQ:【eclipse的paho之java客户端使用注意事项】
这里,简单记录一下自己在最近项目中遇到的paho的心得,这里也涵盖EMQX的问题. 1. cleanSession 这个标识,是确保client和server之间是否持久化状态的一个标志,不管是cli ...
- MQTT研究之EMQ:【基础研究】
EMQ版本V2, emqttd-centos7-v2.3.11-1.el7.centos.x86_64.rpm 下载地址:http://emqtt.com/downloads/2318/centos7 ...
- MQTT研究之EMQ:【SSL双向验证】
EMQ是当前MQTT中,用于物联网领域中比较出色的一个broker,今天我这里要记录和分享的是关于SSL安全通信的配置和注意细节. 环境: 1. 单台Linux CentOS7.2系统,安装一个EMQ ...
随机推荐
- Promise学习使用
Promise是承诺的意思,“承诺可以获取异步操作的消息”,是一种异步执行的方案,Promise有各种开源实现,在ES6中被统一规范,由浏览器直接支持. Promise 对象有三种状态:pending ...
- Mysql和Hadoop+Hive有什么关系?
1.Hive不存储数据,Hive需要分析计算的数据,以及计算结果后的数据实际存储在分布式系统上,如HDFS上. 2.Hive某种程度来说也不进行数据计算,只是个解释器,只是将用户需要对数据处理的逻辑, ...
- WScript与CScript的区别
WSH有两种形式:一为WScript是一个窗口化的版本:一为CScript是一个命令行的版本.两种版本都可以运行任何脚本.二者之间的区别是,窗口化版本(WScript)使用一个弹出对话框来显示文本输出 ...
- mssql server 数据库帮助类
using System; using System.Collections.Generic; using System.Text; using System.IO; using System.Web ...
- python3 操作配置文件
一 json文件 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.它基于ECMAScript的一个子集. JSON采用完全独立于语言的文本格式,但是也使用 ...
- java+jenkins+testng+selenium+ant
1.安装jdk7以上2.http://mirrors.jenkins-ci.org/windows/latest 下载最新的war包3.cmd命令在war包目录下执行:java -jar jenkin ...
- Git从入门到差不多会用
工作以后最先接触到的新东西可能就包括版本控制工具了,对Git的感觉是又敬又畏,敬是因为最初的时候都是跟着同事照猫画虎地通过开发软件图形化操作,大家都不太懂,也不知道这东西有多深奥:畏就是因为有过几次惨 ...
- Python 递归锁
import time from threading import Thread, Lock, RLock def f1(locA, locB): # print('xxxx') # time.sle ...
- mysql数据库的备份
有时会遇到需要重装虚拟机的情况,这时候之前创建好的数据库就需要备份啦,等重新装好虚拟机直接导入就可以正常使用. 数据库备份大体分为两步: 第一步.导出数据库,因为是备份,会将所有的数据库导出,因此需要 ...
- Java中String类常用方法(字符串中的子字符串的个数)
重点内容 4种方法: 1.int indexOf(String str)返回第一次出现的指定子字符串在此字符串中的索引. 2.int indexOf(String str, int startInde ...