概念

证书

证书是由CA(Certificate Authority,证书颁发机构)或自行签发的,用于确定身份安全性和进行数字签名等操作的文件。

证书的内容

X.509标准证书通常包含以下内容:

  • 版本号:标识证书的X.509版本(v1/v2/v3)
  • 序列号:颁发机构分配的唯一标识符
  • 签名算法:如SHA256withRSA、SHA256withECDSA等
  • 颁发机构(Issuer):签发该证书的CA信息
  • 证书有效时间:包含生效时间(Not Before)和过期时间(Not After)
  • 主体(Subject):证书持有者的信息(域名、组织、国家等)
  • 公钥:证书持有者的公钥信息
  • 扩展信息:如SAN(Subject Alternative Name)、密钥用途等(v3版本支持)

证书的作用

  • 身份验证:验证证书持有者的身份,确保通信对方的真实性
  • 数据加密:利用证书中的公钥对数据进行加密,保证传输安全
  • 签名验证:通过证书验证数据签名的真实性,确保数据完整性和不可否认性

证书的类型

  • 自签名证书:由自己签发,通常用于测试环境
  • CA签发证书:由权威CA机构签发,用于生产环境
    • DV证书(域名验证)
    • OV证书(组织验证)
    • EV证书(扩展验证)

私钥

私钥是非对称加密中的关键组成部分,必须严格保密,与公钥配对使用。

私钥的格式和编码

私钥转换成字符串主要取决于私钥的格式和编码方式:

  • 格式

    • PKCS#1:较老的格式,仅支持RSA算法,以-----BEGIN RSA PRIVATE KEY-----开头
    • PKCS#8:通用格式,支持多种算法,以-----BEGIN PRIVATE KEY-----开头
  • 编码

    • PEM:Base64编码的文本格式,包含头尾标记
    • DER:二进制编码格式

PEM vs DER

PEM格式

  • -----BEGIN PRIVATE KEY-----开头,以-----END PRIVATE KEY-----结尾
  • 适合文本传输和存储
  • Java中可以直接使用Bouncy Castle库的工具还原成私钥对象

DER格式

  • 不带BEGIN/END标记的纯二进制数据
  • Java中keyPair.getPrivateKey().getEncoded()默认生成的是PKCS#8格式的DER编码字节数组

PEM和DER的关系

1
2
3
-----BEGIN CERTIFICATE-----
<Base64 编码的 DER 数据>
-----END CERTIFICATE-----

PEM通过BEGIN和END标记包含DER数据的Base64编码。本质上PEM的内容就是DER数据,只是做了Base64编码以便于文本传输,两者可以相互转换。

Java中的证书和私钥处理

Java的CertificateFactory原生支持PEM格式的输入。当输入流中包含PEM头尾标记时,CertificateFactory会自动进行以下操作:

  • 移除-----BEGIN CERTIFICATE----------END CERTIFICATE-----头尾标记
  • 对Base64编码的数据进行解码
  • 将解码后的DER字节解析为X.509证书对象

代码示例:Java中读取PEM格式私钥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.io.FileReader;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.nio.file.Files;
import java.nio.file.Paths;

public class PemKeyReader {

/**
* 从PEM文件读取私钥
*/
public static PrivateKey readPrivateKeyFromPem(String pemFilePath) throws Exception {
// 读取PEM文件内容
String content = new String(Files.readAllBytes(Paths.get(pemFilePath)));

// 移除PEM头尾标记
String privateKeyPEM = content
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");

// Base64解码得到DER格式字节
byte[] encoded = Base64.getDecoder().decode(privateKeyPEM);

// 使用PKCS8EncodedKeySpec解析私钥
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");

return keyFactory.generatePrivate(keySpec);
}
}

代码示例:Java中读取PEM格式证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.FileInputStream;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

public class PemCertReader {

/**
* 从PEM文件读取证书(Java原生支持PEM格式)
*/
public static X509Certificate readCertificateFromPem(String pemFilePath) throws Exception {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");

try (FileInputStream fis = new FileInputStream(pemFilePath)) {
return (X509Certificate) certFactory.generateCertificate(fis);
}
}
}

公钥

公钥与私钥配对使用,可以公开分发,用于加密数据或验证签名。

公钥的格式

  • PKCS#1-----BEGIN RSA PUBLIC KEY-----
  • PKCS#8(X.509 SubjectPublicKeyInfo):-----BEGIN PUBLIC KEY-----

Java中获取公钥

1
2
3
4
5
6
7
// 从KeyPair获取
PublicKey publicKey = keyPair.getPublic();
byte[] encoded = publicKey.getEncoded(); // 默认返回X.509 SubjectPublicKeyInfo格式的DER编码

// 从证书获取
X509Certificate certificate = ...;
PublicKey publicKeyFromCert = certificate.getPublicKey();

证书链

在实际应用中,证书通常以链的形式存在:

1
2
3
4
5
终端证书(End Entity Certificate)
↓ 由
中间CA证书(Intermediate CA Certificate)
↓ 由
根CA证书(Root CA Certificate)签发

证书链验证流程

  1. 使用根CA证书的公钥验证中间CA证书的签名
  2. 使用中间CA证书的公钥验证终端证书的签名
  3. 验证证书的有效期、吊销状态等

Java中的证书链处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 创建证书链
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");

List<X509Certificate> certChain = new ArrayList<>();
certChain.add(endEntityCert); // 终端证书
certChain.add(intermediateCert); // 中间证书
certChain.add(rootCert); // 根证书

// 验证证书链
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
CertPath certPath = certFactory.generateCertPath(certChain);

PKIXParameters params = new PKIXParameters(trustAnchors);
params.setRevocationEnabled(true); // 启用吊销检查

validator.validate(certPath, params);

常见证书文件格式

扩展名 格式说明 编码方式
.pem Privacy Enhanced Mail,可包含证书或私钥 PEM(Base64)
.cer / .crt 证书文件 PEM或DER
.der Distinguished Encoding Rules DER(二进制)
.pfx / .p12 PKCS#12格式,可包含证书链和私钥 DER(二进制)
.jks Java KeyStore,Java专用密钥库 专有格式

PKCS#12 (PFX) 文件

PKCS#12格式可以同时存储证书链和私钥,常用于证书导入导出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 读取PKCS#12文件
KeyStore pkcs12Store = KeyStore.getInstance("PKCS12");
try (FileInputStream fis = new FileInputStream("certificate.p12")) {
pkcs12Store.load(fis, "password".toCharArray());
}

// 导出为PKCS#12
KeyStore exportStore = KeyStore.getInstance("PKCS12");
exportStore.load(null, null);
exportStore.setKeyEntry("alias", privateKey, "password".toCharArray(), certChain);

try (FileOutputStream fos = new FileOutputStream("export.p12")) {
exportStore.store(fos, "password".toCharArray());
}

常用工具命令

OpenSSL常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 生成RSA私钥(PKCS#1格式)
openssl genrsa -out private.key 2048

# 转换为PKCS#8格式
openssl pkcs8 -topk8 -inform PEM -in private.key -outform PEM -nocrypt -out private_pkcs8.key

# 从私钥提取公钥
openssl rsa -in private.key -pubout -out public.key

# 生成自签名证书
openssl req -x509 -new -nodes -key private.key -sha256 -days 365 -out cert.pem

# 查看证书信息
openssl x509 -in cert.pem -text -noout

# PEM转DER
openssl x509 -in cert.pem -outform DER -out cert.der

# DER转PEM
openssl x509 -in cert.der -inform DER -out cert.pem -outform PEM

Java keytool常用命令

1
2
3
4
5
6
7
8
9
10
11
# 生成密钥对和JKS密钥库
keytool -genkeypair -alias mykey -keyalg RSA -keysize 2048 -keystore keystore.jks

# 导出证书
keytool -exportcert -alias mykey -keystore keystore.jks -file cert.cer

# 导入证书到信任库
keytool -importcert -alias ca -file ca.crt -keystore truststore.jks

# 查看密钥库内容
keytool -list -v -keystore keystore.jks

安全建议

  1. 私钥保护:私钥必须严格保密,建议加密存储
  2. 证书有效期:定期更新证书,避免使用过期证书
  3. 证书吊销检查:验证证书时检查CRL或OCSP
  4. 密钥长度:RSA建议使用2048位以上,ECDSA建议使用256位以上
  5. 签名算法:避免使用MD5、SHA1等不安全算法,推荐使用SHA256或更高