# 证书格式详解：PEM、DER、PFX、P7B、JKS 的区别

> PEM、DER、PKCS#7、PKCS#12、JKS 各能装什么、怎么识别来路不明的证书文件、Nginx/IIS/Tomcat/宝塔分别要哪种格式。

Source: https://ssl.sh/zh/learn/certificate-formats/
Last updated: 2026-11-10

证书文件在两个维度上互不相同：编码方式（二进制 DER 还是 Base64 文本 PEM），以及容器（单张证书、只装证书的 PKCS#7 包、带私钥并用密码保护的 PKCS#12 包，或者 Java 密钥库）。Nginx、Apache 和大多数 Linux 软件要 PEM；Windows 和 IIS 要 PKCS#12（.pfx）；Java 能用 PKCS#12，老系统还在用 JKS。扩展名只是参考，转换之前先看清文件内容。

## 编码和容器是两回事

证书本身永远是 RFC 5280 定义的同一个 X.509 结构，各种格式只是改变它的书写方式和随行内容。实际工作中会碰到下面五种：

| 格式 | 编码 | 能装什么 | 含私钥？ | 常见扩展名 | 规范 |
|---|---|---|---|---|---|
| DER | 二进制 ASN.1 | 一张证书、一把私钥或一个 CRL | 只能单独成文件 | .der、.cer、.crt | ITU-T X.690、RFC 5280 |
| PEM | 带首尾标记行的 Base64 文本 | 任意多张证书、私钥、CSR、CRL | 可以，单独一段 | .pem、.crt、.cer、.key | RFC 7468 |
| PKCS#7 | DER 或 PEM | 证书和 CRL | 不能 | .p7b、.p7c | RFC 2315 |
| PKCS#12 | 二进制 | 私钥 + 证书 + 证书链，用密码加密 | 能 | .pfx、.p12 | RFC 7292 |
| JKS | 二进制，Java 专有 | 密钥库里的私钥和证书 | 能 | .jks、.keystore | Java 文档 |

用分层的方式看更清楚：

```text
X.509 证书（ASN.1 结构，RFC 5280）
 |
 +-- 写成二进制 DER ................................ .der / .cer
      |
      +-- Base64 + "-----BEGIN CERTIFICATE-----" ..... PEM（.pem / .crt）
      +-- 几张证书装进一个信封 ...................... PKCS#7（.p7b）
      +-- 证书 + 私钥，用密码保护 .................... PKCS#12（.pfx / .p12）
```

## PEM：大多数服务器用的文本格式

PEM 就是把 DER 做 Base64 编码、每 64 个字符换行，再夹在标明内容的 `BEGIN` 行和 `END` 行之间。RFC 7468 把沿用了几十年的标签正式标准化：

| 标签 | 内容 |
|---|---|
| `CERTIFICATE` | X.509 证书 |
| `PRIVATE KEY` | 未加密的 PKCS#8 私钥（任何算法） |
| `ENCRYPTED PRIVATE KEY` | 带密码的 PKCS#8 私钥 |
| `RSA PRIVATE KEY` / `EC PRIVATE KEY` | 较老的按算法区分的私钥格式（PKCS#1、SEC 1） |
| `CERTIFICATE REQUEST` | [CSR 证书签名请求](https://ssl.sh/zh/glossary/csr/) |
| `X509 CRL` | 证书吊销列表 |
| `PKCS7` | PEM 形式的 PKCS#7 包 |

从 OpenSSL 3.0 起，`openssl genrsa` 默认输出 `-----BEGIN PRIVATE KEY-----`（PKCS#8），不再是老的 `RSA PRIVATE KEY`，Nginx 和 Apache 两种都认。

PEM 是文本，所以可以直接拼接。`fullchain.pem` 就是网站证书后面接上中间证书；HAProxy 则要求把证书、中间证书和私钥放进同一个文件。PEM 常见的问题有：Windows 换行符、编辑器自动加的 BOM、复制粘贴时两段之间丢了换行、`-----END` 行被截断。在 Windows 记事本里编辑过再上传到 Linux 的证书尤其容易出这些问题。块外面的多余文字，比如 `openssl pkcs12` 导出时写的 `Bag Attributes`，基于 OpenSSL 的服务器会直接忽略。

## DER：二进制形式

DER 是 ASN.1 结构的标准二进制编码。DER 证书几乎总是以字节 `30 82` 开头，表示一个长度占两字节的 ASN.1 SEQUENCE。Windows 导出证书时同时提供“DER 编码二进制 X.509 (.CER)”和“Base64 编码 X.509 (.CER)”两种选项，所以两个都叫 `.cer` 的文件可能是不同格式。Java 的 `keytool -exportcert` 默认也输出 DER，加 `-rfc` 才是 PEM。

```bash
# DER 转 PEM，再转回来
openssl x509 -inform der -in certificate.cer -out certificate.pem
openssl x509 -in certificate.pem -outform der -out certificate.der
```

## PKCS#7（.p7b）：只有证书，没有私钥

PKCS#7 包把证书（也可以带 CRL）装进一个不带签名、不带私钥的 signed-data 信封。一些 CA 用这种格式交付中间证书链，Windows 和 Java 可以一步导入。因为 .p7b 里永远没有私钥，单靠它无法配置服务器。

```bash
# 把 P7B 里的所有证书导出为 PEM
openssl pkcs7 -in chain.p7b -print_certs -out chain.pem
# 用 PEM 证书链生成 P7B
openssl crl2pkcs7 -nocrl -certfile fullchain.pem -out chain.p7b
```

## PKCS#12（.pfx/.p12）：私钥和证书链装进一个加密文件

PKCS#12 用来把私钥连同证书和证书链一起搬运。它把每一项放进一个“袋子”（bag），用由密码派生的密钥加密私钥（通常也加密证书），再用 MAC 保护整个文件。Windows、IIS、Azure 以及许多 Java 和 macOS 工具都能直接导入。PFX 这个名字来自微软更早的 Personal Information Exchange 格式，PKCS#12 正是在它的基础上发展来的。

### OpenSSL 3.0 改了默认算法

OpenSSL 3.0 改变了 `openssl pkcs12 -export` 保护新文件的方式。老版本用 RC2-40 或 3DES 加 SHA-1，OpenSSL 3.0 把这些算法挪进了 legacy 提供程序。在 OpenSSL 3.x 下默认结果如下：

```bash
openssl pkcs12 -export -inkey example.com.key -in example.com.crt \
  -certfile intermediate.pem -out example.com.pfx
openssl pkcs12 -info -in example.com.pfx -noout
```

```text
MAC: sha256, Iteration 2048
MAC length: 32, salt length: 16
PKCS7 Encrypted data: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256
Certificate bag
Certificate bag
PKCS7 Data
Shrouded Keybag: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256
```

这会在两个方向上引出问题：

- **老程序读不了新文件。** 较老的 Windows Server、较早的 Java 8 更新版本和老版本 macOS 钥匙串，可能拒绝 AES-256 的 PKCS#12 文件，而且往往报一个误导人的“密码错误”。用 `-legacy` 生成即可：私钥用 3DES、证书用 RC2、MAC 用 SHA-1。
- **OpenSSL 3 读不了老文件。** 打开多年前由 Windows 或 OpenSSL 1.x 导出的 PFX 时，会报 `unsupported` 并提到 `RC2-40-CBC`。在命令里加 `-legacy` 加载 legacy 提供程序就行。

```text
Error outputting keys and certificates
...:error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:...
Algorithm (RC2-40-CBC : 0), Properties ()
```

另外，`-nodes` 从 OpenSSL 3.0 起已废弃，“导出的私钥不加密”的新写法是 `-noenc`。两个方向的现成命令见 [PKCS#12 命令手册](https://ssl.sh/zh/cookbook/pkcs12/)。

## JKS 与 Java 密钥库

JKS 是 Java 专有的密钥库格式，完整性保护较弱，现代 Java 默认已经不再生成它。从 JDK 9（JEP 229）起，默认密钥库类型是 PKCS12，也就是说用 OpenSSL 生成的 `.p12` 文件可以直接当 Java 密钥库用。转换已有的 JKS：

```bash
keytool -importkeystore -srckeystore server.jks -srcstoretype jks \
  -destkeystore server.p12 -deststoretype pkcs12
keytool -list -v -keystore server.p12
```

每次打开 JKS 文件，`keytool` 都会提示建议迁移到 PKCS12。只有老版本的 Servlet 容器和部分设备还要求 JKS，其他情况优先用 PKCS12。国内不少老项目（如老版本 Tomcat、部分国产中间件）配置里仍写着 `.jks`，迁移前先确认软件版本是否支持 PKCS12。

## 私钥的格式

私钥也有自己的格式，私钥格式不对是服务器起不来的常见原因。日常会见到三种 PEM：

| 首行 | 结构 | 说明 |
|---|---|---|
| `BEGIN PRIVATE KEY` | PKCS#8，未加密 | OpenSSL 3.x 对所有密钥类型的默认输出 |
| `BEGIN ENCRYPTED PRIVATE KEY` | PKCS#8，带密码 | 服务器启动时需要密码 |
| `BEGIN RSA PRIVATE KEY` / `BEGIN EC PRIVATE KEY` | PKCS#1 / SEC 1 | 老版本 OpenSSL 的输出，仍被广泛支持 |

`openssl pkey` 可以在它们之间转换，密钥本身不变：

```bash
# 任意支持的私钥转成未加密的 PKCS#8
openssl pkey -in old.key -out new.key
# 查看算法和长度
openssl pkey -in new.key -noout -text | head -1
```

Nginx 只有通过 `ssl_password_file` 提供密码才能读取加密私钥，所以大多数部署让私钥保持不加密，靠文件权限保护（`chmod 600`、属主 root）。PKCS#12 文件也一样：密码只在传输途中起保护作用，拿到文件和密码的人就拿到了私钥。密码和文件要通过不同渠道发送，也不要把私钥或 PFX 上传到会在服务器端处理的网站。

## 如何识别来路不明的文件

先看开头的字节，再让 OpenSSL 按你猜测的格式解析，别信扩展名。

| 看到的内容 | 格式 | 验证命令 |
|---|---|---|
| `-----BEGIN CERTIFICATE-----` | PEM 证书 | `openssl x509 -in f -noout -text` |
| `-----BEGIN PKCS7-----` | PEM 形式的 PKCS#7 | `openssl pkcs7 -in f -print_certs` |
| `-----BEGIN PRIVATE KEY-----` | PEM PKCS#8 私钥 | `openssl pkey -in f -noout -text` |
| `-----BEGIN CERTIFICATE REQUEST-----` | PEM CSR | `openssl req -in f -noout -text` |
| 二进制，以 `30 82` 开头 | DER 证书、PKCS#12 或 DER 形式的 PKCS#7 | 依次试 `x509 -inform der`、`pkcs12 -info`、`pkcs7 -inform der` |

Linux 和 macOS 的 `file` 命令能认出其中几种：

```bash
file leaf.der site.pfx chain.p7b leaf.pem
```

```text
leaf.der:  Certificate, Version=3
site.pfx:  data
chain.p7b: ASCII text
leaf.pem:  PEM certificate
```

PKCS#12 文件只会显示为 `data`，所以二进制文件如果不是 DER 证书，下一步就试 `openssl pkcs12 -info -in 文件 -noout`。公开证书也可以直接粘贴到 [证书解码工具](https://ssl.sh/zh/tools/certificate-decoder/) 里自动识别。

## 格式之间的转换

大多数转换只要一条 OpenSSL 命令，私钥不必离开你的电脑。速查表：

| 从 | 到 | 命令 |
|---|---|---|
| DER | PEM | `openssl x509 -inform der -in cert.der -out cert.pem` |
| PEM | DER | `openssl x509 -in cert.pem -outform der -out cert.der` |
| P7B | PEM | `openssl pkcs7 -in chain.p7b -print_certs -out chain.pem` |
| PEM | P7B | `openssl crl2pkcs7 -nocrl -certfile fullchain.pem -out chain.p7b` |
| PEM + 私钥 | PFX | `openssl pkcs12 -export -inkey key.pem -in fullchain.pem -out site.pfx` |
| PFX | PEM | `openssl pkcs12 -in site.pfx -out all.pem -noenc` |
| JKS | PKCS12 | `keytool -importkeystore -srckeystore a.jks -destkeystore a.p12 -deststoretype pkcs12` |

带输出示例和边界情况的版本见 [格式转换命令手册](https://ssl.sh/zh/cookbook/convert-formats/)，常见转换也可以用浏览器里的 [证书格式转换工具](https://ssl.sh/zh/tools/ssl-converter/)。转换之后，用 [私钥匹配检查](https://ssl.sh/zh/tools/key-matcher/) 确认私钥和证书仍然配对；不配对时 Nginx 报的就是 [key values mismatch](https://ssl.sh/zh/errors/nginx-key-values-mismatch/)。

## 各种服务器要什么格式

按加载证书的软件来选格式：

| 平台 | 证书 | 私钥 | 证书链 |
|---|---|---|---|
| Nginx | PEM（`ssl_certificate`） | PEM（`ssl_certificate_key`） | 放在证书文件里，紧跟网站证书 |
| Apache 2.4.8+ | PEM（`SSLCertificateFile`） | PEM（`SSLCertificateKeyFile`） | 放在证书文件里；`SSLCertificateChainFile` 已废弃 |
| HAProxy | 一个 PEM 文件 | 同一文件 | 同一文件 |
| IIS / Windows | 导入 PKCS#12（.pfx） | 在 PFX 里 | 在 PFX 里 |
| Tomcat | PKCS#12 或 JKS 密钥库；较新版本也能直接用 PEM | 在密钥库或 PEM 里 | 在密钥库或 PEM 里 |
| Node.js | PEM 字符串（`cert`、`key`）或 PFX（`pfx`） | PEM 或 PFX | 追加在 `cert` 里 |
| 宝塔面板 | 粘贴 PEM（“证书(PEM格式)”框） | 粘贴 PEM（“密钥(KEY)”框） | 粘在证书框里，紧跟网站证书 |
| 云负载均衡、CDN | 一般分别粘贴 PEM 证书、证书链和私钥 | PEM | 单独字段或附在证书后 |

阿里云、腾讯云等平台的证书下载页会按服务器类型给出不同的压缩包：Nginx 包是 PEM 证书（含证书链）加私钥文件，Apache 包把证书和证书链分成两个文件，IIS 包是 .pfx 加一个存密码的文本文件，Tomcat 包是 .pfx 或 .jks 加密码文件。它们是同一张证书装在不同的容器里。不论下载哪一种，证书链都必须完整，原因见 [证书链详解](https://ssl.sh/zh/learn/certificate-chains/)。
