# OpenSSL 密码套件与 TLS 版本：查看、测试与枚举

> 用 OpenSSL 3.x 展开密码套件字符串、测试 TLS 1.0 到 1.3、逐个枚举服务器支持的套件，并检查 X25519MLKEM768 后量子密钥交换。

Source: https://ssl.sh/zh/cookbook/ciphers-and-protocols/
Last updated: 2026-11-10

`openssl ciphers` 能查出某个套件字符串在你这版 OpenSSL 上代表哪些套件，`openssl s_client` 能测出服务器实际接受哪些协议版本、密码套件和密钥交换组。命令按 OpenSSL 3.x 写（输出取自 3.6.1），需要 3.4 或 3.5 的命令单独标注。名称背后的含义见[密码套件原理](https://ssl.sh/zh/learn/cipher-suites/)，查单个套件可以用[密码套件查询](https://ssl.sh/zh/tools/cipher-suite-lookup/)。

## 把套件字符串展开成具体套件

`openssl ciphers -v` 会列出 `ECDHE+AESGCM` 这类字符串选中的全部套件，以及各自的密钥交换、认证和加密算法。

```bash
openssl ciphers -v 'ECDHE+AESGCM'
```

```text
TLS_AES_256_GCM_SHA384         TLSv1.3 Kx=any      Au=any   Enc=AESGCM(256)            Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256   TLSv1.3 Kx=any      Au=any   Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256         TLSv1.3 Kx=any      Au=any   Enc=AESGCM(128)            Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(256)            Mac=AEAD
ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2 Kx=ECDH     Au=RSA   Enc=AESGCM(256)            Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(128)            Mac=AEAD
ECDHE-RSA-AES128-GCM-SHA256    TLSv1.2 Kx=ECDH     Au=RSA   Enc=AESGCM(128)            Mac=AEAD
```

前三个 TLS 1.3 套件总会出现，因为套件字符串管不到它们。语法上 `+` 表示同时满足，`:` 分隔多个条件，`!` 表示永久排除，`@STRENGTH` 表示按强度排序。TLS 1.2 那一列是该套件可用的最低版本，不代表只能用于 1.2。

## 只看某个协议版本会用到的套件

`-s` 只列出在当前设置下真正可用的套件，可以配合 `-tls1_2` 或 `-tls1_3`。

```bash
openssl ciphers -s -tls1_3 -v
openssl ciphers -s -tls1_2 | tr ':' '\n' | wc -l
```

```text
TLS_AES_256_GCM_SHA384         TLSv1.3 Kx=any      Au=any   Enc=AESGCM(256)            Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256   TLSv1.3 Kx=any      Au=any   Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256         TLSv1.3 Kx=any      Au=any   Enc=AESGCM(128)            Mac=AEAD
      27
```

TLS 1.3 一共定义了 5 个套件，OpenSSL 默认只启用 3 个，`TLS_AES_128_CCM_SHA256` 和 `TLS_AES_128_CCM_8_SHA256` 需要显式打开。第二条统计这版 OpenSSL 默认可用于 TLS 1.2 的套件数（27 个，另外还有 3 个 TLS 1.3 套件）。各 Linux 发行版经常改默认值，以你机器上的结果为准。

## 把 OpenSSL 名称换成 IANA 名称

`-stdname` 在 OpenSSL 名称旁边列出 RFC/IANA 标准名称，浏览器、Java、Go 用的都是后者。

```bash
openssl ciphers -stdname -s -tls1_2 'ECDHE+CHACHA20'
```

```text
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 - ECDHE-ECDSA-CHACHA20-POLY1305  TLSv1.2 Kx=ECDH     Au=ECDSA Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256   - ECDHE-RSA-CHACHA20-POLY1305    TLSv1.2 Kx=ECDH     Au=RSA   Enc=CHACHA20/POLY1305(256) Mac=AEAD
```

Nginx、Apache、HAProxy 的 `ssl_ciphers` 要写 OpenSSL 名称，Java、Go 的配置要写 IANA 名称。两种写法混用，是套件配置“写了不生效”的常见原因。

## 用 -ciphersuites 而不是 -cipher 指定 TLS 1.3 套件

TLS 1.3 套件由 `-ciphersuites` 控制，`-cipher` 对 TLS 1.3 握手不起作用。

```bash
openssl s_client -connect www.example.com:443 -tls1_3 -ciphersuites TLS_CHACHA20_POLY1305_SHA256 -brief < /dev/null
openssl s_client -connect www.example.com:443 -tls1_3 -cipher ECDHE-RSA-AES128-GCM-SHA256 -brief < /dev/null
```

```text
Protocol version: TLSv1.3
Ciphersuite: TLS_CHACHA20_POLY1305_SHA256
...
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
```

第二次连接完全忽略了 `-cipher`，协商出的是默认套件。服务器配置也是这样分开的：Nginx 的 `ssl_ciphers` 只管 TLS 1.2 及以下，TLS 1.3 套件要用 `ssl_conf_command Ciphersuites ...`（Nginx 1.19.4 起支持）。绝大多数站点保持默认就好。

## 检查服务器接受哪些 TLS 版本

逐个版本发起握手，打印每次的结果。

```bash
for v in tls1_2 tls1_3; do
  printf '%s: ' "$v"
  openssl s_client -connect www.example.com:443 -servername www.example.com -"$v" -brief < /dev/null 2>&1 \
    | grep -m1 -E 'Protocol version|alert|error' || echo failed
done
```

```text
tls1_2: Protocol version: TLSv1.2
tls1_3: Protocol version: TLSv1.3
```

只支持 TLS 1.2 的服务器会对 `-tls1_3` 回 `alert protocol version`（告警 70）。截至 2026 年 11 月，公网站点完全关闭 TLS 1.2 仍然为时过早，部分老客户端和函数库还不支持 TLS 1.3。

## 确认 TLS 1.0 和 1.1 真的关掉了

OpenSSL 3.x 在安全级别 1 下连 TLS 1.0/1.1 都不会发起，所以下结论之前先加 `@SECLEVEL=0`。

```bash
openssl s_client -connect www.example.com:443 -tls1_1 < /dev/null
openssl s_client -connect www.example.com:443 -tls1_1 -cipher 'DEFAULT:@SECLEVEL=0' < /dev/null
```

```text
...:error:0A0000BF:SSL routines:tls_setup_handshake:no protocols available:...
...:error:0A00042E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version:...:SSL alert number 70
```

第一条错误来自你自己的客户端，跟服务器无关。第二条是服务器拒绝了 TLS 1.1，这才是想要的结果；如果第二条反而连上了，说明服务器仍允许 TLS 1.1。这一变化写在 OpenSSL 3.0 迁移指南里；RFC 8996 在 2021 年正式弃用了 TLS 1.0 和 1.1。等保测评、安全扫描报告里“支持不安全的 TLS 版本”这一项，就可以用这两条命令自查。

## 枚举服务器接受的全部套件

每次握手只尝试一个套件，把成功的记下来。

```bash
HOST=www.example.com; PORT=443
for c in $(openssl ciphers -tls1_2 'ALL:@SECLEVEL=0' | tr ':' ' '); do
  echo | openssl s_client -connect $HOST:$PORT -servername $HOST -tls1_2 \
    -cipher "$c:@SECLEVEL=0" >/dev/null 2>&1 && echo "TLSv1.2 $c"
done
for s in TLS_AES_128_GCM_SHA256 TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 \
         TLS_AES_128_CCM_SHA256 TLS_AES_128_CCM_8_SHA256; do
  echo | openssl s_client -connect $HOST:$PORT -servername $HOST -tls1_3 \
    -ciphersuites $s >/dev/null 2>&1 && echo "TLSv1.3 $s"
done
```

```text
TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384
TLSv1.2 ECDHE-ECDSA-CHACHA20-POLY1305
TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256
TLSv1.2 ECDHE-ECDSA-AES256-SHA384
TLSv1.2 ECDHE-ECDSA-AES128-SHA256
TLSv1.2 ECDHE-ECDSA-AES256-SHA
TLSv1.2 ECDHE-ECDSA-AES128-SHA
TLSv1.3 TLS_AES_128_GCM_SHA256
TLSv1.3 TLS_AES_256_GCM_SHA384
TLSv1.3 TLS_CHACHA20_POLY1305_SHA256
```

这台服务器用的是 OpenSSL 默认配置加 ECDSA 证书，TLS 1.2 下仍接受 4 个 CBC 套件。只有本机 OpenSSL 认识的套件才能被测到，被编译时去掉的套件永远不会出现。需要的话按同样方法加 `-tls1`、`-tls1_1` 的循环。`ALL:@SECLEVEL=0` 在 OpenSSL 3.6 上展开约 140 个套件，也就是要发起这么多次连接，请只对自己的服务器使用。

## 检查服务器是否同时部署了 ECDSA 和 RSA 证书

用 `-sigalgs` 限定签名算法，双证书服务器会返回对应类型的证书。

```bash
openssl s_client -connect www.example.com:443 -sigalgs 'ECDSA+SHA256' < /dev/null 2>/dev/null | grep -m1 'a:PKEY'
openssl s_client -connect www.example.com:443 -sigalgs 'RSA-PSS+SHA256:RSA+SHA256' < /dev/null 2>/dev/null | grep -m1 'a:PKEY'
```

```text
   a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA384
   a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption
```

第二条如果报 `alert handshake failure`，说明服务器只有 ECDSA 证书，极少数很老的客户端会因此连不上。Nginx 部署双证书很简单：`ssl_certificate` 和 `ssl_certificate_key` 按密钥类型各写一组。

## 测试混合后量子密钥交换（OpenSSL 3.5+）

`-groups X25519MLKEM768` 只提供混合 ML-KEM 组，服务器支持才能握手成功。

```bash
openssl s_client -connect www.example.com:443 -groups X25519MLKEM768 -brief < /dev/null
```

```text
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: CN=www.example.com
Verification: OK
Negotiated TLS1.3 group: X25519MLKEM768
```

不支持 ML-KEM 的服务器会回 `ssl/tls alert handshake failure ... SSL alert number 40`。在默认设置下，OpenSSL 3.5+ 会同时发送 X25519MLKEM768 和 X25519 两个密钥份额，所以会悄悄回落，显示 `Peer Temp Key: X25519, 253 bits`。OpenSSL 3.0 到 3.4 根本不认识这个组名。背景见[后量子 TLS](https://ssl.sh/zh/learn/post-quantum-tls/) 和 [X25519MLKEM768](https://ssl.sh/zh/glossary/x25519mlkem768/)。

## 查看提供的组和密钥份额大小

`-trace` 会解码 ClientHello 和 ServerHello，包括 `supported_groups` 和 `key_share` 扩展。

```bash
openssl s_client -connect www.example.com:443 -trace < /dev/null 2>&1 \
  | grep -E 'supported_groups|key_share|NamedGroup|key_exchange:  \(len' | cut -c1-70
```

```text
        extension_type=supported_groups(10), length=18
        extension_type=key_share(51), length=1258
            NamedGroup: X25519MLKEM768 (4588)
            key_exchange:  (len=1216): 00DDC3A82B7EB93BC261440DE87C418A717D3
            NamedGroup: ecdh_x25519 (29)
            key_exchange:  (len=32): 233767194B5DECAE196362B3FC539DC6541371E
        extension_type=key_share(51), length=1124
            NamedGroup: X25519MLKEM768 (4588)
            key_exchange:  (len=1120): F4AB5DAE2E917CEBEA4E2B3BB80085B06DFE1
```

客户端的混合份额是 1216 字节（1184 字节 ML-KEM-768 封装公钥加 32 字节 X25519），服务器的是 1120 字节（1088 字节密文加 32 字节）。这个组的编号是 4588（0x11EC）。ClientHello 变大，正是部分老旧防火墙、中间设备处理后量子握手出问题的原因。

## 列出 KEM、签名算法和 TLS 组

`openssl list` 显示你这版 OpenSSL 及其 provider 支持哪些算法。

```bash
openssl list -kem-algorithms
openssl list -tls-groups                  # OpenSSL 3.5+
openssl list -tls-signature-algorithms    # OpenSSL 3.4+
openssl list -signature-algorithms | grep -i -E 'ml-dsa|slh-dsa'
```

```text
  { 2.16.840.1.101.3.4.4.2, id-alg-ml-kem-768, ML-KEM-768, MLKEM768 } @ default
  X25519MLKEM768 @ default
  SecP256r1MLKEM768 @ default
  ...
secp256r1:secp384r1:secp521r1:x25519:x448:...:MLKEM512:MLKEM768:MLKEM1024:SecP256r1MLKEM768:X25519MLKEM768:SecP384r1MLKEM1024
```

ML-KEM（FIPS 203）、ML-DSA（FIPS 204）和 SLH-DSA（FIPS 205）从 OpenSSL 3.5 起进入默认 provider，3.0 到 3.4 不加载外部 provider 就看不到它们。`@ default` 是实现该算法的 provider 名称。Ubuntu 22.04/24.04、Debian 12 等常见系统自带的仍是 3.0 系列，想测后量子要另装新版本。

## 提高或降低安全级别

套件字符串里的 `@SECLEVEL=n` 决定了可接受的密钥长度、签名算法和协议版本。

```bash
openssl s_client -connect www.example.com:443 -cipher 'DEFAULT:@SECLEVEL=2' -brief < /dev/null
openssl ciphers -s -tls1 -v 'DEFAULT:@SECLEVEL=0' | head -3
```

```text
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
...
ECDHE-ECDSA-AES256-SHA         TLSv1   Kx=ECDH     Au=ECDSA Enc=AES(256)               Mac=SHA1
ECDHE-RSA-AES256-SHA           TLSv1   Kx=ECDH     Au=RSA   Enc=AES(256)               Mac=SHA1
DHE-RSA-AES256-SHA             SSLv3   Kx=DH       Au=RSA   Enc=AES(256)               Mac=SHA1
```

级别 1（OpenSSL 默认）要求至少 80 位安全强度，禁止 SHA-1 和 MD5 签名；级别 2 要求 RSA、DSA、DH 密钥至少 2048 位，ECC 密钥至少 224 位。有些发行版把系统默认调到了级别 2，所以在这些系统上连接使用 1024 位 RSA 或 DH 参数的老服务器会报 `dh key too small`。级别 0 只在测试老旧系统时临时使用。
