2020_1课程设计—基于BC的证书格式转换工具的设计与实现—Week2
2020_1课程设计—基于BC的证书格式转换工具的设计与实现—Week2
任务要求
- 清楚.pem .pfx /.keystore .crt .cer .der 这些格式的文件用openssl如何产生
- 使用OpenSSL命令行查看证书,并实现证书格式转换
- 清楚哪些格式可以互相转换,并使用BouncyCastle编程实现
Week2 任务安排
- 收集相关资料,学习BouncyCastle的使用方法
- 使用BouncyCastle编程实现证书格式的转换
实践过程
我好像不知道什么是BouncyCastle
- 学java的时候知道,java标准库提供了一系列常用的哈希算法
- 但是呢,如果我们要用的某种算法,Java标准库没有怎么办捏
- 方法一:自己写一个。。。这也太为难我
- 方法二:找一个现成的第三方库,直接使用就好啦
- Bouncy Castle 就是一个这样神奇的提供了很多哈希算法和加密算法的第三方库,提供了Java标准库没有的一些算法,例如RipeMD160哈希算法
- 使用BouncyCastle的目的就是为了扩充算法支持
- ouncy Castle除了提供Provider本身以外,还包括了一个S/MIME和一个open pgp的jar,包只有Provider本身是必要的,后两个包是方便编程而提供的。
- 例如有了S/MIME包后,就不再需要为诸如"加密一个字符串或者一篇文章然后签名"之类的很现实的应用程序写上一大堆代码了,只要引用S/MIME包中的某几个类,很快就可以搞定。
- 而open pgp的存在,使你在用Java编写和PGP/GPG交互的程序时方便多了.
那怎么使用BouncyCastle嘞
- 下载最新加密组件包:The Legion of the Bouncy Castle
- 下载下来的加密组件包有以下两个:
bcprov-ext-jdk15to18-165.jar
#用于配置方式使用bcprov-jdk15to18-165.jar
#用于调用方式使用
配置方式
通过配置JRE环境,使其作为提供者提供相应的算法支持,在代码层面只需要指定要扩展的算法名称
1、 修改D:\IDEA\Java\jdk\jre\lib\security\java.security
文件
security.provider.11=org.bouncycastle.jce.provider.BouncyCastleProvider
2、 将文件导入D:\IDEA\Java\jdk\jre\lib\ext
目录下
3、 刚才上面修改了安装目录下的jdk文件夹里的内容,接下来对与jdk同目录下的jre文件夹进行相同的修改与导入
调用方式
1、 必须BouncyCastle提供的jar包bcprov-jdk15to18-165.jar
放到classpath中
2、 Java标准库的java.security包提供了一种标准机制,允许第三方提供商无缝接入。我们要使用BouncyCastle提供的RipeMD160算法,需要先把BouncyCastle注册一下:
2、 在需要使用加密的代码中导入以下两个类
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
3、 在初始化密钥工厂、密钥生成器等引擎前调用如下代码:
//加入BouncyCastleProvider的支持
Security.addProvider(new BouncyCastleProviderrr());
###############或者使用以下方式###############
MessageDigest md = MessageDigest.getInstant("MD4","BC");
//每个提供者都有简称,Bouncy Castle提供者的简称为BC
据说使用BC Provider建CA无敌宇宙巨简单
1、 在所有的此类程序的开头,都都必须有:Security.addProvider(new BouncyCastleProvider())
;
这是为啥捏?将BouncyCaslte的Provider添加到系统中,这样以后系统在运行相关程序的时候调用的就是这个Provider中的加密算法。
2、 建立CA首先要有自己的一对公钥和私钥叭
- 我们使用
KeyPairGenerator
对象就可以噻 - 调用
KeyPairGenerator.getInstance
方法可以根据要生成的密钥类型来产生一个合适的实例,例如常用的RSA等 - 然后调用该对象的
initialize
方法和generateKeyPair
方法就可以产生一个KeyPair
对象 - 然后调用
KeyPair
对象中的相应方法,就可以获取生成的密钥对中的公钥和私钥啦
3、 怎么安全地存储公私钥嘞?
- 通常我们要对这些安全对象,如公钥、私钥、证书等先进行编码
- 编码的目的是为了把结构复杂的安全对象变成字节流以便存储和读取,如DER编码
- 另外,通常还把DER编码后的字节流再次进行base64编码,以便使字节流中所有的字节都变成可打印的字节
- 在Java语言中,这些安全对象基本上都用
getEncoded()
方法,将私钥进行DER编码后保存到一个byte数组中
//公私钥的存储
byte[] priBytes = privateCrtKey.getEncoded();
- 还可以使用BC Provider中的Base64编码解码器类进行编码
//使用Base64再次进行编码
String priData = Base64.encode(priBytes);
-
那如何读取私钥嘞
- 对RSA私钥进行编码就会自动地按照PKCS8进行,因此读取的时候将包含编码的字节数组作为PKCS8EncodedKeySpec对象的构造函数的参数就可以生成一个该类型的对象
- 然后创建一个密钥工厂对象就可以按照要求生成一个RSA私钥
- 这里的DpriBytes应该是和上面的priBytes内容相同.
-
之后使用第一周生成的根证书签发就好啦(这里好像不是重点。。。)
回归到正题:证书转换!
PEM->PFX
前提摘要
- 第一周进行证书转换时,使用命令
openssl pkcs12 -nocerts -nodes -in test.pfx -out test.pem
进行证书转换时,并未导出私钥 - 而且PEM是使用Base64编码
- 但是PFX格式的证书中含有私钥,且使用二进制编码
- 因此需要调用产生证书时偷偷保存的私钥
具体思路
-
用Bouncy Castle转换证书的话,首先应该加载BC哦
Security.addProvider( new BouncyCastleProvider() );
-
既然要转换格式,那肯定得想方设法把PEM证书内容给读出来叭
- 到bouncycastle官网找一些资料,发现
- bouncycastle提供了PemReader/PemWriter可以读写证书
- 那就看看
PemReader
到底是个什么样的方法呗,机智的我找到了具体描述- 首先使用
PemReader
创建个对象 - 再使用其中
readPemObject
方法,读取证书就可以啦
- 首先使用
//使用BC提供的Pemreader读证书
PemReader pemReader = new PemReader( new FileReader( "E:\\认真的侯颖好好看(课内)\\大三下\\课设\\certificate\\test.pem" ) );
PemObject pemObject = pemReader.readPemObject();
byte cert[] = pemObject.getContent();
- PEM证书读出的内容可以直接写入PFX证书嘛,应该是不能
- 我觉得应该用X.509标准给他规范一下
- 果然,输出的就能看懂了
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");//获取工厂实例
Certificate certificate = certificateFactory.generateCertificate(new
ByteArrayInputStream(cert));//用文件流读入证书
X509Certificate x509Certificate = (X509Certificate)certificate;
System.out.println(x509Certificate);
- PFX证书里面还有私钥,但是PEM中没有,所以还要读取一波私钥
- 这个过程就和读取证书的过程很像啦
PemReader pemReader_key = new PemReader(new FileReader( "E:\\认真的侯颖好好看(课内)\\大三下\\课设\\certificate\\test_key.pem" ));
PemObject pemObject1 = pemReader_key.readPemObject();
byte[] keyByte = pemObject1.getContent();
- 私钥有了,但也不能干巴巴明明白白写到证书里吧,所以得处理一下
//处理私钥
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyByte);//使用给定的编码密钥创建新的 PKCS8EncodedKeySpec
KeyFactory keyFactory = KeyFactory.getInstance("RSA");//创建一个密钥工厂对象就可以按照要求生成一个RSA私钥
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
- 好像需要的东西都准备好啦,这就可以写进PFX证书了呗
-
PFX中有私钥,那肯定不能直接就让你读,所以一般有密码保护,转换时需要输入PFX文件的加密密码
char[] storePwd = "Hy981103".toCharArray();
-
好像还得解决一个证书链的问题
-
X509Certificate[] chain = new X509Certificate[1];//证书链的长度为1
chain[0]= x509Certificate;
-
变身第一步:将给定的证书分配给给定的别名
-
变身第二步:将给定的密钥(已经被保护)分配给给定的别名
-
变身第三步:写入文件
FileOutputStream out = new FileOutputStream( "E:\\认真的侯颖好好看(课内)\\大三下\\课设\\certificate\\PemToPfx.pfx" );
-
变身最后一步:将加密密码和证书绑定起来鸭
关键代码
PFX->PEM
前提摘要
- PFX是二进制编码
- PEM是Base64编码
- 所以只要将PFX编码一波就可以了呗
具体思路
- 读入PFX证书后,由于PFX还有密钥保护,所以得先解密一波,这时候查API,发现
engineLoad
可以从给定的输入流浃在密钥库。
//读PFX证书,用密钥解密证书
FileInputStream fs = new FileInputStream( "E:\\认真的侯颖好好看(课内)\\大三下\\课设\\certificate\\test.pfx");
char[] passwd = "Hy981103".toCharArray();
PKCS12KeyStore store = new PKCS12KeyStore();
store.engineLoad(fs,passwd);//从给定的输入流加载密钥库
fs.close();
- 发现
KeyStoreSpi
这个类还挺管用,那就看看有没有其他方法可以用-
诶,好想让我发现了一个可以返回与给定别名相关联的证书
-
那这个
alias
别名不就是我刚刚读出来的内容嘛
-
再用X509安排一下,这样就行二进制编码变成可读的信息啦
-
//生成可读证书内容
Enumeration enumas = store.engineAliases();
String keyAlias = null;
if (enumas.hasMoreElements())
{
keyAlias = (String)enumas.nextElement();
System.out.println("alias=[" + keyAlias + "]");
}
Certificate cert = store.engineGetCertificate(keyAlias);
X509Certificate certificate = (X509Certificate)cert;
System.out.println(certificate);
-
BC进行格式转换
- PEM是Base64编码,因此需要
certificate.getEncoded()
进行编码 - 使用BC中的PEMWriter写证书
- PEMWriter需要一个Writer来封装,因此定义了StringWriter,将证书内容写入字符串中
- PEM是Base64编码,因此需要
-
将上述构造的字符串写入文件就ok啦
FileOutputStream fos = new FileOutputStream("E:\\认真的侯颖好好看(课内)\\大三下\\课设\\certificate\\PfxToPem.pem");
fos.write(str.toString().getBytes());
关键代码
PEM->DER
前提摘要
- PEM是Base64编码
- DER是二进制编码
- 均不包含私钥
- 好像解决编码问题就可以了奥
具体思路
-
用Bouncy Castle转换证书的话,首先还是加载BC哦
Security.addProvider( new BouncyCastleProvider() );
-
也使用PemReader将证书内容读出来,这部分和上面一模一样嘿嘿嘿,我认为这一步已经编码过了,最后写入了一个字节数组(这一点还不是很清楚)
//使用BC提供的Pemreader读证书
PemReader pemReader = new PemReader( new FileReader( "E:\\认真的侯颖好好看(课内)\\大三下\\课设\\certificate\\test.pem" ) );
PemObject pemObject = pemReader.readPemObject();
byte cert[] = pemObject.getContent();
- 直接写入.der文件就可以啦
//转换DER证书
File file=new File("E:\\认真的侯颖好好看(课内)\\大三下\\课设\\certificate\\PemToDer.der");
if(!file.exists()){
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
fos.write(cert);
fos.close();
DER->PEM
前提摘要
- PEM是Base64编码
- DER是二进制编码
- 均不包含私钥
- 好像解决编码问题就可以了奥
具体思路
- 加载DER证书,读入字节数组中
//读DER证书
File file = new File("E:\\认真的侯颖好好看(课内)\\大三下\\课设\\certificate\\PemToDer.der");
long filesize = file.length();
FileInputStream fis = new FileInputStream(file);
byte[] cert = new byte[(int) filesize];
int offset = 0;
int numRead = 0;
while (offset < cert.length && (numRead = fis.read(cert, offset, cert.length - offset)) >= 0) {
offset += numRead;
}
fis.close();
- 用X509规范一波,输出的内容就可以看懂啦
//输出证书内容
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");//获取工厂实例
Certificate certificate = certificateFactory.generateCertificate(new ByteArrayInputStream(cert));//用文件流读入证书
X509Certificate x509Certificate = (X509Certificate)certificate;
System.out.println(x509Certificate);
- 剩下的步骤直接参考上面PFXToPem的过程就可呦
关键代码
遇到问题
- 这不写代码不知道,一动手就把以前学的java全还给老师了,好多地方都写得很冗余,完全没有达到“高内聚,低耦合”,而且很多地方写的都很“固定”,比如文件名直接嵌到代码里,不能动态输入等等。
- 而且自己本周的进度较慢,查资料理解代码花了大量的时间,也捡起了曾经用的java API,这个过程中大致搞懂各个方法直接的联系,收货非常大,基本是搞懂一两个转换证书的代码,之后的就基本一样了哈哈哈哈哈。
- 但是,其实很多东西都找不到资料,完全都是“摸着石头过河”,很多地方都不是很懂,也不知道理解的正不正确,希望老师可以指点一下嘿嘿嘿~
- 希望接下来一段时间,可以解决代码的冗余问题、更加灵活。