SSD Nodes Learn 🎉 VPS $4.99/月起
指南 Matt Connor作者: Matt Connor

VPS如何检查并恢复AES-NI加速

检查VPS是否暴露AES-NI,测量CPUID被屏蔽后AES-GCM吞吐量的损失,并通过OPENSSL_ia32cap环境变量恢复快速路径。

VPS 上的 AES-NI 实际能带来什么

VPS 上的 AES-NI 是一组包含 6 条 x86 指令的指令集,可在硬件中执行一轮 AES(高级加密标准)运算。如果服务商隐藏了 CPU 型号中的这些指令,底层芯片仍然具备相关功能,但 OpenSSL 无法检测到它们,只能回退到软件实现。软件实现每字节大约需要多 10 倍的时钟周期。您可以使用一条命令检查该功能,使用两条命令测量差距,并且通常可以通过设置一个环境变量重新启用快速路径。

这些指令是 AESENCAESENCLASTAESDECAESDECLASTAESIMCAESKEYGENASSIST。Intel 在 2010 年推出了这些指令,AMD 随后也加入支持,因此您可能租用的任何服务器 CPU 都很可能具备相关硬件。配套指令 PCLMULQDQ 用于执行无进位乘法,这是 GCM(Galois/Counter 模式)生成认证标签所需的运算。只有两者都可用时,AES-GCM 才能实现较高速度,因为加密和生成标签是两项独立的工作。

在 VPS 上,以下 4 类场景会在监控中体现这一点:

  • TLS(传输层安全)终止。提供 AES-128-GCM 或 AES-256-GCM 的 Web 服务器,其大部分批量加密时间都消耗在 AES 中。
  • 加密卷。LUKS(Linux 统一密钥设置)和 dm-crypt 会在内核中对每次读写执行 aes-xts,由 CPU 完成运算。
  • 基于 AES 的 VPN 流量。使用 AES-256-GCM 的 OpenVPN 和使用 AES-GCM 的 IPsec 都依赖 AES-NI。
  • 加密备份。任何在数据离开服务器前使用 AES 加密数据流的程序,都会产生相同的开销。

有一种常见工作负载完全不受影响。WireGuard 的数据通道使用 ChaCha20-Poly1305,从不使用 AES。因此,在隐藏 AES-NI 标志的主机上,自行托管的 WireGuard VPN 运行速度不变。在为廉价 VPS 选择隧道方案前,这种差异也是比较 WireGuard 与 OpenVPN 的实际原因。

如何检查 VPS 是否支持 AES-NI

内核会将 CPUID 特性位复制到 /proc/cpuinfo,因此使用一次 grep 即可确认。

grep -m1 -o '\baes\b' /proc/cpuinfo
lscpu | grep -i -o '\baes\b'

任一命令输出 aes,都表示 CPU 向此 guest 声明支持 AES-NI。没有任何输出则表示不支持。lscpu 读取的是相同的标志,因此两条命令的结果始终一致。使用已安装的任一命令即可。

现在查看主机声称您正在使用哪种 CPU。

grep -m1 'model name' /proc/cpuinfo

如果显示 Intel(R) Xeon(R) Gold 6338 CPU @ 2.00GHzAMD EPYC 7443P 24-Core Processor 这样的真实型号字符串,表示主机将物理 CPU 型号传递给了您。显示 QEMU Virtual CPU version 2.5+Common KVM processor 则表示情况不同,这正是需要进一步了解的情况。

硅片支持该功能时为什么仍然缺少标志

CPUID 是程序用来查询 CPU 支持哪些功能的指令。在虚拟机中,CPUID 始终会陷入 hypervisor,因此由 hypervisor 决定向 guest 报告哪些信息。大多数控制面板会将这一决定显示为 guest CPU 模型。qemu64kvm64 是通用基线模型,其功能集都不包含 AES-NI 或 SSSE3,因此即使物理主机使用当前的 EPYC,guest 仍看不到 aes 标志。VPS 运行在他人的硬件上,因此它报告的每项功能,都是上一层做出的决定。如果你刚接触这种分层方式,请先阅读什么是 VPS

主机会有意选择通用模型,因为只有当 guest 从未获知目标主机缺少的功能时,才能在处理器不同的机器之间执行实时迁移。代价由你承担。你的内核和 OpenSSL 副本都会在启动时读取一次经过屏蔽的 CPUID,随后在该进程的整个生命周期内选择较慢的代码路径。

从源头修复需要修改主机端设置:在 QEMU 中使用 -cpu host,选择包含 AES-NI 的命名模型,或向模型中显式添加 +aes。你无法在 guest 内部设置这些选项。提交支持工单,或选择其 hypervisor 会透传 CPU 模型的套餐,才是持久的解决方案。

使用 openssl speed 测量差距

不要盲目相信已发布的基准测试。请运行您自己的服务器实际协商的密码算法。

openssl version
openssl speed -evp aes-128-gcm

结果行标记为 AES-128-GCM,并以每秒 1000 字节为单位,给出 6 种块大小下的吞吐量。对于批量传输,请读取 8192 字节列。16 字节列主要受每次调用的开销影响,无法反映文件下载性能。

现在关闭软件中的 AES-NI 和 PCLMULQDQ,运行相同的命令:

OPENSSL_ia32cap="~0x200000200000000" openssl speed -evp aes-128-gcm

该值来自 OpenSSL 自带的能力向量文档。开头的 ~ 表示“清除这些位”。第 57 位是 AES-NI,第 33 位是 PCLMULQDQ,因此 0x200000200000000 恰好只指定这两项。如果第二个数远低于第一个数,说明您的主机支持正常工作的 AES-NI,测试到此结束。如果两个数相同,说明 OpenSSL 原本就已使用软件路径,因为没有需要清除的标志。

ChartAES-128-GCM throughput, one core, 8192-byte blocks (representative published figures)
The data behind this chart
[
  {
    "label": "AES-NI and PCLMULQDQ",
    "mb_per_sec": "4,850",
    "cycles_per_byte": 0.7
  },
  {
    "label": "Software fallback",
    "mb_per_sec": 310,
    "cycles_per_byte": 11.0
  }
]

这些是现代 x86 处理器核心在接近 3.4 GHz 时的代表性已发布数据,并非从某台特定主机测得。请将其作为性能形态参考。硬件路径的速度约为每字节 0.7 个周期,软件回退路径约为 11.0 个周期;在单个核心上,换算后分别约为 4,850 MB/s 和 310 MB/s。上面的两条命令产生的数值,才是描述您服务器的唯一数据。评估机器的其他部分时也应遵循相同原则,因此在判断某个方案前,请将本测试与一种可重复的 VPS 基准测试方法结合使用。

使用 OPENSSL_ia32cap 强制启用指令集

下面是最容易让人意外的部分。AES-NI 指令无需特权,虚拟机监控程序不会拦截它们。只有 CPUID 会被拦截。因此,宿主机可以向您的虚拟机报告不支持 AES-NI,同时 AESENC 仍以原生速度全速执行。软件因为查询 CPUID 后得到错误结果而跳过快速路径。指令本身从未停止工作。

OpenSSL 允许您代替 CPU 提供能力信息。在 OPENSSL_ia32cap 中设置普通十六进制值时,会覆盖整个能力向量,而不是屏蔽其中的位。

OPENSSL_ia32cap="0x0200020207000000" openssl speed -evp aes-128-gcm

如果这次运行比普通运行快数倍,说明 CPU 支持 AES-NI,而宿主机将其隐藏了。首先,这是一个诊断方法。对于 OpenSSL,它也恰好可以解决问题。

如何构造这个十六进制值

第一个逻辑向量将 CPUID 叶 1 的 EDX 放入低 32 位,将叶 1 的 ECX 放入高 32 位。在低半部分中,第 24 位表示 FXSR,第 25 位表示 SSE,第 26 位表示 SSE2,因此得到 0x07000000。在高半部分中,第 33 位表示 PCLMULQDQ,第 41 位表示 SSSE3,第 57 位表示 AES-NI,因此得到 0x02000202。合并后就是 0x0200020207000000。列表中包含 SSSE3,是因为基于 OpenSSL PCLMULQDQ 的 GHASH 使用 pshufb 交换字节,而通用虚拟机 CPU 模型会同时隐藏 SSSE3 和 AES-NI。

这里有两个警告,而且您可以故意触发它们。

只设置第一个向量会使后续向量保持为零,从而关闭 AVX2 和 AVX-512 代码路径。这里是有意这样做的。不要尝试在已屏蔽的虚拟机上强制启用 AVX 位,因为 AVX 寄存器需要操作系统在 XCR0 中启用扩展状态,而您的内核根据同一个被屏蔽的 CPUID 结果拒绝执行此操作。随后执行 VEX 编码的指令会触发非法操作码错误,进程将退出。

在确实不支持 AES-NI 的 CPU 核心上强制启用 AES-NI,会立即终止进程:

Illegal instruction (core dumped)

这是 AESENC 触发的非法操作码错误,因为该核心没有可执行的此类指令。某些服务器固件也可以在下一次重置前通过硬件禁用 AES-NI,表现出的症状完全相同。无论是哪种情况,解决方案都是更换宿主机,而不是更换环境变量。

要让此覆盖设置对长时间运行的服务持续生效,请使用 systemd drop-in。

sudo systemctl edit nginx
[Service]
Environment="OPENSSL_ia32cap=0x0200020207000000"
sudo systemctl daemon-reload
sudo systemctl restart nginx
sudo systemctl show nginx -p Environment

最后一条命令应将该变量回显出来。请明确了解这样做的后果:如果服务器日后迁移到确实不支持 AES-NI 的宿主机,nginx 会在第一次 TLS 连接时因非法指令而退出。请将此设置记录到运行手册中,或者完全不要在生产环境中使用覆盖设置,只在提交工单时用它验证问题。

无法修复的部分

OPENSSL_ia32cap只对 OpenSSL 生效,其他任何程序都不会受到影响。其他软件都会自行检测功能,并且不会读取该变量。

内核是关键情况。dm-crypt 和 LUKS 使用内核加密 API;当 CPU 功能位缺失时,aesni_intel模块会拒绝加载:

modprobe: ERROR: could not insert 'aesni_intel': No such device

用户空间没有对应的变量。内核只会在启动时读取一次 CPUID;除非换到其他主机并重新启动,否则该决定不会改变。因此,无论 OpenSSL 如何运行,加密卷都会继续使用软件密码。请测量实际获得的性能:

sudo cryptsetup benchmark -c aes-xts -s 256

使用硬件 AES 时,aes-xts 256b行的结果可达数千 MiB/s;没有硬件 AES 时,只有几百 MiB/s。拥有独立检测机制的语言运行时同样无法通过该变量控制,Go 和 Java 就是其中的例子。Go 的 crypto/aes会直接检查 CPUID;如果该功能位未设置,则会静默使用其恒定时间软件实现。如果负责终止 TLS 的服务是 Go 二进制程序,OpenSSL 变量对它不起作用。

如果无法使用 AES-NI,请优先选择 ChaCha20

ChaCha20-Poly1305 专为纯软件环境下的高性能而设计。在不具备可用 AES-NI 的 CPU 核心上,它通常会大幅领先 AES-GCM。因此,在此类主机上,合理的做法是不再优先选择 AES。

对于 nginx 1.19.4 及更高版本,且其基于 OpenSSL 1.1.1 或更高版本构建时:

ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_conf_command Ciphersuites TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384;

ssl_ciphers适用于 TLS 1.2。ssl_conf_command Ciphersuites适用于 TLS 1.3。nginx 没有用于 TLS 1.3 的专用指令,而是将该字符串直接传递给 OpenSSL,不会对其进行检查。因此,其中的拼写错误会被静默接受。重新加载配置,并确认客户端可用的配置:

sudo nginx -t && sudo systemctl reload nginx
openssl s_client -connect example.com:443 -tls1_3 </dev/null 2>/dev/null | grep -i cipher

正常结果应包含 TLS_CHACHA20_POLY1305_SHA256。在正式应用更改前,在同一台主机上运行 openssl speed -evp chacha20-poly1305,并与 AES 测试结果进行对比,让这两个数值决定采用哪种配置。

ARM 主机使用不同的扩展

AES-NI 仅适用于 x86。ARM VPS 使用 ARMv8 加密扩展,这是执行相同功能的另一套指令集。在 aarch64 上,标志位位于 Features,而不是 flags

grep -m1 Features /proc/cpuinfo

查找 aespmullpmull 是 ARM 对应 PCLMULQDQ 的扩展,GCM 出于相同原因需要它。ARM 上 OpenSSL 的覆盖变量是 OPENSSL_armcap,其位布局在 OpenSSL 源代码的 crypto/arm_arch.h 中定义,因此本指南中的 x86 十六进制值在那里没有意义。实际上,作为 VPS 主机销售的 ARM 服务器 CPU 通常都会暴露这些扩展,因此功能被屏蔽的问题主要发生在 x86 环境中。

故障模式及其对应的输出

/proc/cpuinfo 中没有 aes,强制运行速度明显更快。 主机屏蔽了 CPUID。确认型号名称是否为通用名称,然后询问服务提供商的虚拟机监控程序向虚拟机呈现了哪种来宾 CPU 型号。

/proc/cpuinfo 中没有 aes,强制运行会输出 Illegal instruction 这些指令确实不存在,或者已被固件禁用。请将工作负载迁移到其他主机。

存在 aes,但吞吐量仍然很低。 确认读取的是 8192 字节列,并确认没有其他任务占用该 CPU 核心。在共享型套餐中,邻居租户产生的高负载看起来与缺少 CPU 特性完全相同,直到您在一天中不同时间运行两次测试。

屏蔽运行和正常运行得到相同的数值。 OpenSSL 原本就已使用软件路径。这个结果就是要确认的事实,不是测试出错。

嵌套虚拟化会改变下一层中的结果。 来宾系统中的来宾系统只能获得中间层选择传递的 CPUID 信息,AES-NI 很容易在这一层丢失而不易察觉。如果您在 VPS 上运行 嵌套虚拟机,还应在内部来宾系统中检查该标志,以及在您租用的机器上检查该标志。

FAQ

为什么我的 VPS 在 /proc/cpuinfo 中没有 aes 标志?

因为 hypervisor 提供的是通用 guest CPU 模型。qemu64kvm64 的特性集中不包含 AES-NI,因此无论物理处理器是什么,CPUID 都会报告该特性不存在。主机这样做,是为了让运行中的 guest 能够在 CPU 不同的机器之间迁移。运行 grep -m1 'model name' /proc/cpuinfo:如果输出类似 QEMU Virtual CPU version 2.5+Common KVM processor,就说明使用的是通用模型;如果输出真实的 Xeon 或 EPYC 型号字符串,则说明 CPU 模型已透传,而该标志确实不在处理器硬件中。

OPENSSL_ia32cap 是真的启用 AES-NI,还是只是在输出中伪装启用?

它会启用真实的指令。AES-NI 指令不需要特权,hypervisor 也不会拦截它们,因此无论 CPUID 报告什么,AESENC 都会原生执行。只有 CPUID 指令会被拦截。将 OPENSSL_ia32cap 设置为普通十六进制值,会替换 OpenSSL 从 CPUID 获取的结果,因此 OpenSSL 会选择硬件代码路径,硬件随后会以全速执行该路径。如果处理器确实不支持这些指令,进程会在首次执行 AES 操作时因 Illegal instruction (core dumped) 退出。

这个覆盖设置会提高 LUKS 加密卷的速度吗?

不会。OPENSSL_ia32cap 只会被 OpenSSL 读取,其他组件不会读取它。LUKS 和 dm-crypt 使用 kernel crypto API;当特性位未设置时,aesni_intel 模块会因 modprobe: ERROR: could not insert 'aesni_intel': No such device 加载失败。kernel 会在启动时读取 CPUID,任何 user-space 变量都无法改变这一点。使用 sudo cryptsetup benchmark -c aes-xts -s 256 测量实际结果,并将 aes-xts 256b 行与报告该标志的主机进行比较。

缺少 AES-NI 标志会降低 WireGuard 的速度吗?

不会。WireGuard 对所有数据都使用 ChaCha20-Poly1305,从不使用 AES 指令,因此在屏蔽该标志和未屏蔽该标志的主机上,其吞吐量相同。OpenVPN 和配置为使用 AES-GCM 的 IPsec 在不支持 AES-NI 的主机上会降低吞吐量。因此,同一 VPS 上的两个隧道可能表现出很大差异。在将问题归咎于网络之前,应先考虑这一点。

如何检查 ARM VPS 是否支持 AES-NI?

ARM 核心不支持 AES-NI。它们提供 ARMv8 加密扩展,使用不同的指令实现相同功能。运行 grep -m1 Features /proc/cpuinfo,并查找 aespmull;aarch64 会在 Features 下列出这些特性,而不是在 flags 下列出。x86 的 OPENSSL_ia32cap 值在 ARM 上没有意义。ARM 上 OpenSSL 的等效变量是 OPENSSL_armcap,其位布局定义在 OpenSSL 源码的 crypto/arm_arch.h 中。

#aes-ni#cpu#openssl#encryption#性能