VIA PadLock—高速加密

作者:Michal Ludvig

可能每个使用过加密的人很快就会意识到对处理器能力的需求瞬间增长。在较旧的系统上,使用加密文件系统的代价是文件操作速度变慢;在较新的系统上,代价至少是 CPU 负载显著增加。使用 IPsec 协议加密网络流量也会降低速度,有时即使在标准的 100Mbps 网络上,您也可能会遇到性能问题。

然而,存在一些方法可以解决这些加密/性能权衡问题

  • 不加密:显然是最廉价的解决方案,但从长远来看,这可能会变得非常昂贵。

  • 接受速度减慢:典型的方法。

  • 使用独立的密码学加速器:例如,PCI 卡的帮助不如您预期的那么大,因为数据必须更频繁地遍历 PCI 总线。

  • 使用配备 VIA PadLock 技术的 CPU。什么是 VIA PadLock?请继续阅读。

VIA PadLock

不久前,VIA 引入了一种简单但略有争议的方法:选择一些加密算法并将它们直接连接到 CPU 中。结果是推出了一个 i686 类处理器,它可以理解一些专用于加密功能的新指令。这项技术称为 VIA PadLock,该处理器与 AMD Athlon 和 Intel Pentium 完全兼容。

机器处理器上可用的 PadLock 功能由其版本决定。处理器版本通常写为家族-型号-步进 (F/M/S) 三元组。对于 i686 类 CPU,家族始终为 6。如果型号为 9,则您的 CPU 具有 Nehemiah 核心;如果型号为 10,则它具有 Esther 核心。步进表示每个型号的修订版本。您可以在 /proc/cpuinfo 中找到处理器的版本。

Nehemiah 步进 3 及更高版本提供了一种基于电噪声的随机数生成器 (RNG),它可以为不同的目的生成良好的随机数。访问 RNG 的指令称为 xstore。与 Intel 和 AMD 处理器一样,VIA 处理器中的随机数生成器也由 hw_random 设备驱动程序支持。

Nehemiah 步进 8 及更高版本包含两个独立的 RNG 和高级密码引擎 (ACE)。ACE 可以使用高级加密标准 (AES) 算法,以三种标准密钥长度(128、192 和 256 字节)在四种不同的操作模式下加密和解密数据:电子密码本 (ECB)、密码块链接 (CBC)、密码反馈 (CFB) 和输出反馈 (OFB) 模式(请参阅在线资源)。相应的指令称为 xcryptecb、xcryptcbc 等。在本文的后面部分,我主要使用它们的通用组名称 xcrypt,而不是特定于模式的指令名称。

Esther 步进 0 及更高版本继承了 Nehemiah 的两个 RNG 单元。ACE 扩展了计数器 (CTR) 模式支持和 MAC(消息认证码)计算。还有两个新的首字母缩略词 PHE 和 PMM。PadLock 哈希引擎 (PHE) 用于计算给定输入块的密码哈希值,也称为摘要,使用 SHA1 或 SHA256 算法。建议的指令名称是 xsha。

PadLock Montgomery 乘法器 (PMM) 负责加速非对称或公钥密码学中最耗时的计算之一:AB mod M,其中 A、B 和 M 是巨大的数字,通常为 1,024 或 2,048 位。此指令称为 montmul。

如上所述,在本文的其余部分,我主要讨论 xcrypt 指令。进一步描述的原理主要也适用于其他单元,xcrypt 仅用作示例。此外,本加密讨论中涵盖的术语和概念也适用于解密。

如何使用 PadLock

与通常插入 PCI 插槽的外部密码学加速器相比,PadLock 引擎是 CPU 的组成部分。这一事实大大简化了它的使用,因为它没有必要费心访问总线或处理中断、异步操作等等。使用 xcrypt 加密内存块就像使用 movs 指令复制它一样容易。

在这一点上,加密几乎是一个原子操作。在执行指令之前,缓冲区包含明文输入数据;几个时钟周期后,当执行完成时,我们就有了密文。如果任务请求处理单个块(对于 AES 算法,为 16 字节),则操作完全是原子的。也就是说,CPU 不会在中间中断它,并且在加密完成之前不会执行任何其他操作。

但是,如果要处理的缓冲区包含 1GB 的纯文本怎么办?当缓冲区如此大时,停止所有其他操作并等待加密完成是不好的。在这种情况下,CPU 可以在每处理完 16 字节的单个块后中断加密。当前状态被保存,并且可以完成的任何其他事情都会完成——可以处理中断并切换进程。一旦加密过程重新启动,指令就会从中断点继续执行。这就是为什么我说这几乎是一个原子操作:对于调用进程来说,它看起来是原子的,但它可以被更高优先级的事件中断。然后,当前处理状态将保存到正在运行的进程的内存和寄存器中,这使多个任务可以同时进行加密,而不会有混合数据的风险。同样,这与使用 movs 指令复制内存块的情况类似。

速度有多快?

根据 VIA 的说法,1.2GHz 处理器上的最大吞吐量超过 15Gb/s,几乎为 1.9GB/s。我运行的基准测试证实,这样的速度可以在实际应用中实现,而不仅仅是在 VIA 的营销文件中,这绝对是一个惊喜。

实际的加密速度取决于两个因素:密码模式和数据对齐。ECB 是最快的,而使用最广泛的 CBC 模式的运行速度约为 ECB 速度的一半。PadLock 要求数据在 16 字节边界上对齐,因此未对齐的数据必须首先移动到正确的地址,这需要一些时间。在某些情况下,Esther CPU 可以自动重新对齐数据,但这仍然会导致一些速度减慢。

表 1 显示了我测试中的一些数字。VIA Nehemiah 1.2GHz 的 OpenSSL 基准测试产生了以下结果,单位为 kB/s。

表 1. VIA Nehemiah 1.2GHz 的 Open SSL 基准测试,单位为 kB/s

类型16 字节64 字节256 字节1,024 字节8,192 字节
aes-128-ecb(软件)11,274.5314,327.7914,608.6414,672.5514,693.72
aes-128-ecb(PadLock)66,892.82346,583.52910,704.211,489,932.591,832,151.72
aes-128-cbc(软件)8,276.2712,915.7513,264.1313,313.0213,322.92
aes-128-cbc(PadLock)48,542.30241,898.79523,706.28745,157.61846,402.90

块越大,结果越好,因为 OpenSSL 库本身的开销被消除。在 ECB 模式下加密 8kB 块的速度约为 1.7GB/s;在 CBC 模式下,我们得到约 800MB/s。与软件加密相比,在同一处理器上,ECB 模式下的 PadLock 快 120 倍,CBC 模式快 60 倍。

由于这种加速,100Mbps 网络上的 IPsec 以接近全速运行,大约为 11MB/s。在加密文件系统上也可以看到类似的速度提升。《Bonnie 基准测试》在 UDMA100 模式下的 Seagate Barracuda 上运行,产生 61,543kB/s 的明文吞吐量;使用 PadLock,吞吐量为 49,961kB/s,而纯软件加密的吞吐量仅为 10,005kB/s。换句话说,PadLock 仅比非加密运行慢约 20%,而纯软件则慢了几乎 85%。有关更多详细信息和更多数字的链接,请参阅“资源”部分中我的基准测试页面。

Linux 支持

到目前为止,我仅针对 xcrypt 指令提供的 AES 算法开发了 Linux 支持,因为我尚未使用 Esther CPU。一旦我获得了新的处理器,我将酌情添加对其他算法的支持。

内核

当内核需要 AES 算法时,默认情况下它会加载 aes.ko 模块,该模块提供其软件实现。要使用 PadLock for AES,您必须加载 padlock.ko 模块。您可以通过 modprobe 手动执行此操作,也可以通过在 /etc/modprobe.conf 中添加一行来完成

alias aes padlock

现在,每次内核需要 AES 时,它也会自动加载 padlock.ko。

补丁适用于内核版本 2.6.5 及更高版本;请参阅“资源”中的 PadLock in Linux 主页。此外,基本驱动程序将在 vanilla 2.6.11 内核中提供,无需任何补丁。

OpenSSL

我们当中那些勇敢到足以使用最新 CVS 版本的 OpenSSL 的人已经获得了 PadLock 支持。OpenSSL 0.9.7 的用户必须修补和重建库,或者他们可以使用在其软件包中已包含补丁的 Linux 发行版,例如 SuSE Linux 9.2。

要查看您的 OpenSSL 构建是否具有 PadLock 支持,请运行此简单命令

$ openssl engine padlock
(padlock) VIA PadLock (RNG, ACE)

如果看到的不是 (RNG, ACE) 而是 (no-RNG, no-ACE),则表示您的 OpenSSL 安装已准备好 PadLock,但您的处理器不是。您也可能会看到一个难看的错误消息,提示没有这样的引擎。在这种情况下,您应该升级或修补您的 OpenSSL 库。

对于使用 OpenSSL 来满足其密码学需求的程序,要享受 PadLock 支持,它们必须使用所谓的 EVP_interface 并在其运行开始时初始化硬件加速器支持

#include <openssl/engine.h>
int main ()
{
    [...]
    ENGINE_load_builtin_engines();
    ENGINE_register_all_complete();
    [...]
}

有关详细信息,请参阅 OpenSSL 文档中的 evp(3) 手册页。

例如,在 SUSE Linux 9.2 中,OpenSSH 有类似的补丁,可让您体验更快的网络 scp 传输。

Binutils

要在您自己的程序中使用 PadLock,您可以按名称调用指令(例如,xcryptcbc),或者直接写入其十六进制形式

.byte 0xf3,0x0f,0xa7,0xd0

为了向后兼容旧的开发工具,使用操作码形式更安全。但是,Binutils 2.15 及更高版本已经理解适当的符号名称,例如,在 gas(GNU 汇编器)或 objdump 程序中。负责指令级操作的 binutils 的 BFD 库也用于 GNU 调试器 gdb 中。加密函数的示例指令转储可能像这样简单

(gdb) x/3i $pc
0x8048392 <demo1+14>:    lea    0x80495f0,%edx
0x8048398 <demo1+20>:    repz xcryptecb
0x804839c <demo1+24>:    push   %eax

您可能已经猜到,SUSE Linux 9.2 在所有相应的软件包中都包含 PadLock 补丁,您可以开箱即用地享受 PadLock 支持。如果您的发行版没有这些补丁,请查看“资源”中我的 Linux PadLock 主页以获取可用的补丁。

PadLock 编程

在以下各节中,我将描述一些 PadLock 编程指南,包括 xcryptcbc 的详细信息。我还将解释如何设置 PadLock 以使用 AES 算法和 128 位密钥长度在 CBC 模式下加密数据缓冲区。xcrypt 组的所有其他指令都以完全相同的方式使用。其他 PadLock 函数也适用类似的规则。

xcryptcbc

xcryptcbc 没有任何显式操作数。相反,每个寄存器都有一个给定的固定功能

  • ESI—源地址。

  • EDI—目标地址。

  • EAX—初始化向量地址。

  • EBX—密码密钥地址。

  • ECX—要处理的块数。

  • EDX—控制字地址。

除非另有说明,否则所有地址都必须在 16 字节边界上对齐。

ESI/EDI—源/目标数据的地址

源地址和目标地址可以是相同的,因此可以就地加密。目标缓冲区的大小必须至少与源缓冲区的大小相同。两者都必须是块大小(16 字节)的倍数。在某些情况下,Esther CPU 允许处理未对齐的缓冲区,但操作速度较慢。

EAX—初始化向量地址

初始化向量 (IV) 是加密结果所依赖的参数之一。IV 的大小与块大小相同,为 16 字节。有关初始化向量的详细信息,请查阅相关文献。

EBX—密码密钥地址

密码密钥可以具有以下大小之一:128、192 或 256 位。AES 算法在内部使用所谓的扩展密钥,该密钥是从给定的密码密钥派生的。对于 128 位密钥,扩展密钥可以由 PadLock 计算。对于更长的密钥,您必须自己计算。

ECX—要处理的块数

xcrypt 指令始终与 rep 前缀一起使用,这使其能够重复执行,除非 ECX 寄存器为零。ECX 中的值在每个块加密或解密后递减。

EDX—控制字地址

为了让 PadLock 确切地知道如何处理数据,我们必须用以下项填充一个名为控制字的结构

  • 算法—您只能选择 AES。

  • 密钥大小—支持的大小之一。

  • 加密/解密—方向:加密或解密。

  • 密钥生成—我们是否准备了扩展密钥,还是应该由 PadLock 自己计算?

  • 轮数—算法的内部值;请参阅本文后面的解释和 PadLock 文档。

在 C 语言中,我们可以使用联合来为结构分配适当的空间,并使用位字段来轻松描述和访问其项目

union cword {
    uint8_t cword[16];
    struct {
        int rounds:4;
        int algo:3;
        int keygen:1;
        int interm:1;
        int encdec:1;
        int ksize:2;
    } b;
};
汇编器示例

现在我们了解了所有理论,是时候看一个真实的例子了。首先,这里有一些纯汇编器代码行

.comm   iv,16,16
.comm   key,16,16
.comm   data,16,16
.comm   cword,16,16

.text
cryptcbc:
    movl    $data, %esi  #; Source address
    movl    %esi, %edi   #; Destination
    movl    $iv, %eax    #; IV
    movl    $key, %ebx   #; Cipher key
    movl    $cword, %edx #; Control word
    movl    $1, %ecx     #; Block count
    rep xcryptcbc
    ret

这段代码使用密码密钥和初始化向量加密一个数据块,遵循控制字 cword 中设置的参数。请注意,我们对源数据和目标数据使用相同的地址,因此我们进行就地加密。因为字段数据的大小仅为一个块,所以我们将 ECX 寄存器设置为 1。

C 语言示例

要在 C 程序中直接使用 PadLock,我们可以将 PadLock 例程写入单独的汇编器源文件,然后编译为独立的模块,最后链接到我们的二进制文件。但是,使用 GCC 内联汇编器并将指令直接写入 C 代码通常更容易。有关内联汇编器的教程链接,请参阅“资源”部分。

static inline void *
padlock_xcryptcbc(char *input, char *output,
    void *key, void *iv, void *control_word,
    int count)
{
    asm volatile ("xcryptcbc"
       : "+S"(input), "+D"(output), "+a"(iv)
       : "c"(count), "d"(control_word), "b"(key));
    return iv;
}

此代码指示编译器将给定的输入值、计数和其他参数加载到相应的寄存器中。然后指示它发出 xcryptcbc 指令,最后,返回 EAX 寄存器中找到的值作为指向新初始化向量的指针。

要在此处获得成功,我们还必须正确填写控制字结构。首先,最好清除联合以避免使用内存中可能存在的任何无关值

memset(&cword, 0, sizeof(cword));

现在让我们逐个填写字段。列表中的第一个是轮数。此项指定应使用输入块运行 AES 处理的次数,每轮使用扩展密钥的唯一部分。为了符合 FIPS AES 标准,对于 128 位密钥使用 10 轮,对于 192 位密钥使用 12 轮,对于 256 位密钥使用 14 轮。如果 key_size 变量包含密码密钥的长度(以字节为单位),这就是我们获取轮数值的方式

cword.b.rounds = 10 + (key_size - 16) / 4;

下一个字段是 algo。保留此字段是为了让您选择未来的加密算法而不是 AES,尽管目前 AES 是唯一的选择。因此,此处保留为零。

如果我们自己准备扩展密钥,则必须将 keygen 字段设置为 1。零表示应该由 PadLock 生成它,但这仅适用于 128 位密钥

cword.b.keygen = (key_size > 16);

interm 项启用在每轮算法运行后存储中间结果。我怀疑 CPU 架构师使用此字段来调试他们的核心,并且我不认为在程序中设置此字段有什么意义。

加密与解密通过位 encdec 区分。零是加密;一是解密。

最后,我们必须在 ksize 的两位中设置密钥大小

cword.b.ksize = (key_size - 16) / 8;

就是这样。使用此准备好的控制字结构和正确对齐的缓冲区,我们可以调用 padlock_xcryptcbc()。如果电子站在我们这边,很快我们就会收到加密的数据。

结论

PadLock 文档可在 VIA 网站上公开获得;您可以在那里找到有关 PadLock 编程注意事项的更多信息。用于加密单个数据块(包括结果验证)的完整示例程序可以在我的 PadLock in Linux 主页上找到。有关其他链接,请参阅“资源”部分。

本文资源: /article/8137

Michal Ludvig (michal@logix.cz) 最近从捷克共和国的布拉格搬到世界另一端的奥克兰,在 Asterisk Ltd. 担任高级工程师。他喜欢与妻子和女儿一起探索新西兰的秘密。

加载 Disqus 评论