请告诉我,如何检查 OpenSSL 是否支持/使用 Intel AES-NI?
如何检查 OpenSSL 是否支持/使用 Intel AES-NI?
事情没那么简单,虽然应该如此。 OpenSSL 曾经提供了一个函数来获取 ia32 处理器检测到的功能,但它不再可用。请参阅
OPENSSL_ia32cap_loc
手册页中对
OPENSSL_ia32cap
的讨论。另请参阅 OpenSSL 邮件列表上的在运行时验证 AES-NI 使用?。
如果您要链接到 OpenSSL 静态库,那么您可以使用:
extern unsigned int OPENSSL_ia32cap_P[];
# define AESNI_CAPABLE (OPENSSL_ia32cap_P[1]&(1<<(57-32)))
if(AESNI_CAPABLE)
/* AES-NI is available */
如果您要链接到 OpenSSL 共享对象,则符号
OPENSSL_ia32cap_P
不会导出。在这种情况下,您需要编写自己的检测代码。
我什至不关心 OpenSSL,因为它只适用于库的静态链接。我在下面分享了我用于检测的代码。我相信我从英特尔的戴夫·约翰斯顿(Dave Johnston)那里窃取了其中的很大一部分(他设计了 RDRAND 电路)。
注意:下面的代码可能会错误地拒绝具有 AES-NI 的 AMD 处理器。我没有可供测试的处理器,因此无法提供代码。
注意:下面的代码在 Valgrind 下不会按预期执行。没有针对 AES-NI 或 RDRAND 指令的模拟,因此 Valgrind 从
CPUID
返回一个“经过修改的”值,因此看起来它们不可用。请参阅邮件列表上的在 Valgrind 下运行时内联汇编的错误结果。
尽管 AES-NI 可用,但它不意味着您将使用它。
如果您使用像
AES_*
这样的低级原语,那么您将不会使用AES-NI,因为它是软件实现。
如果您使用高级
EVP_*
齿轮,那么您将使用AES-NI(如果可用)。库将自动切换到 AES-NI。
如果 AES-NI 可用但您不想使用它,请在启动程序之前执行以下操作:
$ export OPENSSL_ia32cap="~0x200000200000000"
您可以使用以下 OpenSSL 命令测试速度差异。切换上面的导出以查看差异:
$ openssl speed -elapsed -evp aes-128-ecb
struct CPUIDinfo {
unsigned int EAX;
unsigned int EBX;
unsigned int ECX;
unsigned int EDX;
};
int HasIntelCpu();
int HasAESNI();
int HasRDRAND();
void cpuid_info(CPUIDinfo *info, const unsigned int func,
const unsigned int subfunc);
int HasIntelCpu() {
CPUIDinfo info;
cpuid_info(&info, 0, 0);
if (memcmp((char *) (&info.EBX), "Genu", 4) == 0
&& memcmp((char *) (&info.EDX), "ineI", 4) == 0
&& memcmp((char *) (&info.ECX), "ntel", 4) == 0) {
return 1;
}
return 0;
}
int HasAESNI() {
if (!HasIntelCpu())
return 0;
CPUIDinfo info;
cpuid_info(&info, 1, 0);
static const unsigned int AESNI_FLAG = (1 << 25);
if ((info.ECX & AESNI_FLAG) == AESNI_FLAG)
return 1;
return 0;
}
int HasRDRAND() {
if (!HasIntelCpu())
return 0;
CPUIDinfo info;
cpuid_info(&info, 1, 0);
static const unsigned int RDRAND_FLAG = (1 << 30);
if ((info.ECX & RDRAND_FLAG) == RDRAND_FLAG)
return 1;
return 0;
}
void cpuid_info(CPUIDinfo *info, unsigned int func, unsigned int subfunc) {
__asm__ __volatile__ (
"cpuid"
: "=a"(info->EAX), "=b"(info->EBX), "=c"(info->ECX), "=d"(info->EDX)
: "a"(func), "c"(subfunc)
);
}
根据jww提供的信息构建了几个快速衬里:
openssl 速度 -elapsed -evp aes-128-cbc ... OPENSSL_ia32cap="~0x200000200000000" openssl 速度 -elapsed -evp aes-128-cbc ...
第一行的输出应该明显快于第二行。就我而言,在 i5 测试机上,几乎翻了一番。
我制作了一个 Perl 脚本(见下文),它在默认模式下运行 OpenSSL,并通过环境变量显式禁用 AES-NI。它测试 CBC 模式(早期指令,如
AESENC
)和 GCM 模式(后来的指令,如 PCLMULQDQ
或 VPCLMULQDQ
)。如果 OpenSSL 的默认模式与通过环境变量显式禁用 AES-NI 的模式之间的增益大于 0%(实际上,应至少为 200%),则 OpenSSL 使用新指令。这是脚本的示例输出:
AES-CBC In default mode : 1641669.67k bytes/second, on average
AES-CBC With AES-NI disabled : 448189.38k bytes/second, on average
AES-CBC Gain : 266.29%
AES-GCM In default mode : 4112319.92k bytes/second, on average
AES-GCM With AES-NI disabled : 272767.79k bytes/second, on average
AES-GCM Gain : 1407.63%
我已经在 Windows、Linux 和 MacOS 上测试了该脚本。在 Windows 上,默认情况下不安装 Perl,但您可以下载并安装任何 Perl 实现,例如 Strawberry Perl。在 Linux 和 MacOS 中,Perl 应该已经安装。对于 MacOS,可以通过“brew install perl”并按照此命令给出的说明安装更高版本的 Perl;但是,对于我的脚本,不需要更高版本的 Perl。
尽管如此,无论操作系统如何,此脚本都会使用 Intel 兼容处理器(x86/IA-32 或 x86-64/IA-64)的环境变量。因此,此脚本不会在 ARM 处理器(例如 Mac Mini M1 上使用的处理器)上给出正确的结果,因为它将无法启用/禁用特殊的 AES 指令。
您的问题仍然是如何检查 OpenSSL 是否支持/使用 Intel AES-NI。因此,您的问题仅限于应该具有 Intel AES-NI 指令的 Intel 兼容处理器。
这是脚本:
#!/usr/bin/perl
use strict;
my $formatstr = 'The OpenSSL output does not match the regular expression.\nRegEx: %s\n---output begin---\n%s\n---output end---:';
my $regex_cbc = qr/^\s*AES-128-CBC\s+(\d+\.\d+)k\s+(\d+\.\d+)k\s+(\d+\.\d+)k\s+(\d+\.\d+)k\s+(\d+\.\d+)k\s+(\d+\.\d+)k\s*$/m;
my $regex_gcm = qr/^\s*AES-128-GCM\s+(\d+\.\d+)k\s+(\d+\.\d+)k\s+(\d+\.\d+)k\s+(\d+\.\d+)k\s+(\d+\.\d+)k\s+(\d+\.\d+)k\s*$/m;
my $command_cbc = 'openssl speed -elapsed -seconds 1 -evp aes-128-cbc';
my $command_gcm = 'openssl speed -elapsed -seconds 1 -evp aes-128-gcm';
my $env_var_name = 'OPENSSL_ia32cap';
my $env_var_value_disable_aes_ni = '~0x200000200000000';
# Test the CBC mode
delete $ENV{$env_var_name};
print "Running OpenSSL for AES-128-CBC in default mode...\n";
sleep(1); # Cooldown for the CPU before the benchmark
my $defaultrun_cbc = `$command_cbc`;
chomp $defaultrun_cbc;
print "Running OpenSSL for AES-128-CBC with AES-NI explicityly disabled...\n";
sleep(1); # Cooldown for the CPU before the benchmark
$ENV{$env_var_name}=$env_var_value_disable_aes_ni;
my $disabledrun_cbc = `$command_cbc`;
chomp $disabledrun_cbc;
# Test the GCM mode
delete $ENV{$env_var_name};
print "Running OpenSSL for AES-128-GCM in default mode...\n";
sleep(1); # Cooldown for the CPU before the benchmark
my $defaultrun_gcm = `$command_gcm`;
chomp $defaultrun_gcm;
print "Running OpenSSL for AES-128-GCM with AES-NI explicityly disabled...\n";
sleep(1); # Cooldown for the CPU before the benchmark
$ENV{$env_var_name}=$env_var_value_disable_aes_ni;
my $disabledrun_gcm = `$command_gcm`;
chomp $disabledrun_gcm;
# Calculate the results
my $total_default_cbc; my $total_disabled_cbc; my $total_default_gcm; my $total_disabled_gcm;
if ( $defaultrun_cbc =~ $regex_cbc ) {$total_default_cbc = ( $1 + $2 + $3 + $4 + $5 + $6 ) / 6;} else {die sprintf( $formatstr, $regex_cbc, $defaultrun_cbc );}
if ( $disabledrun_cbc =~ $regex_cbc ) {$total_disabled_cbc = ( $1 + $2 + $3 + $4 + $5 + $6 ) / 6;} else {die sprintf( $formatstr, $regex_cbc, $disabledrun_cbc);}
my $percentage_gain_cbc = 100 * ( $total_default_cbc - $total_disabled_cbc ) / $total_disabled_cbc;
if ( $defaultrun_gcm =~ $regex_gcm ) {$total_default_gcm = ( $1 + $2 + $3 + $4 + $5 + $6 ) / 6;} else {die sprintf( $formatstr, $regex_gcm, $defaultrun_gcm );}
if ( $disabledrun_gcm =~ $regex_gcm ) {$total_disabled_gcm = ( $1 + $2 + $3 + $4 + $5 + $6 ) / 6;} else {die sprintf( $formatstr, $regex_gcm, $disabledrun_gcm);}
my $percentage_gain_gcm = 100 * ( $total_default_gcm - $total_disabled_gcm ) / $total_disabled_gcm;
# Print the results
print "\n\n";
print "AES-CBC In default mode : " . sprintf( "\%14.2f", $total_default_cbc ) . "k bytes/second, on average\n";
print "AES-CBC With AES-NI disabled : " . sprintf( "\%14.2f", $total_disabled_cbc ) . "k bytes/second, on average\n";
print "AES-CBC Gain : " . sprintf( "\%14.2f", $percentage_gain_cbc) . "%\n";
print "AES-GCM In default mode : " . sprintf( "\%14.2f", $total_default_gcm ) . "k bytes/second, on average\n";
print "AES-GCM With AES-NI disabled : " . sprintf( "\%14.2f", $total_disabled_gcm ) . "k bytes/second, on average\n";
print "AES-GCM Gain : " . sprintf( "\%14.2f", $percentage_gain_gcm) . "%\n";