# OpenSSL PKCS#12（PFX）命令与 3.0 的 -legacy 变化

> 用 OpenSSL 3.x 生成、查看、拆分、修复 PFX/PKCS#12 文件：3.0 起默认 AES-256、给老 Windows 和 Java 用 -legacy、常见报错的处理方法。

Source: https://ssl.sh/zh/cookbook/pkcs12/
Last updated: 2026-11-10

[PKCS#12](https://ssl.sh/zh/glossary/pkcs12/)（`.pfx`、`.p12`）把私钥、证书和证书链装进一个带密码的文件，`openssl pkcs12` 负责生成和读取。实际使用中最大的坑来自 OpenSSL 3.0：新导出的文件改用 AES-256 和 SHA-256 MAC，老版本 Windows、macOS、Java 可能不认；而老工具导出的文件用 RC2，3.x 不加 `-legacy` 就读不了。输出示例来自 OpenSSL 3.6.1。

单纯的格式转换（PFX 转 PEM、PFX 转 JKS）见[证书格式转换](https://ssl.sh/zh/cookbook/convert-formats/)，也可以用浏览器里的 [SSL 格式转换工具](https://ssl.sh/zh/tools/ssl-converter/)。

## 生成包含私钥、证书和证书链的 PFX

`-inkey` 是私钥，`-in` 是服务器证书，`-certfile` 是中间证书，`-name` 是导入时显示的友好名称。

```bash
openssl pkcs12 -export -inkey server.key -in server.crt -certfile intermediate.crt \
  -name "example.com" -out example.pfx
```

```text
Enter Export Password:
Verifying - Enter Export Password:
```

友好名称在 Java 密钥库里就是别名，在 Windows 证书管理器里显示为“友好名称”。私钥和证书不配对时会报 `No cert in -in file 'server.crt' matches private key`。也可以直接把 `fullchain.pem` 交给 `-in`，OpenSSL 会挑出和私钥匹配的那张当服务器证书。

## 让 OpenSSL 自动补全证书链

`-chain` 会从服务器证书一路构建到受信任的根证书，并把路径上的证书都放进去。

```bash
openssl pkcs12 -export -inkey server.key -in server.crt -chain \
  -untrusted intermediate.crt -CAfile root.crt -name example.com -out example.pfx
openssl pkcs12 -in example.pfx -nokeys -passin pass:changeit | grep subject=
```

```text
subject=CN=example.com
subject=CN=Example Test Intermediate CA
subject=CN=Example Test Root CA
```

找不到可用的根证书时会报 `Error getting chain: unable to get local issuer certificate`。公网证书不写 `-CAfile` 时会用系统信任库。注意 `-chain` 会把根证书也放进去，服务器其实不需要发送根证书。

## 查看 PFX 用的是什么算法

`-info -noout` 只打印 MAC 和加密算法，不输出任何密钥。

```bash
openssl pkcs12 -in example.pfx -info -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
```

这是 OpenSSL 3.x 的默认格式。旧格式会显示 `MAC: sha1` 以及 `pbeWithSHA1And40BitRC2-CBC` 或 `pbeWithSHA1And3-KeyTripleDES-CBC`。导入失败时先跑这条，就知道该改哪一边。

## 弄清 OpenSSL 3.0 的默认算法变化

OpenSSL 3.0 把 PKCS#12 导出从老的 PKCS#12 PBE 算法换成了 PBES2 + AES-256。

| | OpenSSL 1.1.1 及更早 | OpenSSL 3.0 及以后（默认） | 3.x 加 `-legacy` |
|---|---|---|---|
| 证书加密 | RC2-40-CBC | AES-256-CBC，PBKDF2，HMAC-SHA256 | RC2-40-CBC（未编译 RC2 时为 3DES） |
| 私钥加密 | 3DES | AES-256-CBC，PBKDF2，HMAC-SHA256 | 3DES |
| MAC | SHA-1 | SHA-256 | SHA-1 |
| 迭代次数 | 2048 | 2048 | 2048 |

3.0 把 RC2 等老算法移进了默认不加载的 legacy provider。所以同一条用了多年的命令，升级系统（比如 Ubuntu 22.04 起自带 OpenSSL 3）后导出的文件突然在老服务器上导入失败；反过来，3.x 不加 `-legacy` 也打不开老工具导出的文件。

## 给老 Windows、macOS、Java 生成兼容的 PFX

加 `-legacy` 就会使用 3.0 之前的算法。

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

```text
MAC: sha1, Iteration 2048
MAC length: 20, salt length: 16
PKCS7 Encrypted data: pbeWithSHA1And40BitRC2-CBC, Iteration 2048
Certificate bag
Certificate bag
PKCS7 Data
Shrouded Keybag: pbeWithSHA1And3-KeyTripleDES-CBC, Iteration 2048
```

Windows Server 2016 及更早版本的 IIS、旧版 macOS 钥匙串、较老的 Java 8、部分防火墙和负载均衡设备，在密码明明正确却提示“密码错误”时，就用这个办法。OpenSSL 不是装在系统默认路径时，`-legacy` 可能还需要 `-provider-path` 或环境变量 `OPENSSL_MODULES` 才能找到 `legacy.so`。

## 显式指定加密算法

`-certpbe`、`-keypbe`、`-macalg` 可以直接选算法，不需要加载 legacy provider。

```bash
openssl pkcs12 -export -inkey server.key -in server.crt -certfile intermediate.crt \
  -certpbe PBE-SHA1-3DES -keypbe PBE-SHA1-3DES -macalg sha1 \
  -name example.com -out example-compat.pfx
openssl pkcs12 -in example-compat.pfx -info -noout
```

```text
MAC: sha1, Iteration 2048
MAC length: 20, salt length: 16
PKCS7 Encrypted data: pbeWithSHA1And3-KeyTripleDES-CBC, Iteration 2048
Certificate bag
Certificate bag
PKCS7 Data
Shrouded Keybag: pbeWithSHA1And3-KeyTripleDES-CBC, Iteration 2048
```

3DES 仍在默认 provider 里，所以这个文件 OpenSSL 3.x 不加 `-legacy` 就能读，老系统基本也都能导入。不知道对方用什么系统时，这是最稳妥的折中。加 `-iter 100000` 可以提高迭代次数，增加暴力猜密码的成本。

## 在 OpenSSL 3.x 上读取旧格式 PFX

报 `unsupported` 并提到 `RC2-40-CBC`，说明文件用了旧算法，加 `-legacy`。

```bash
openssl pkcs12 -in old.pfx -info -noout
openssl pkcs12 -in old.pfx -info -noout -legacy
```

```text
MAC: sha1, Iteration 2048
MAC length: 20, salt length: 16
PKCS7 Encrypted data: pbeWithSHA1And40BitRC2-CBC, Iteration 2048
Error outputting keys and certificates
...:error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:...:Algorithm (RC2-40-CBC : 0), Properties ()

MAC: sha1, Iteration 2048
...
Shrouded Keybag: pbeWithSHA1And3-KeyTripleDES-CBC, Iteration 2048
```

Node.js 17 以上、Python 等链接 OpenSSL 3 的程序读老 PFX 时也会报同样的 `0308010C ... unsupported`。一劳永逸的办法是读入时加 `-legacy`、导出时用默认 AES 重新生成一次，做法见下文“修改 PFX 密码”。

## 处理 “Mac verify error: invalid password?”

这表示 MAC 校验没通过，绝大多数情况是密码错了，偶尔是 MAC 算法不被支持。

```bash
openssl pkcs12 -in example.pfx -noout -passin pass:wrong
```

```text
Mac verify error: invalid password?
```

程序库里同样的情况会报 `mac verify failure`。逐项排查：密码是否为空（`-passin pass:`）、密码文件末尾是否多了换行、从云厂商密码文件复制时是否带了空格、密码里有没有中文等非 ASCII 字符。确认密码无误而文件来自很新的系统时，可能是旧程序不认识新的 MAC 算法（例如 OpenSSL 3.4 起可用 `-pbmac1_pbkdf2` 生成的 PBMAC1）。

## 提取私钥、证书和 CA 证书

`-nocerts`、`-clcerts -nokeys`、`-cacerts -nokeys` 分别取出三部分。

```bash
openssl pkcs12 -in example.pfx -nocerts -noenc -out example.com.key
openssl pkcs12 -in example.pfx -clcerts -nokeys -out example.com.crt
openssl pkcs12 -in example.pfx -cacerts -nokeys -out chain.crt
head -3 example.com.key
```

```text
Bag Attributes
    localKeyID: 12 21 0D 64 22 E9 E3 EB CC 6B 65 BE B6 A8 CA 17 5F 63 39 3A 
    friendlyName: example.com
```

`-clcerts` 只输出和私钥匹配的那张证书，避免在乱序文件里拿错。不加 `-noenc` 会让你给私钥再设一个 PEM 密码。旧格式文件每条命令都加 `-legacy`。从 Windows 导出的 PFX 转给 Nginx 或宝塔面板用，就是这三步。

## 生成只含证书的 PKCS#12 信任库

`-nokeys` 可以把 CA 证书打包成 Java 等客户端用的信任库。

```bash
openssl pkcs12 -export -nokeys -in root.crt -jdktrust anyExtendedKeyUsage \
  -out truststore.p12
openssl pkcs12 -in truststore.p12 -nokeys | head -2
```

```text
Bag Attributes
    Trusted key usage (Oracle): Any Extended Key Usage (2.5.29.37.0)
```

Java 只把带有 Oracle“受信任密钥用途”属性的证书当作信任条目，`-jdktrust`（OpenSSL 3.2 起）负责加这个属性。更老的 OpenSSL 请改用 `keytool -importcert` 生成信任库。

## 生成无密码 PFX

`-passout pass:` 只是用空密码加密；`-keypbe NONE -certpbe NONE -nomac` 才是完全不加密。

```bash
openssl pkcs12 -export -inkey server.key -in server.crt -passout pass: -out nopass.pfx
openssl pkcs12 -export -inkey server.key -in server.crt \
  -keypbe NONE -certpbe NONE -nomac -passout pass: -out plain.pfx
openssl pkcs12 -in plain.pfx -info -noout -passin pass:
```

```text
Warning: MAC is absent!
PKCS7 Data
Certificate bag
PKCS7 Data
Key bag
```

部分软件和容器平台要求无密码 PFX。完全不加密的版本等于换了个外壳的明文私钥，要像 `.key` 文件一样保管。启用 FIPS provider 时也会用 `-nomac`，因为 FIPS 模式算不了 PKCS#12 的 MAC。

## 修改 PFX 密码

先导出成临时 PEM，再用新密码重新导出，最后删掉临时文件。

```bash
umask 077
openssl pkcs12 -in example.pfx -noenc -passin file:old.txt -out tmp-all.pem
openssl pkcs12 -export -in tmp-all.pem -name example.com -passout file:new.txt -out example-new.pfx
rm -f tmp-all.pem
```

```text
（成功时没有输出）
```

不能用管道把第一条直接接到第二条：`pkcs12 -export` 要把输入读两遍，而标准输入只能读一次，会报 `Could not find certificates from -in file from <stdin>`。第一条加 `-legacy`，就能顺便把老的 RC2 文件升级成 AES。

## 在脚本里免交互运行

用 `file:`、`env:` 或 `fd:` 传密码，不用手动输入。

```bash
P12PASS='s3cret' openssl pkcs12 -in example.pfx -noout -passin env:P12PASS && echo ok
openssl pkcs12 -export -inkey server.key -in server.crt -passout file:pfx-pass.txt -out example.pfx
```

```text
ok
```

`pass:密码` 也能用，但会出现在 `ps` 输出和 shell 历史里。`-passin` 是读取 PFX（或 `-export` 时读取输入私钥）的密码，`-passout` 是新写出文件的密码。其他 OpenSSL 报错的含义见[常见问题排查](https://ssl.sh/zh/cookbook/troubleshooting/)。
