1. X.509证书简介
X.509是密码学里公钥证书的格式标准。X.509证书己应用在包括TLS/SSL(WWW万维网安全浏览的基石)在内的众多Internet协议里。同时它也用在很多非在线应用场景里,比如电子签名服务。X.509证书里含有公钥、身份信息(比如网络主机名,组织的名称或个体名称等)和签名信息(可以是证书签发机构CA的签名,也可以是自签名)。对于一份经由可信的证书签发机构签名或者可以通过其它方式验证的证书,证书的拥有者就可以用证书及相应的私钥来创建安全的通信,对文档进行数字签名。-百度百科
所有的X.509证书包含以下数据:
①X.509版本号:指出该证书使用了哪种版本的X.509标准,版本号会影响证书中的一些特定信息。目前的版本是3。
②证书持有人的公钥:包括证书持有人的公钥、算法(指明密钥属于哪种密码系统)的标识符和其他相关的密钥参数。
③证书的序列号:由CA给予每一个证书分配的唯一的数字型编号,当证书被取消时,实际上是将此证书序列号放入由CA签发的CRL(Certificate Revocation List证书作废表,或证书黑名单表)中。这也是序列号唯一的原因。
④主题信息:证书持有人唯一的标识符(或称DN-distinguished name)这个名字在 Internet上应该是唯一的。DN由许多部分组成,看起来象这样:
CN=Bob Allen
OU=Total Network Security Division
O=Network Associates, Inc.
C=US
这些信息指出该科目的通用名、组织单位、组织和国家或者证书持有人的姓名、服务处所等信息。
⑤证书的有效期:证书起始日期和时间以及终止日期和时间;指明证书在这两个时间内有效。
⑥认证机构:证书发布者,是签发该证书的实体唯一的CA的X.509名字。使用该证书意味着信任签发证书的实体。(注意:在某些情况下,比如根或顶级CA证书,发布者自己签发证书)
⑦发布者的数字签名:这是使用发布者私钥生成的签名,以确保这个证书在发放之后没有被撰改过。
⑧签名算法标识符:用来指定CA签署证书时所使用的签名算法。算法标识符用来指定CA签发证书时所使用的公开密钥算法和HASH算法。
证书主题属性
属性类型名称 | 含义 | 简写 |
---|
Common Name | 通用名称 | CN | Organizational Unit name | 机构单元名称 | OU | Organization name | 机构名 | O | Locality | 地理位置 | L | State or province name | 州/省名 | S | Country | 国名 | C |
此外还有一些有效期,颁发者,证书颁发对象,用途等。详情参考
2.java操作X.509证书
java对x509有自己的一套实现可以选择sun公司自己的实现类,但是有些复杂的操作sun自带的做不了,所以我们一般使用bouncycastle这个java开源加密包。
需要注意的是:jdk自带的aes加密只支持到128位,更高的256位的加密,需要到oracle官网下载jce包,替换java自带的加密包。一般对称和非对称加密都是混合使用的,这也是为了在安全性和效率上取得平衡,这也是业内目前普遍采用的方法。
需要引入的包:
<dependencies>
<!--bouncycastle加密包-->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.64</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.64</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcmail-jdk15</artifactId>
<version>1.46</version>
</dependency>
<!--StringUtils等都在这个包下-->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
X509接口
import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Date;
public interface X509Dao {
/**
* 制作证书
* @param issuer 发布者
* @param notBefore 使用日期
* @param notAfter 到期
* @param certDestPath 生成证书地址
* @param serial 证书序列号
* @param keyPassword 证书密码
* @param alias 证书别名
* @throws Exception
*/
void createCert(String issuer, Date notBefore, Date notAfter, String certDestPath, BigInteger serial,
String keyPassword, String alias) throws Exception;
/**
* 输出证书信息
* @param certPath 证书地址
* @param keyPassword 证书密码
* @throws Exception
*/
void printCert(String certPath, String keyPassword) throws Exception;
/**
* 返回公钥
* @param certPath
* @param keyPassword
* @return
* @throws Exception
*/
PublicKey getPublicKey(String certPath, String keyPassword) throws Exception;
/**
* 返回私钥
* @param certPath
* @param keyPassword
* @return
* @throws Exception
*/
PrivateKey getPrivateKey(String certPath, String keyPassword) throws Exception;
/**
* 证书延期(未实现)
* @param endTime
* @param certPath
* @param keyPassword
* @throws Exception
*/
void certDelayTo(Date endTime, String certPath, String keyPassword) throws Exception;
/**
* 修改密码
* @param certPath
* @param oldPwd
* @param newPwd
* @throws Exception
*/
void changePassword(String certPath, String oldPwd, String newPwd) throws Exception;
/**
* 删除证书
* @param certPath
* @param keyPassword
* @param alias
* @param entry
* @throws Exception
*/
void deleteAlias(String certPath, String keyPassword, String alias, String entry) throws Exception;
}
X509实现类
package testx509;
import org.apache.commons.lang.time.DateUtils;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
public class X509CertDaoImpl implements X509Dao{
public static final String Default_keyType = "PKCS12";//存储证书keyStore类型
public static final String Default_KeyPairGenerator = "RSA";//非对称加密算法
public static final String Default_Signature = "SHA1withRSA";//签名算法:RSA签名,用MD5哈希算法摘要
public static final String cert_type = "X509";//证书类型
public static final Integer Default_KeySize = 2048;
static {
//系统添加BC加密算法,以后系统中调用的算法都是BC的算法
Security.addProvider(new BouncyCastleProvider());
}
@Override
public void createCert(String issuer, Date notBefore, Date notAfter, String certDestPath, BigInteger serial, String keyPassword, String alias) throws Exception {
//产生公私钥对
KeyPairGenerator kpg = KeyPairGenerator.getInstance(Default_KeyPairGenerator);
kpg.initialize(Default_KeySize);
KeyPair keyPair = kpg.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
//组装证书
X500Name issueDn = new X500Name(issuer);
X500Name subjectDn = new X500Name(issuer);
//组装公钥信息
SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(new ASN1InputStream(publicKey.getEncoded()).readObject());
X509v3CertificateBuilder builder = new X509v3CertificateBuilder(issueDn, serial, notBefore, notAfter, subjectDn, subjectPublicKeyInfo);
//证书签名数据
ContentSigner signGen = new JcaContentSignerBuilder(Default_Signature).build(privateKey);
X509CertificateHolder holder = builder.build(signGen);
byte[] certBuf = holder.getEncoded();
X509Certificate certificate = (X509Certificate) CertificateFactory.getInstance(cert_type).generateCertificate(new ByteArrayInputStream(certBuf));
//创建KeyStore,存储证书
KeyStore store = KeyStore.getInstance(Default_keyType);
store.load(null, null);
store.setKeyEntry(alias, keyPair.getPrivate(), keyPassword.toCharArray(), new Certificate[]{certificate});
FileOutputStream fout = new FileOutputStream(certDestPath);
store.store(fout, keyPassword.toCharArray());
fout.close();
}
@Override
public void printCert(String certPath, String keyPassword) throws Exception {
//char[] charArray = keyPassword.toCharArray();
//KeyStore ks = KeyStore.getInstance(Default_keyType);
//FileInputStream fis = new FileInputStream(certPath);
//ks.load(fis, charArray);
//fis.close();
//System.out.println("keystore type=" + ks.getType());
//Enumeration enumas = ks.aliases();
//String keyAlias = null;
//if (enumas.hasMoreElements()) {
// keyAlias = (String) enumas.nextElement();
// System.out.println("alias=[" + keyAlias + "]");
//}
//System.out.println("is key entry="+ks.isKeyEntry(keyAlias));
//PrivateKey prikey = (PrivateKey) ks.getKey(keyAlias, charArray);
//Certificate cert = ks.getCertificate(keyAlias);
//PublicKey pubkey = cert.getPublicKey();
//System.out.println("cert class = "+ cert.getClass().getName());
//System.out.println("cert= "+cert);
//System.out.println("public key = "+pubkey);
//System.out.println("private key = "+prikey);
char[] charArray = keyPassword.toCharArray();
KeyStore ks = KeyStore.getInstance(Default_keyType);
FileInputStream fis = new FileInputStream(certPath);
ks.load(fis, charArray);
fis.close();
System.out.println("keystore type=" + ks.getType());
Enumeration enumas = ks.aliases();
String keyAlias = null;
while (enumas.hasMoreElements()) {
keyAlias = (String) enumas.nextElement();
System.out.println("alias=[" + keyAlias + "]");
System.out.println("is key entry="+ks.isKeyEntry(keyAlias));
PrivateKey prikey = (PrivateKey) ks.getKey(keyAlias, charArray);
Certificate cert = ks.getCertificate(keyAlias);
PublicKey pubkey = cert.getPublicKey();
System.out.println("cert class = "+ cert.getClass().getName());
System.out.println("cert= "+cert);
System.out.println("public key = "+pubkey);
System.out.println("private key = "+prikey);
}
}
@Override
public PublicKey getPublicKey(String certPath, String keyPassword) throws Exception {
char[] charArray = keyPassword.toCharArray();
KeyStore ks = KeyStore.getInstance(Default_keyType);
FileInputStream fis = new FileInputStream(certPath);
ks.load(fis, charArray);
fis.close();
Enumeration enumas = ks.aliases();
String keyAlias = null;
if (enumas.hasMoreElements()) {
keyAlias = (String) enumas.nextElement();
return ks.getCertificate(keyAlias).getPublicKey();
}
return null;
}
@Override
public PrivateKey getPrivateKey(String certPath, String keyPassword) throws Exception {
char[] charArray = keyPassword.toCharArray();
KeyStore ks = KeyStore.getInstance(Default_keyType);
FileInputStream fis = new FileInputStream(certPath);
ks.load(fis, charArray);
fis.close();
Enumeration enumas = ks.aliases();
String keyAlias = null;
if (enumas.hasMoreElements()) {
keyAlias = (String) enumas.nextElement();
return (PrivateKey) ks.getKey(keyAlias, charArray);
}
return null;
}
@Override
public void certDelayTo(Date endTime, String certPath, String keyPassword) throws Exception {
}
@Override
public void changePassword(String certPath, String oldPwd, String newPwd) throws Exception {
KeyStore ks = KeyStore.getInstance(Default_keyType);
FileInputStream fis = new FileInputStream(certPath);
ks.load(fis, oldPwd.toCharArray());
fis.close();
FileOutputStream fout = new FileOutputStream(certPath);
ks.store(fout,newPwd.toCharArray());
fout.close();
}
@Override
public void deleteAlias(String certPath, String keyPassword, String alias, String entry) throws Exception {
char[] charArray = keyPassword.toCharArray();
KeyStore ks = KeyStore.getInstance(Default_keyType);
FileInputStream fis = new FileInputStream(certPath);
ks.load(fis, charArray);
fis.close();
if (ks.containsAlias(alias)){
ks.deleteEntry(entry);
FileOutputStream fout = new FileOutputStream(certPath);
ks.store(fout,keyPassword.toCharArray());
fout.close();
}else {
throw new Exception("该证书未包含别名------->"+alias);
}
}
public static void main(String[] args) throws Exception {
//1.创建证书
//X509CertDaoImpl impl = new X509CertDaoImpl();
//String issuer="C=CN,ST=BJ,L=Beijing,O=testserver,OU=testserver,CN=testserver";
//String certDestPath="E:\\iotest\\test2.p12";
//BigInteger serial=BigInteger.valueOf(System.currentTimeMillis());
//String keyPassword="123456";
//String alias="test2";
//
//Calendar calendar = Calendar.getInstance();
//calendar.add(Calendar.YEAR,5);
//
//impl.createCert(issuer,new Date(),calendar.getTime(),certDestPath,serial,keyPassword,alias);
//System.out.println("创建证书成功");
//2.遍历打印证书信息
X509CertDaoImpl impl = new X509CertDaoImpl();
impl.printCert("E:\\iotest\\cert\\test.p12","123456");
//3.DateUtils的使用
//Date date = DateUtils.parseDate("2021-11-12 09:50:00", new String[]{"yyyy-MM-dd HH:mm:ss"});
//System.out.println(date.toLocaleString());
//4.枚举的遍历问题
//char[] charArray = "123456".toCharArray();
//KeyStore ks = KeyStore.getInstance(Default_keyType);
//FileInputStream fis = new FileInputStream("E:\\iotest\\cert\\test.p12");
//ks.load(fis, charArray);
//fis.close();
//System.out.println("keystore type=" + ks.getType());
//Enumeration enumas = ks.aliases();
//ArrayList<String> aliasList = new ArrayList<>();
//String [] keyAlias = null;
//while (enumas.hasMoreElements()) {
// String alias = (String) enumas.nextElement();
// aliasList.add(alias);
//}
//System.out.println(aliasList);
}
}
这是java相关的证书操作(证书延期没有做),关于java如何使用x509实现加密解密,签名验签等操作,可以阅读这篇文章。
3.证书文件扩展名
X.509有多种常用的扩展名。不过其中的一些还用于其它用途,就是说具有这个扩展名的文件可能并不是证书,比如说可能只是保存了私钥。
.pem:(隐私增强型电子邮件)DER编码的证书再进行Base64编码的数据存放在"-----BEGIN CERTIFICATE-----"和"-----END CERTIFICATE-----"之中。
.cer、.crt、.der:用于存放证书,通常是二进制格式,但Base64编码后也很常见,不含私钥。
.p7b、.p7c:PKCS#7SignedData structure without data, just certificate(s) orCRL(s)
.p12:PKCS#12格式,包含证书的同时可能还有带密码保护的私钥。
.pfx:PFX,PKCS#12之前的格式(通常用PKCS#12格式,比如那些由IIS产生的PFX文件)。
PKCS#7是签名或加密数据的格式标准,官方称之为容器。由于证书是可验真的签名数据,所以可以用SignedData结构表述。.P7C文件是退化的SignedData结构,没有包括签名的数据。
PKCS#12由PFX进化而来的用于交换公共的和私有的对象的标准格式,可以包含公钥证书及其私钥,也可包含整个证书链。
4.证书管理工具keytool
Java自带的keytool工具是个密钥和证书管理工具。它使用户能够管理自己的公钥/私钥对及相关证书,用于(通过数字签名)自我认证(用户向别的用户/服务认证自己)或数据完整性以及认证服务。它还允许用户储存他们的通信对等者的公钥(以证书形式)。
keytool将密钥和证书储存在一个所谓的密钥仓库(keystore)中。缺省的密钥仓库实现将密钥仓库实现为一个文件。它用口令来保护私钥。
Java KeyStore的类型
JKS和JCEKS是Java密钥库(KeyStore)的两种比较常见类型(还有JKS、JCEKS、PKCS12、BKS、UBER)。
JKS的Provider是SUN,在每个版本的JDK中都有,JCEKS的Provider是SUNJCE,1.4后我们都能够直接使用它。
JCEKS在安全级别上要比JKS强,使用的Provider是JCEKS(推荐),尤其在保护KeyStore中的私钥上(使用TripleDes)。
PKCS#12是公钥加密标准,它规定了可包含所有私钥、公钥和证书。其以二进制格式存储,也称为PFX文件,在windows中可以直接导入到密钥区,注意,PKCS#12的密钥库保护密码同时也用于保护Key。
BKS来自BouncyCastle Provider,它使用的也是TripleDES来保护密钥库中的Key,它能够防止证书库被不小心修改(Keystore的keyentry改掉1个bit都会产生错误),BKS能够跟JKS互操作。
UBER比较特别,当密码是通过命令行提供的时候,它只能跟keytool交互。整个keystore是通过PBE/SHA1/Twofish加密,因此keystore能够防止被误改、察看以及校验。以前,Sun JDK(提供者为SUN)允许你在不提供密码的情况下直接加载一个Keystore,类似cacerts,UBER不允许这种情况。
从.p12中提出为.cer证书
使用上述的java代码,生成了test2.p12,alias为test2。使用keytool操作命令如下:
keytool -export -alias test2 -keystore test2.p12 -storetype PKCS12 -rfc -file test2.cer
将.cer证书放入.p12中
使用上述的java代码,生成了test.p12,alias为test。将上面的test2.cer放入到test.p12中:
keytool -import -alias test2 -trustcacerts -file test2.cer -keystore test.p12
这样在test.p12中就存在了两个别名的证书test和test2。
?参考文献:
原文链接:https://blog.csdn.net/do_bset_yourself/article/details/78159697
https://www.cnblogs.com/testlife007/p/6888191.html
|