1. 项目概述:TLS 1.2密钥派生的硬件加速之路
在构建现代安全通信链路时,TLS协议是基石。握手过程中最核心、最耗时的环节之一,就是从短暂的预主密钥(premaster_secret)派生出用于实际数据加密和完整性校验的一整套会话密钥材料。这个过程依赖于一个称为伪随机函数(PseudoRandom Function, PRF)的密码学组件。在软件中实现PRF,尤其是面对高并发、低延迟的服务器场景,会消耗可观的CPU资源。因此,将这部分计算卸载到专用的硬件安全引擎(Security Engine, SEC),成为提升系统整体性能与安全性的关键。NXP的QorIQ LS1046A处理器集成的SEC模块,正是为此而生。它不仅仅是一个协处理器,更是一个理解TLS协议语义的“智能”加速器,能够高效、安全地完成从预主密钥到最终密钥材料的完整派生流程。本文将深入剖析SEC对TLS 1.2 PRF的硬件实现机制,从算法原理到硬件指令集,从数据块格式到安全考量,为你呈现一套完整的硬件加速密钥派生方案。
2. TLS 1.2 PRF算法原理与SEC的硬件映射
2.1 PRF算法核心:基于HMAC的迭代扩展
TLS 1.2的PRF定义在RFC 5246中,其核心思想是使用HMAC(基于哈希的消息认证码)作为密码学原语,从一个秘密种子(secret)和标签(label)、种子(seed)等输入材料中,确定性地生成任意长度的伪随机字节流。
算法可以概括为两个阶段:
- P_hash 函数:这是PRF的基础。给定一个秘密
secret、一个种子seed和所需的输出长度,P_hash通过迭代HMAC计算来生成输出。- 首先,计算
A(1) = HMAC_hash(secret, A(0) = seed)。 - 然后,计算
A(2) = HMAC_hash(secret, A(1)),以此类推,生成序列A(i)。 - 对于每个
A(i)(i>=1),计算HMAC_hash(secret, A(i) + seed),并将其输出依次拼接,直到达到所需的字节数。
- 首先,计算
- TLS PRF:TLS 1.2的PRF实际上是两个P_hash的拼接,分别使用SHA-256和SHA-384哈希算法,然后将它们的输出进行异或(XOR)。这种设计提供了额外的安全冗余。具体公式为:
PRF(secret, label, seed) = P_SHA256(secret, label + seed) XOR P_SHA384(secret, label + seed)。
在SEC的硬件实现中,它巧妙地封装了这个过程。硬件并不需要软件预先计算好A(i)序列,而是内部状态机自动处理迭代。用户只需要提供最原始的输入:secret(即预主密钥或主密钥)、label(如“client write key”)和seed(来自握手消息的随机数),并指定输出长度和目的地,SEC就能自动完成整个PRF计算。
2.2 SEC硬件实现的优化与约束
SEC的硬件设计对算法进行了深度优化,同时也引入了一些必须了解的约束:
输入预处理:协议标准对
premaster_secret的长度没有硬性限制,但SEC硬件有物理限制。它不支持长度超过512字节的预主密钥。此外,HMAC算法的密钥(即这里的secret)也有长度限制:对于大多数HMAC算法(如HMAC-SHA256),最大为64字节;对于HMAC-SHA384/512,最大为128字节。如果提供的secret超过这个长度,SEC会在内部自动执行预处理——使用指定的哈希算法对过长的secret进行哈希,将哈希结果作为新的、长度合规的secret用于后续PRF计算。这个过程对用户透明,但开发者需要知晓此限制,避免提供超长的密钥材料导致性能非预期下降(因为多了一次哈希运算)。输入拼接的持久化:在PRF计算中,
label和seed需要拼接成一个连续的输入字符串。SEC在硬件层面实现了这一优化:它将所有标记为label和seed的输入材料在计算开始前就拼接成一个持久的内部字符串。在整个P_hash迭代计算过程中,这个拼接后的字符串被反复使用,避免了软件实现中每次迭代都需要进行内存拷贝或拼接的开销,显著提升了效率。密钥材料的定向分发:这是SEC硬件PRF最强大的特性之一。它不仅仅是生成一堆随机字节,而是理解TLS协议中密钥材料的具体用途。SEC的PRF函数被设计成可以将生成的字节流按需、安全地分发到最多8个不同的“目的地”。每个目的地可以独立配置其安全属性。例如,在典型的TLS密钥派生中,PRF输出会被分割为:
- 客户端写MAC密钥(Client-write MAC secret)
- 服务器写MAC密钥(Server-write MAC secret)
- 客户端写加密密钥(Client-write key)
- 服务器写加密密钥(Server-write key)
- 客户端写IV(Client-write IV)
- 服务器写IV(Server-write IV) SEC允许在单个操作中,指定每个目标输出的长度,并决定其是否为“敏感密钥材料”。对于敏感材料(如MAC密钥和加密密钥),SEC可以启用输出加密功能,使用一个密钥加密密钥(Key Encryption Key, KEK)对其在离开安全边界前进行加密保护。而对于非敏感数据(如IV),则可以明文输出。这种“生成即保护”的机制,极大地增强了整个密钥生命周期的安全性。
算法套件感知:SEC的硬件逻辑与协议深度结合。例如,当选择的加密套件是AES-GCM时,由于GCM模式使用认证加密,不再需要独立的MAC运算,因此也就没有MAC密钥。SEC能够识别这一情况,并自动跳过为“client-write MAC secret”和“server-write MAC secret”分配输出长度的步骤,无论协议数据块(PDB)中为这两个字段指定了何种长度。这种智能行为减少了软件配置的复杂性,并避免了潜在的错误。
3. TLS 1.2 PRF协议数据块(PDB)详解
要驱动SEC执行PRF操作,软件需要构建一个称为协议数据块(Protocol Data Block, PDB)的命令结构。这个PDB包含了所有必要的控制信息和数据指针。理解PDB的每个字段,是正确使用硬件加速器的关键。
3.1 PDB整体结构与选项字节(Options Byte)
TLS 1.2 PRF的PDB格式相对复杂,其核心控制部分是一个“选项字节”(Options Byte),它位于PDB的特定偏移位置。这个字节的每一位都控制着关键的安全和处理行为。
表 3-1: TLS 1.2 PRF PDB 选项字节格式与描述
| 位 | 名称 | 描述 | 详解与配置建议 |
|---|---|---|---|
| 7-4 | Reserved | 保留位 | 必须设置为0。 |
| 3 | IEKT(Input Encryption Key Type) | 输入加密密钥类型 | 此字段仅当输入的主密钥(master_secret)被加密时才有效。它定义了用于解密输入密钥的算法。 • 0: 使用AES-ECB-256算法解密。• 1: 使用AES-CCM-256算法解密。注意:如果输入是明文( IEOV=1),此字段被忽略。AES-CCM模式能同时提供加密和完整性校验,安全性更高,但计算开销略大于ECB。 |
| 2 | OEKT(Output Encryption Key Type) | 输出加密密钥类型 | 此字段仅当生成的密钥材料需要加密输出时才有效。它定义了用于加密输出密钥材料的算法。 • 0: 使用AES-ECB-256算法加密。• 1: 使用AES-CCM-256算法加密。注意:是否加密由 OEOV字段及PROTINFO共同决定。同样,CCM模式是更安全的选择。 |
| 1 | IEOV(Input Encryption Override) | 输入加密覆盖 | 控制输入的主密钥(master_secret)的加密状态。 • 1:输入是明文。SEC将直接使用提供的master_secret进行PRF计算。• 0:输入是密文。SEC会先使用IEKT指定的算法和相应的KEK对其进行解密,然后再使用。这是最常见的使用场景,用于保护存储在外部内存中的主密钥。 |
| 0 | OEOV(Output Encryption Override) | 输出加密覆盖 | 控制输出的密钥材料是否加密。此字段仅在PROTINFO字段值为FFFF或FFFE时被忽略(此时加密行为由其他机制决定)。在其他情况下:• 1: 如果PROTINFO=FFFF/FFFE,生成的密钥材料不加密(明文输出)。• 0: 如果PROTINFO=FFFF/FFFE,生成的密钥材料被加密。加密算法由OEKT指定。实操提示:在典型的TLS会话建立中, PROTINFO会携带加密套件信息,因此OEOV通常有效。设置为0可以确保派生的会话密钥在离开SEC硬件时已被加密,是推荐的安全实践。 |
注意:
PROTINFO是操作命令(OPERATION Command)中的一个字段,用于指示协议类型和算法套件。当它为FFFF或FFFE时,通常表示一个“原始”的PRF计算,不关联特定TLS套件,此时输出加密行为由OEOV直接控制。当它表示一个具体的TLS套件时,SEC内部逻辑可能会覆盖OEOV的设置。
3.2 输入与输出引用(Input/Output Reference)
PDB中包含了指向输入数据和输出缓冲区的“引用”(Reference)。每个引用由两部分组成:一个指针(Pointer)和一个控制字(Control Word)。
- 指针(Pointer):指向数据在内存中的地址。其长度(32位或64位)由主配置寄存器(Master Configuration Register)的PS字段决定。这是开发驱动时需要特别注意的移植性问题。
- 控制字(Control Word):描述了所指向数据的属性和长度。
输入引用控制字的字段至关重要:
- Input Secret Length (30-21位):输入密钥(
secret)的长度,以字节为单位。关键约束:如果PRF输出被分割成多个密钥(即PROTINFO != FFFF/FFFE),那么输入的必须是标准的48字节TLS主密钥(master_secret),并且此字段被忽略,长度固定为48。 - Input Label Length (20-14位):输入标签(
label)的长度,以字节为单位。根据协议定义,合法值在11到15之间(含)。例如,“client finished”是15字节,“server write key”是14字节。 - Input Seed Part 1 Length (13-7位):输入种子第一部分(
seed)的长度,合法值为16或32字节。这通常对应客户端随机数(ClientHello.random)或服务器随机数(ServerHello.random)。 - Input Seed Part 2 Length (6-0位):输入种子第二部分(
seed)的长度,合法值为20或32字节。这通常对应另一个随机数或握手消息的哈希值。
输出引用控制字的字段:
- SGT (31位):指定指针是直接指向数据(0)还是指向一个散点/聚集表(Scatter/Gather Table, 1)。SGT允许将输出分散到多个不连续的内存块,这在复杂的内存管理场景中很有用。
- LENGTH (23-16位):数据长度(字节)。行为取决于
PROTINFO:- 如果
PROTINFO = FFFF/FFFE,此字段表示输出的主密钥或验证数据(verify_data)的长度。 - 如果
PROTINFO != FFFF/FFFE(即指定了TLS套件),此字段被忽略。输出长度由PROTINFO所指示的加密套件决定。
- 如果
3.3 配置示例与流程
假设我们要为一个TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256套件的连接派生密钥,且主密钥已加密存储在外部DDR内存中。
构建PDB:
- 设置
IEKT=0(AES-ECB-256解密输入)。 - 设置
IEOV=0(输入是密文)。 - 设置
OEKT=1(AES-CCM-256加密输出,更安全)。 - 设置
OEOV=0(加密输出密钥材料)。 - 在OPERATION命令中设置
PROTINFO为该加密套件对应的值。 - 配置输入引用:
Input Secret Length: 忽略(因为PROTINFO非特殊值,且输入是密文,长度由解密后决定,但硬件知道是48字节)。Input Label Length: 根据派生阶段设置(如扩展主密钥时是“key expansion”)。Input Seed Part 1/2 Length: 设置为客户端和服务器随机数的长度(各32字节)。
- 配置6个输出引用,分别对应
client write key,server write key,client write IV,server write IV。(注意:AES-GCM套件无MAC密钥,SEC会自动跳过对应的引用)。 - 为每个输出引用设置
LENGTH(此处被忽略,但需预留空间)、SGT,并指向准备好的输出缓冲区(或SGT)。
- 设置
驱动执行:将构建好的PDB和OPERATION命令提交给SEC的描述符控制器。SEC会依次执行:解密输入主密钥 -> 拼接label和seed -> 执行PRF迭代计算 -> 按指定长度分割输出 -> 使用KEK加密敏感密钥材料 -> 将结果写入各个输出目的地。
软件后处理:软件从输出缓冲区获取加密后的会话密钥材料。这些材料在用于加解密操作前,可能需要先加载回SEC的内部密钥寄存器(这通常由后续的对称加解密描述符自动完成,使用相同的KEK解密)。
4. 派生密钥协议(DKP)的深度解析与应用
除了PRF,SEC还提供了一个极为重要的辅助协议:派生密钥协议(Derived Key Protocol, DKP)。它的主要目的不是生成新密钥,而是对现有密钥进行格式转换,以适配硬件加速器的内部处理格式,从而获得巨大的性能提升。
4.1 DKP的核心价值:HMAC Split Key
DKP最典型的应用是生成HMAC的“分裂密钥”(Split Key),也称为IPAD/OPAD格式。在HMAC算法中,密钥K会与两个固定的填充常量(ipad和opad)进行异或,生成K_ipad和K_opad,然后才参与哈希运算。在软件实现中,这一步通常在每次HMAC计算时动态完成。
SEC的MDHA(消息摘要硬件加速器)模块在设计上可以直接接受这种预计算好的K_ipad和K_opad作为输入,从而跳过异或步骤,直接开始哈希压缩。DKP的作用,正是将原始的、协商好的HMAC密钥(Negotiated Key)一次性计算并转换成(K_ipad, K_opad)对,即“分裂密钥”。
性能优势:对于IPsec、TLS等需要处理大量小数据包认证的场景,每个数据包的HMAC计算如果都从原始密钥开始做异或,会引入不必要的开销。通过DKP预计算并存储分裂密钥,后续的每一个HMAC操作都节省了这一步,在高速网络处理中,累积的性能收益非常显著。
4.2 DKP的工作机制与描述符内替换
DKP的设计非常巧妙,它支持“就地”(in-place)替换。这意味着你可以在一个描述符(Descriptor,即SEC的指令序列)中直接完成密钥转换和后续的加密/认证操作。
工作流程如下:
- 在描述符中,你首先放置一个DKP操作命令(OPERATION Command)。
- 在该命令之后,你提供原始的、协商好的HMAC密钥(可能是明文,也可能是特定格式的密文)。
- DKP命令执行,它读取原始密钥,计算其分裂密钥形式。
- 关键步骤:DKP硬件不仅计算分裂密钥,还会动态修改你提交的描述符。它会将描述符中的DKP操作命令本身,替换成一个标准的KEY命令。同时,它将描述符中提供的原始密钥数据,替换成刚刚计算好的分裂密钥。
- 分裂密钥会被写入SEC的Class 2密钥寄存器,供紧随其后的MDHA命令(例如用于计算IPsec的AH/ESP认证数据)直接使用。
- 描述符继续执行,此时它“看到”的已经是一个标准的KEY命令和可用的分裂密钥了。
这种“描述符内替换”机制,使得软件可以编写一个统一的、包含密钥协商和数据处理流程的描述符。DKP在运行时自动完成密钥格式的适配,无需软件中断处理或额外的内存拷贝,实现了极高的流水线效率和简洁的编程模型。
警告:描述符作者必须确保,DKP操作输出的分裂密钥数据,不会覆盖描述符中后续需要保留的命令或数据。这需要仔细计算描述符的内存布局。通常的做法是在原始密钥数据后预留足够的空间(分裂密钥的长度是原始密钥的两倍,见下文表4-1),或者使用分散/聚集表(SGT)来管理输出。
4.3 DKP的输入/输出源与长度
DKP操作命令中的PROTINFO字段的I/O控制子域(bits 16-19)被分为两部分,分别指定输入源和输出目的地。
输入源(Input Source, bits 16-17):
00(IMM):原始密钥紧跟在DKP命令之后。此选项只能与输出目的地00(IMM)配对使用。01(SEQ):原始密钥位于由SEQ IN PTR命令定义的输入帧中。在可信描述符(Trusted Descriptor)中必须使用此选项。10(PTR):原始密钥的地址指针紧跟在DKP命令之后。11(SGF):原始密钥分散在由散点/聚集表指定的多个内存位置。
输出目的地(Output Destination, bits 18-19):
00(IMM):计算出的分裂密钥将写回描述符,紧挨着被替换成的KEY命令之后。这会覆盖该位置原有的数据。01(SEQ):分裂密钥写入由SEQ OUT PTR命令定义的输出帧。仅当输入源也是SEQ时才有效,且是可信描述符的必须选择。10(PTR):分裂密钥写入DKP命令后指定的地址指针处。不能与输入源IMM或SGF共用。11(SGF):分裂密钥根据DKP命令后的散点/聚集表写入内存。不能与输入源IMM或PTR共用。
分裂密钥长度:派生出的分裂密钥长度取决于底层哈希算法,是固定的,与原始密钥长度无关。
表 4-1: HMAC派生密钥(分裂密钥)长度
| 哈希算法 | 派生“分裂”密钥长度 | 对应字数(32位系统) |
|---|---|---|
| MD5 | 32 字节 | 8 字 |
| SHA-1 | 40 字节 | 10 字 |
| SHA-224 / SHA-256 | 64 字节 | 16 字 |
| SHA-384 / SHA-512 | 128 字节 | 32 字 |
例如,如果你有一个20字节的原始SHA-256 HMAC密钥,经过DKP处理后,你会得到一个64字节的数据块,其中前32字节是K_ipad,后32字节是K_opad。后续的MDHA命令在配置为HMAC-SHA256时,会期望从这个64字节的缓冲区中读取密钥。
5. 硬件加速器协同工作与安全考量
SEC不是一个单一的模块,而是由多个密码学硬件加速器(CHA)协同工作的综合体。理解PRF和DKP如何与其他CHA交互,对于设计高效安全的系统至关重要。
5.1 与MDHA和AESA的协作
- PRF & MDHA:PRF的核心是HMAC,而HMAC的核心是哈希函数(如SHA256)。SEC内部的MDHA模块就是专门执行哈希运算的硬件。当PRF命令被执行时,它会在内部调用MDHA来完成HMAC计算。这种调用对软件完全透明,但意味着PRF的性能与MDHA的性能直接相关。
- PRF/DKP & AESA:当启用输入/输出加密时(通过IEKT/OEKT配置),PRF和DKP会调用AESA(AES加速器)来执行AES-ECB或AES-CCM算法。KEK通常存储在SEC内部受保护的密钥寄存器中。这种设计确保了密钥材料在SEC芯片边界之外(即在系统内存中)始终处于加密状态,即使物理内存被窃取,攻击者也无法直接获得明文密钥。
- 密钥传递:DKP生成的Split Key或PRF生成的会话密钥,通常不会直接返回给主CPU。它们被直接写入SEC内部的Class 1或Class 2密钥寄存器。随后,当描述符中的下一个命令(如AESA用于加密,MDHA用于认证)需要密钥时,它直接从这些寄存器中读取。这实现了密钥材料在安全引擎内部的“闭环”流动,最大限度地减少了密钥在总线或内存中暴露的风险。
5.2 安全最佳实践与常见陷阱
密钥生命周期管理:
- 预主密钥:应在安全环境(如TEE)中生成,或通过PKHA(公钥硬件加速器)计算得到。进入SEC参与PRF计算前,应始终以加密形式(
IEOV=0)提供。 - 主密钥:PRF扩展出的48字节主密钥,是会话安全的根。如果不需要存储,应尽快使用并让SEC在内部销毁。如果需要存储(例如用于会话恢复),必须使用
OEKT加密后输出。 - 会话密钥:PRF派生的最终加密密钥和MAC密钥,必须设置为加密输出(
OEOV=0)。IV可以明文输出。 - KEK管理:用于加密输入/输出密钥的KEK,是更高层级的秘密。它应由安全的随机源生成,并存储在SEC的受保护密钥寄存器或硬件安全模块(HSM)中,绝不能出现在普通内存中。
- 预主密钥:应在安全环境(如TEE)中生成,或通过PKHA(公钥硬件加速器)计算得到。进入SEC参与PRF计算前,应始终以加密形式(
防侧信道攻击:
- SEC的PKHA模块在执行模幂运算等公钥操作时,支持时序均衡化(timing-equalized)模式,以抵御时序攻击。在配置ECC或RSA操作时,应优先启用此功能。
- 虽然PRF和DKP本身是确定性的,但确保其输入(随机数)的熵值足够高,是防御各类密码分析攻击的前提。
配置错误排查:
- PRF输出长度异常:检查
PROTINFO字段是否与预期的加密套件匹配。如果PROTINFO设置为FFFF但期望输出多个密钥,会导致错误,因为此时SEC期望的是单一输出(如主密钥或Finished验证数据)。 - DKP描述符崩溃:最常见的原因是分裂密钥的输出覆盖了描述符后续的指令。务必精确计算IMM输出模式下的内存偏移。使用PTR或SGF模式将输出指向独立的缓冲区是更安全的选择。
- 性能未达预期:如果PRF计算慢,检查输入的
secret长度是否超过了HMAC密钥限制(64/128字节),导致SEC内部触发了预处理哈希。尽量使用标准长度的密钥。
- PRF输出长度异常:检查
与Blob协议的结合:SEC还支持Blob协议,用于跨芯片上下电周期加密保护用户数据。虽然本文未深入展开,但它的思想与PRF的输出加密一脉相承:使用一个非易失性主密钥派生出临时密钥来加密数据。在需要持久化加密密钥或敏感配置的场景,可以将PRF输出的、经KEK加密的会话密钥,再用Blob协议封装一次后存储到Flash中,实现双层保护。
6. 总结与实战心得
将TLS 1.2的密钥派生工作卸载到NXP QorIQ SEC硬件引擎,远不止是获得计算性能的提升。它更是一种系统级的安全架构升级。通过深入理解PRF PDB的每个比特位、DKP的原地替换机制,以及各CHA间的协同,开发者能够设计出既高效又健壮的网络安全应用。
从我多年的嵌入式安全开发经验来看,成功集成此类硬件加速器,关键在于转变思维:从“调用一个库函数”转变为“编排一个安全的数据处理流水线”。你需要像设计硬件电路一样思考描述符的编排,确保密钥的生成、转换、使用、销毁都在硬件的安全边界内完成,软件只负责触发和提供必要的参数。
一个实用的建议是,在项目初期就构建一个完备的测试向量套件。不仅包括标准RFC的测试向量,还应模拟各种边界情况:超长的预主密钥(触发预处理)、使用AES-GCM套件(验证MAC密钥被跳过)、配置错误的输出长度等。硬件行为有时比软件更“固执”,清晰的文档和充分的测试是避免后期调试噩梦的最佳良药。
最后,永远不要忽视那个最简单的选项——OEOV。除非有极其特殊的调试需求,否则永远将其设置为0(加密输出)。让密钥在离开安全引擎的瞬间就被锁进“保险箱”,这是利用此类硬件所能获得的最基本、也最重要的安全收益。