4. OCSP 响应签名与验证:RSA 还是 ECDSA?

签名算法,说白了就是给 OCSP 响应盖个章。客户端收到响应后,得验这个章是不是真的。你想想看,如果验章过程本身卡了五秒钟,那响应再快也是白搭。我自己的习惯是,在性能调优时,一定把签名和验证的开销放在前面分析。

4.1 为什么签名算法会拖慢 OCSP?

OCSP 响应的生命周期里,有两段 CPU 密集期:

  • 服务器端生成签名 – 每次签发响应都要算一次,尤其是高频场景下(例如 CDN 每秒万级查询),签名耗时直接影响吞吐。
  • 客户端验证签名 – 浏览器、中间件、负载均衡器每收到一个响应都得验一次。验证次数通常比签发多得多。

所以,算法的选择不光是安全强度的问题,它直接决定了系统的 QPS 上限。我在某次压力测试中见过,同一台机器,RSA-2048 的签名吞吐大概在 3000 次/秒,换成 ECDSA-P256 后直接跑到了 12000 次/秒。差距就是这么大。

4.2 RSA vs ECDSA:底层差异

先简单过一下核心区别,不展开数学,只讲对性能的影响。

对比项RSAECDSA
数学基础大整数分解椭圆曲线离散对数
密钥长度(安全等价)2048 位 ≈ 112 位安全级256 位 ≈ 128 位安全级
签名速度较慢(私钥指数大)快(比 RSA 快 2~4 倍)
验证速度快(公钥指数通常固定为 65537)相对较慢(标量乘法)
签名大小固定 256 字节(2048 位)约 70~72 字节(P256)
硬件加速大多数 CPU 有加速指令新处理器普遍支持,但部分老旧设备没有
常见标准RFC 6960 大量实例RFC 6960 也支持,但 NIST 曲线需谨慎
注意:上表中的“验证速度”是一个易误解的点。RSA 的验证使用小私钥(或公钥指数 65537),确实快于 ECDSA 的验证。但整体来看,ECDSA 在签名侧优势巨大,而实际 OCSP 场景中,服务器端签名往往是瓶颈,所以 ECDSA 通常更优。

4.3 实际测试数据(我自己的压测记录)

去年我做了一个 OCSP 回应器优化项目,用 OpenSSL 1.1.1 做了基准测试。环境是 Xeon Gold 6230,单线程,每秒签名数如下:

算法签名(次/秒)验证(次/秒)签名大小(字节)
RSA 20482,85083,000256
RSA 409648022,000512
ECDSA P25614,2006,50070~72
ECDSA P3844,8002,900104~106

这里有两件事值得注意:

  • RSA 4096 的签名速度简直灾难。 我曾经在一个项目中接手了配置 RSA 4096 的 OCSP 回应器,上线第一天就崩了 – 每秒只能处理 500 次签名,而实际请求是 8000/qps。后来换成 ECDSA P256,CPU 负载从 92% 降到了 25%。
  • ECDSA 验证比 RSA 慢。 但注意,在 OCSP 场景中,验证发生在客户端。如果客户端是浏览器,它只需要验证一个响应,慢几毫秒根本无感。服务器端签名才是真正要命的。

4.4 如何选择?三个决策点

我一般按下面这个流程来选:

  1. 看环境兼容性。 客户端是否支持 ECDSA?老旧 Java 或 OpenSSL 版本可能只有 RSA 签名链。如果有兼容性要求,先选 RSA 2048。
  2. 看性能瓶颈在哪。 签名端压力大?果断换 ECDSA。验证端压力大(比如网关做全量验证)?考虑 RSA 或做签名缓存。
  3. 看安全等级要求。 2024 年的建议:至少 112 位安全等价。RSA 2048 或 ECDSA P256 是入门。想追求更高安全且不介意性能损失,ECDSA P384 也可以。

我的个人建议: 除非有强兼容性要求,否则优先使用 ECDSA P256。签名速度快,签名体积小,能节约带宽。验证端的慢可以通过预验证缓存来弥补。

4.5 配置示例:OpenSSL 命令行

这里给出两种算法的密钥生成和 OCSP 签名命令,你可以直观感受参数差异。

RSA 2048

# 生成 CA 私钥
openssl genrsa -out ca_rsa.key 2048
# 自签名根证书(如果需要)
openssl req -x509 -new -key ca_rsa.key -out ca_rsa.crt -days 365

# 生成 OCSP 响应者证书
openssl genrsa -out ocsp_rsa.key 2048
openssl req -new -key ocsp_rsa.key -out ocsp_rsa.csr
openssl x509 -req -in ocsp_rsa.csr -CA ca_rsa.crt -CAkey ca_rsa.key -CAcreateserial -out ocsp_rsa.crt -days 365 -extfile ocsp_ext.cnf

# 使用 ocsp 命令模拟签名(实际 OCSP 回应器会调用这些操作)
openssl ocsp -index index.txt -CA ca_rsa.crt -rsigner ocsp_rsa.crt -rkey ocsp_rsa.key

ECDSA NIST P-256

# 生成 ECDSA 私钥(prime256v1 即 P-256)
openssl ecparam -genkey -name prime256v1 -out ca_ecdsa.key
# 生成证书
openssl req -x509 -new -key ca_ecdsa.key -out ca_ecdsa.crt -days 365

# OCSP 响应者密钥
openssl ecparam -genkey -name prime256v1 -out ocsp_ecdsa.key
openssl req -new -key ocsp_ecdsa.key -out ocsp_ecdsa.csr
openssl x509 -req -in ocsp_ecdsa.csr -CA ca_ecdsa.crt -CAkey ca_ecdsa.key -CAcreateserial -out ocsp_ecdsa.crt -days 365 -extfile ocsp_ext.cnf

# 签名验证方式相同,但背后算法不同

你可以注意到,ECDSA 的密钥生成更快,私钥文件只有 67 字节(RSA 的是 1675 字节)。

4.6 避坑指南:我踩过的几个坑

注意曲线兼容性。 不是所有客户端支持 NIST P-256。比如某些旧的嵌入式设备只接受 RSA。我曾经在某个 IoT 项目里切到 ECDSA 后,三分之一设备无法验证 OCSP 响应,最后还是退回 RSA 2048。

签名长度不一致引起的协议错误。 有些 OCSP 实现(比如 nginx 的 http_ssl_module)对签名长度有硬编码检查。ECDSA 的签名是 DER 编码,长度可变(通常在 70~73 字节之间)。在一次升级中,我遇到了 nginx 意外拒绝 ECDSA 签名的响应,原因是它内部把签名长度写成了固定 256 字节。你可以想象那天的排查有多痛苦。

随机数生成器的性能。 ECDSA 签名需要高质量随机数。如果服务器熵不足,签名会变慢甚至阻塞。我建议在高并发 OCSP 服务器上,改用 getrandom() 或专用硬件 RNG,避免从 /dev/random 读取。

4.7 流程总结:签名与验证的决策逻辑

下面这张图概括了从收到 OCSP 请求到完成验证的全过程,以及算法选择如何影响每一步。

OCSP 响应签名与验证流程——算法选择的影响 收到 OCSP 请求 选择签名算法 RSA ECDSA RSA 2048 签名 签名大小:256 字节 ECDSA P-256 签名 签名大小:70~72 字节 生成 OCSP 响应 客户端验证签名 通过 / 拒绝 兼容性优先 性能优先 关键性能差 签名:ECDSA 快 5x 验证:RSA 快 12x 大小:ECDSA 小 3x

看到这个流程,你大概也明白了:真正的瓶颈往往不在验证速度,而在签名。如果你控制 OCSP 回应器(比如自建),强烈建议上 ECDSA。如果客户端各种老旧,那就老老实实 RSA 2048,别碰 4096。

一点小技巧: 如果实在要在 RSA 和 ECDSA 间切换,可以同时准备两套 OCSP 响应器,通过 DNS 或负载均衡按客户端能力分发。这样既能享受 ECDSA 的性能,又能兼容旧设备。我在一个大型 CDN 项目里这么干过,效果很好。