摘要
本规范定义了HPACK,一种用于有效表示HTTP头部字段的压缩格式,将在HTTP/2中使用。
1. 简介
在HTTP/1.1(详见[RFC7230])协议中,头部字段是没有被压缩的。随着网页增长到需要数十到数百个请求,这些请求中的冗余标头字段不必要地消耗了带宽,从而显著增加了延迟。
SPDY [SPDY]最初通过使用DEFLATE [DEFLATE]格式压缩标头字段来解决这个冗余问题,事实证明,这种格式非常有效地表示冗余头部字段。 但是,这种方法暴露了安全风险,如CRIME(Compression Ratio Info-leak Made Easy)攻击所证明的(请参阅[CRIME])。
本规范定义了HPACK,这是一种新的压缩器,它消除了多余的头部字段,将漏洞限制为已知的安全攻击,并且在受限的环境中使用有限的内存需求。第7节介绍了HPACK的潜在安全问题。
HPACK格式特意设计的简单
且不灵活
。 两种特性都降低了由于实现错误而引起的互操作性或安全性问题的风险。 没有定义可扩展机制; 只能通过定义一个完整的替换来更改格式。
1.1 总览
本规范中定义的格式将头部字段列表视为名-值
对的有序集合,其中可以包括重复的对。 名称和值被认为是八位字节的不透明序列,并且头部字段的顺序在压缩和解压缩后保持不变。
编码就是用头部字段表将头部字段映射成索引值。头部字段表可以在编解码新的头部字段时增量更新。
在编码时,头部字段有两种表示形式:字面表示或头部字段表引用表示。 因此,可以使用引用和字面值的混合来编码头字段的列表。
字面值可以直接使用或通过静态霍夫曼编码使用。
编码器负责决定将哪些头部字段作为新条目插入到头部字段表。 解码器执行对编码器指定的头部字段表的修改,从而在此过程中重建头部字段的列表。 这使解码器保持简单并可以与多种编码器互操作。
在附录C中提供了使用这些不同的机制表示头部字段的示例。
Examples illustrating the use of these different mechanisms to represent header fields are available in Appendix C
1.2 约定
1.3 术语
本规范定义了以下术语:
-
Head Field(头部字段):一个名值对。名称和值都被视为不透明的字节流。
-
Dynamic Table(动态表):动态表(请参阅第2.3.2节)是将存储的头部字段和索引值关联的表。这个表是动态的,并且特定于一个编解码上下文。
-
Static Table(静态表):静态表(请参阅第2.3.1节)是将经常出现的头部字段与索引值静态关联的表。 该表是有序的、只读的、始终可访问的,并且可以在所有编码或解码上下文之间共享。
-
Header List(头部列表):头部列表是头部字段的有序集合,这些头部字段被联合编码,并且可以包含重复的头部字段。 HTTP/2头部块中包含的头部字段的完整列表是头部列表。
-
Header Field Representation(头部字段表示):头部字段可以以编码形式表示为文字或索引(请参见第2.4节)。
-
Header Block(头部块):头部字段表示的有序列表,在解码时会产生完整的头部列表。
2. 压缩过程概述
本规范未描述编码器的特定算法。 取而代之的是,它精确地定义了解码器的工作方式,从而允许编码器产生该定义所允许的任何编码。
2.1. 头部列表排序
HPACK保留头部字段在头部列表内的顺序。 编码器必须根据其在原始头部列表中的顺序对头部块中的字段表示进行排序。 解码器必须根据其在头部中的顺序对已解码头部列表中的字段进行排序。
2.2. 编解码上下文
为了解压缩头部块,解码器只需要维护一个动态表(参见第2.3.2节)作为解码上下文。 不需要其它状态信息。
当用于双向通信时(例如在HTTP中),端点维护的编码和解码动态表是完全独立的,即请求和响应动态表是分开的。
2.3 索引表
HPACK使用两个表将头部字段与索引相关联。 静态表(请参阅第2.3.1节)是预定义的,并包含公共头部字段(其中大多数是空值)。 动态表(请参阅第2.3.2节)是动态的,编码器可以反复使用它来索引已编码头部列表中的头部字段。
这两个表被合并到一个用于定义索引值的地址空间中(请参阅第2.3.3节)。
2.3.1. 静态表
静态表由头部字段的预定义静态列表组成。其条目在附录A中有定义。
2.3.2. 动态表
动态表中的第一个条目和最新条目在最低索引处,而动态表的最旧条目在最高索引处。
动态表初始时是空的。当头部块被压缩时条目会被加入到表中。
动态表可以包含重复的条目(如,有相同名称和值的条目)。因此,解码器不能将重复的条目视是错误。
编码器决定怎样更新动态表,这样可以控制动态表使用多少内存。为了限制解码器的存储需求,动态表的大小受到严格限制(请参见第4.2节)。
解码器在处理头部字段表示列表时更新动态表(请参见第3.2节)。
2.3.3. 索引地址空间
静态表和动态表被组合到单个索引地址空间中。
从1到静态表的长度(包括在内)之间的索引存放静态表的元素(请参阅第2.3.1节)。
严格大于静态表长度的索引存放动态表的元素(请参见第2.3.2节)。 用索引地址空间的索引值减去静态表的长度即为动态表的索引。
严格大于两个表的长度之和的索引必须视为解码错误。
对于长度为s的静态表和长度为k的动态表,下图显示了整个有效的地址空间。
<---------- Index Address Space ---------->
<-- Static Table --> <-- Dynamic Table -->
+---+-----------+---+ +---+-----------+---+
| 1 | ... | s | |s+1| ... |s+k|
+---+-----------+---+ +---+-----------+---+
^ |
| V
Insertion Point Dropping Point
图1:索引地址空间
2.4. 头部字段表示
已编码的头部字段可以表示为索引或文字。
索引表示是将标头字段定义为对静态表或动态表中条目的引用(请参见第6.1节)。
文字表示形式通过指定其名称和值来定义头部字段。头部字段名称可以用字面形式表示,也可以作为对静态表或动态表中条目的引用。 头部字段值只能按文字表示
。
定义了三种不同的文字表示形式:
- 在动态表的开头添加头部字段作为新条目的文字表示形式(请参见第6.2.1节)。
- 不添加任何头部字段到动态表的文字表示形式。
- 不将头部字段添加到动态表的文字表示形式,另外规定该头部字段始终使用文字表示形式,尤其是在由中间程序重新编码时(请参阅第6.2.3节)。 此表示旨在保护头字段值,这些头部字段值通过压缩不会受到威胁(有关更多详细信息,请参见第7.1.3节)。
为了保护敏感的头部字段值(请参阅第7.1节),可以从安全考虑出发选择这些文字表示形式之一。
头部字段的名或值的文字表示可以直接或使用静态霍夫曼代码(参见5.2节)对字节流进行编码。
3. 头部块解码
3.1. 头部块处理
解码器顺序处理头部块以重建原始头部列表。
头部块是头部字段表示形式的串联。 第6节中介绍了不同可能的头部字段表示形式。
一旦头部字段被解码并添加到重建的头列表中,就不能删除该头部字段。 添加到头部列表的头部字段可以安全地传递到应用程序。
通过将结果头字段传递给应用程序,除了动态表所需的内存外,还可以使用最少的临时内存投入来实现解码器。
3.2. 头部字段表示处理
在本节中定义了对头部块进行处理以获得头部列表的过程。为了确保解码将成功产生报头列表,解码器必须遵守以下规则。
头部块中包含的所有头部字段表示形式将按照它们出现的顺序进行处理,如下所示。有关各种头部字段表示形式的格式的详细信息以及一些其他处理指令,请参见第6节。
索引表示需要执行以下操作:
- 与静态表或动态表中被引用条目相对应的头部字段被附加到解码的头部列表中。
未添加到动态表的文字表示形式需要执行以下操作:
- 头部字段附加到解码的头部列表中。
已添加到动态表的文字表示形式需要执行以下操作:
- 头部字段附加到解码的头部列表中。
- 将头部字段插入到动态表的表头。 这种插入可能导致动态表中的先前条目被删除(请参见第4.4节)。
4. 动态表管理
为了限制解码器端的存储要求,动态表的大小受到限制。
4.1. 计算表大小
动态表的大小是其条目大小的总和。
条目的大小是其名称的长度(以八位字节为单位)(如5.2节中所定义),值的长度(以八位字节为单位)和32的总和。
条目的大小是使用其名称和值没有经过霍夫曼编码的长度来计算的。
注意:额外的32个八位位组说明了与条目相关的估计开销。 例如,使用两个64位指针来引用条目的名称和值以及使用两个64位整数来计算对该名称和值的引用次数的条目结构将具有32个八位位组的开销。
4.2. 最大表大小
编码器能允许使用的动态表的最大大小由使用HPACK的协议决定。在HTTP/2协议中,这个值由SETTINGS_HEADER_TABLE_SIZE
设置确定(请参见[HTTP2]的6.5.2节)。
编码器可以选择使用小于此最大大小的容量(请参阅第6.3节),但是所选大小必须保持小于或等于协议设置的最大容量。
动态表最大大小的变化通过动态表大小更新来发出信号(请参见第6.3节)。 动态表大小更新必须在更改动态表大小之后的第一个头部块的开头进行。 在HTTP / 2中,这遵循设置确认(请参阅[HTTP2]的6.5.3节)。
在传输两个头部块之间,可能会发生最大表大小的多次更新。 动态表大小在此间隔中更改一次以上的情况下,必须在动态表大小更新中用信号通知在该间隔中出现的最小最大表大小。始终会发出最终最大大小的信号,从而导致最多两个动态表大小更新。 这样可确保解码器能够基于动态表大小的减小执行逐出(请参见第4.3节)。
通过将最大大小设置为0,可以使用此机制从动态表中完全清除条目,然后可以将其恢复。
4.3. 动态表大小更改时条目移出
只要减小了动态表的最大大小,就会从动态表的末尾移出条目,直到动态表的大小小于或等于最大大小为止。
4.4. 动态表增加新条目时旧条目移出
在将新条目添加到动态表之前,将从动态表的末尾移出条目,直到动态表的大小小于或等于(最大大小-新条目大小)或直到表为空。
如果新条目的大小小于或等于最大大小,则会将该条目添加到表中。尝试添加大于动态表最大大小的条目不是错误; 尝试添加大于动态表最大大小的条目会导致该表清空所有现有条目,并导致表为空。
在添加新条目到动态表时,新条目可以引用动态表中即将被移除的条目的名称。请注意,如果将引用的条目从动态表移除先于插入新条目,应避免删除这个引用名称。
5. 基本类型表示
HPACK编码使用两种原始类型:无符号的可变长度整数和字符串。
5.1. 整型表示
整数用于表示名称索引,头部字段索引或字符串长度。 整数表示可以在八位字节内的任何位置开始。 为了优化处理,整数表示总是在八位字节的末尾结束。
整数分为两部分:填充当前八位位组的前缀和可选的八位位组列表,如果整数值不适合该前缀,则使用这些八位字节。 前缀的位数(称为N)是整数表示的参数。
如果整数值足够小,即严格小于 2 N − 1 2^N-1 2N−1,则将其编码在N位前缀中。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| ? | ? | ? | Value |
+---+---+---+---+---+---+---+---+
图2:带前缀的整型值编码(对于N = 5显示)
否则,将前缀的所有位设置为1,并使用一个或多个八位字节的列表对减少了 2 N − 1 2^N-1 2N−1的值进行编码。 每个八位字节的最高有效位用作连续标志:除了列表中的最后一个八位位组,其值均设置为1。 八位位组的其余位用于对减小的值进行编码。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| ? | ? | ? | 1 1 1 1 1 |
+---+---+---+-------------------+
| 1 | Value-(2^N-1) LSB |
+---+---------------------------+
...
+---+---------------------------+
| 0 | Value-(2^N-1) MSB |
+---+---------------------------+
图3:前缀后编码的整数值(对于N = 5显示)
从八位字节列表中解码整数值是通过反转八位字节在列表中的顺序开始的。 然后,对于每个八位字节,将其最高有效位删除。 八位位组的其余位被连接起来,结果值增加 2 N − 1 2^N-1 2N−1以获得整数值。
前缀大小N始终在1到8位之间。 从八位字节边界开始的整数将具有8位前缀。
编码一个整型I的伪代码如下:
if I < 2^N - 1, encode I on N bits
else
encode (2^N - 1) on N bits
I = I - (2^N - 1)
while I >= 128
encode (I % 128 + 128) on 8 bits
I = I / 128
encode I on 8 bits
解码一个整型值伪代码如下:
decode I from the next N bits
if I < 2^N - 1, return I
else
M = 0
repeat
B = next octet
I = I + (B & 127) * 2^M
M = M + 7
while B & 128 == 128
return I
附录C.1中提供了说明整数编码的示例。
此整数表示形式允许使用不确定大小的值。 编码器也可能发送大量的零值,这可能浪费八位字节,并可能使整数值溢出。 超出实现限制(值或八位字节长度)的整数编码必须视为解码错误。 根据实现约束,可以为整数的每种不同用途设置不同的限制。
5.2. 字符串文字表示
头部字段的名称和值可以用字符串文字表示。可以通过直接编码字符串文字的八位字节或使用霍夫曼代码(请参见[HUFFMAN])将字符串文字编码为八位字节序列。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| H | String Length (7+) |
+---+---------------------------+
| String Data (Length octets) |
+-------------------------------+
图4:字符串文字表示
字符串文字表示包含以下字段:
- H:一位标志H,指示该字符串的8位字节是否经过霍夫曼编码。
- 字符串长度:表示用于编码字符串文字的字符数,编码为带有7位前缀的整数(请参阅第5.1节)。
- 字符串数据:字符串文字编码后的数据。如果H标志为0,编码后的数据就是原字符串文字。如果H标志为1,编码后的数据是字符串文字经过霍夫曼编码处理后的数据。
使用霍夫曼编码的字符串文字使用附录B中定义的霍夫曼代码进行编码(有关示例,请参见附录C.4中的示例以及附录C.6中的响应示例)。 编码的数据是与字符串文字的每个八位字节相对应的代码的按位串联。
由于霍夫曼编码的数据并不总是在八位字节边界处结束,因此在其后插入填充,直到下一个八位字节边界。 为避免将此填充误解为字符串文字的一部分,使用了与EOS(字符串结尾)符号相对应的代码的最高有效位。
在解码时,编码数据末尾的不完整代码将被视为填充和丢弃。 严格长于7位的填充必须被视为解码错误。 与EOS符号的代码的最高有效位不对应的填充必须被视为解码错误。 包含EOS符号的霍夫曼编码的字符串文字必须被视为解码错误。
6. 二进制格式
本节描述每种不同的头部字段表示的详细格式以及动态表大小更新指令。
6.1. 索引头部字段表示
索引头字段表示可标识静态表或动态表中的条目(请参见第2.3节)。
索引头部字段表示会将头部字段添加到解码头部列表中,如第 3.2 节所述。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 1 | Index (7+) |
+---+---------------------------+
图5:索引头部字段
索引头部字段第1位为1,后面为匹配的头部字段的索引值,表示为带有7位前缀的整数(请参阅第5.1节)。
不会使用为0的索引值。如果发现有索引头部字段表示中发现值为0,则必须将其视为解码错误。
6.2. 文字头部字段表示
文字头部字段表示包含头部字段值。头部字段名称以文字形式提供,或通过引用静态表或动态表中的现有表条目提供(请参见第2.3节)。
本规范定义了三种文字头部字段表示形式:带索引、不带索引和从不索引。
6.2.1. 带增量索引的文字头部字段
带增量索引的文字头部字段表示会将头部字段追加到解码头部列表中,并把该头部字段做为一个新条目插入到动态表中。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 1 | Index (6+) |
+---+---+-----------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
图6:带增量索引的文字头部字段–索引名称
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 1 | 0 |
+---+---+-----------------------+
| H | Name Length (7+) |
+---+---------------------------+
| Name String (Length octets) |
+---+---------------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
图7:带增量索引的文字头部字段–新名称
带增量索引的文字头部字段表示的开始两位为01
。
如果头部字段名称与存储在静态表或动态表的条目匹配,那么这个头部字段名称可以用这个条目的索引表示。在这种情况下,条目的索引表示为带有6位前缀的整数(请参阅第5.1节)。 此值始终为非零。
反之,如果头部字段名称与存储在静态表和动态表的条目都不匹配,那么这个头部字段名称用字符串文字表示(请参见5.2节)。使用值0代替6位索引,后跟头部字段名称。
两种形式的头部字段名称表示均以表示为字符串文字的头字段值形式(参见5.2节)。
6.2.2. 不带索引的文字头部字段
不带索引的文字头部字段会导致在不改变动态表的情况下将头部字段附加到解码后的头部列表中。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 0 | Index (4+) |
+---+---+-----------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
图8:不带索引的文字头部字段–索引名称
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 0 | 0 |
+---+---+-----------------------+
| H | Name Length (7+) |
+---+---------------------------+
| Name String (Length octets) |
+---+---------------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
图9:不带索引的文字头部字段–新名称
不带索引的文字头部字段表示开始4位为0000
。
如果头部字段名称与存储在静态表或动态表的条目匹配,那么这个头部字段名称可以用这个条目的索引表示。在这种情况下,条目的索引表示为带有4位前缀的整数(请参阅第5.1节)。 此值始终为非零。
反之,如果头部字段名称与存储在静态表和动态表的条目都不匹配,那么这个头部字段名称用字符串文字表示(请参见5.2节)。使用值0代替4位索引,后跟头部字段名称。
两种形式的头部字段名称表示均以表示为字符串文字的头字段值形式(参见5.2节)。
6.2.3. 从不索引的文字头部字段
从不索引的文字头部字段会导致在不改变动态表的情况下将头部字段附加到解码后的头部列表中。中介必须使用相同的表示形式来编码该头字段。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 1 | Index (4+) |
+---+---+-----------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
图10:从不索引的文字头部字段–索引名称
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 1 | 0 |
+---+---+-----------------------+
| H | Name Length (7+) |
+---+---------------------------+
| Name String (Length octets) |
+---+---------------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
图11:从不索引的文字头部字段–新名称
从不索引的文字头部字段表示开始4位为0001
。
当头部字段表示为从未索引的文字头字段时,务必始终使用此特定文字表示进行编码。 特别地,当一个对等体发送它接收到的表示为从未索引的文字头部字段时,它必须使用相同的表示来转发该头部字段。
此表示旨在保护标头部字段值,通过压缩来保护它们不会被置于风险之中(有关更多详细信息,请参见第7.1节)。
该表示形式的编码与不带索引的文字标头字段相同(请参见第6.2.2节)。
6.3. 动态表大小更新
动态表大小更新信号用于改变动态表的大小。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 1 | Max size (5+) |
+---+---------------------------+
图12:动态表最大大小改变
动态表大小更新以3位模式001
开始,后面跟着新的最大大小值,该大小以5位前缀的整数表示(请参见5.1节)。
新的最大大小必须小于或等于协议使用HPACK确定的限制。 超过此限制的值必须视为解码错误。 在HTTP/2中,此限制是从解码器接收并由编码器确认的SETTINGS_HEADER_TABLE_SIZE
参数的最后一个值(请参见[HTTP2]的6.5.2节)(请参见[HTTP2]的6.5.3节)。
减小动态表的最大大小会导致已存在条目删除(请参见第4.3节)。
7. 安全考虑
本节介绍了HPACK的潜在安全隐患:
- 将压缩用作基于长度的预测,以验证有关被压缩到共享压缩上下文中的加密的猜想。
- 由于耗尽解码器的处理或存储容量而导致的拒绝服务。
###7.1. 探测动态表大小
HPACK通过利用HTTP等协议固有的冗余性来减少头部字段编码的长度。 这样做的最终目的是减少发送HTTP请求或响应所需的数据量。
攻击者可以探测用于编码 header 字段的压缩上下文,攻击者也可以定义要编码和传输的 header 字段,并在编码后观察这些字段的长度。当攻击者可以同时执行这两种操作时,他们可以自适应地修改请求,以确认有关动态表状态的猜想。如果将猜想压缩到较短的长度,则攻击者可以观察编码的长度并推断出猜测是正确的。
即使通过传输层安全性(TLS)协议(请参阅 [TLS12]),这也是有可能被攻击的,因为 TLS 为内容提供加密保护,但仅提供有限的内容长度保护。
注意:填充方案仅针对具有这些功能的攻击者提供有限的保护,可能仅迫使增加猜测的次数来了解与给定猜测相关的长度。 填充方案还可以通过增加传输的位数直接对抗压缩。
诸如CRIME [CRIME]之类的攻击证明了这些常规攻击者功能的存在。 特定攻击利用了DEFLATE [DEFLATE]删除基于前缀匹配的冗余这一事实。 这使攻击者可以一次确认一个角色的猜测,从而将指数时间攻击减少为线性时间攻击。
7.1.1. 适用于HPACK和HTTP
HPACK通过强制猜测以匹配整个头部字段值而不是单个字符来缓解但不能完全阻止以CRIME [CRIME]为模型的攻击。 攻击者只能了解猜测是否正确,因此可以将其简化为针对报头字段值的蛮力猜测。
因此,恢复特定头部字段值的可行性取决于值的熵。 结果,具有高熵的值不太可能成功恢复。 但是,低熵值仍然容易受到攻击。
每当两个互不信任的实体控制放置在单个HTTP/2连接上的请求或响应时,就可能发生这种性质的攻击。 如果共享的HPACK压缩器允许一个实体向动态表添加条目,而另一实体访问这些条目,则可以了解表的状态。
当中介机构发生以下情况时,就会出现来自互不信任实体的请求或响应:
- 多个客户端通过一个连接向源服务发送请求。
- 多个源服务端通过一个连接向一个客户端返回响应。
Web浏览器还需要假设由不同Web来源[ORIGIN]在同一连接上发出的请求是由互不信任的实体发出的。
7.1.2. 减轻
要求标头字段具有机密性的HTTP用户可以使用具有足以使猜测不可行的熵的值。 但是,这是不切实际的常规解决方案,因为它会强制HTTP的所有用户采取措施缓解攻击。 这会对使用HTTP强加一些新的限制。
HPACK的实现不是在HTTP的使用上施加约束,而是可以约束压缩的应用方式,以限制动态表探测的潜力。
理想的解决方案基于构造头部字段的实体来隔离对动态表的访问。 添加到表中的头部字段值将归属于一个实体,只有创建特定值的实体才能提取该值。
为了提高此选项的压缩性能,可以将某些条目标记为公共。例如,Web浏览器可以使头部字段Accept-Encoding
的值对所有请求可用。
不太了解头部字段出处的编码器可能会对具有许多不同值的头部字段引入惩罚,以至于大量的尝试去猜测头部字段值将导致未来的消息中的头部字段不再与动态表中的表项进行比较,有效地防止了进一步的猜测。
注意:如果攻击者具有可靠的方式来重新安装值,则从动态表中删除与标头字段相对应的条目可能无效。 例如,在网络浏览器中加载图像的请求通常包括Cookie标头字段(此类攻击的潜在价值很高的目标),网站可以轻松地强制加载图像,从而刷新 动态表。
可以使此响应与标头字段值的长度成反比。 与较短的值相比,较短的值可能更快或更具有更高的概率,将标头字段标记为不再使用动态表。
7.1.3. 从不索引的文字
也可以选择不对敏感头字段进行压缩,而是将其值编码为文字,从而保护它们。
只有在所有跃点都不进行压缩的情况下,拒绝生成头部字段的索引表示才有效。 永不索引的文字(请参阅第6.2.3节)可用于向中介发出信号,指示有意将特定值作为文字发送。
中介不能将永不索引文字表示的值重新编码成另一个索引表示。 如果使用HPACK进行重新编码,则必须使用永不索引的文字表示。
选择为一个头部字段使用永不索引表示取决于很多因素。由于HPACK无法防止猜测整个标头字段值,因此对手更容易恢复短或低熵值。 因此,编码器可能选择不索引具有低熵的值。
编码器还可能选择不为被认为具有很高价值或对恢复敏感的头部字段的值编制索引,如Cookie或授权头部字段。
相反,如果公开,则编码器可能更喜欢索引值很小或没有值的标头字段的索引值。 例如,User-Agent标头字段在请求之间通常不会发生变化,而是发送到任何服务器。 在这种情况下,确认已使用特定的User-Agent值几乎没有价值。
注意,随着发现新的攻击,这些决定使用永不索引的表示的标准将随着时间的推移而发展。
7.2. 静态霍夫曼编码
当前没有针对静态霍夫曼编码的攻击。 一项研究表明,使用静态霍夫曼编码表会造成信息泄漏。 但是,同一项研究得出的结论是,攻击者无法利用此信息泄漏来恢复任何有意义的信息量(请参阅[PETAL])。
7.3. 内存消耗
攻击者可以尝试使终端耗尽其内存。 HPACK旨在限制终端分配的内存的峰值和状态数量。
压缩程序使用的内存量由使用HPACK的协议通过定义动态表的最大大小来限制。在HTTP/2中,此值由解码器通过设置参数SETTINGS_HEADER_TABLE_SIZE
控制(请参见[HTTP2]的6.5.2节)。此限制既考虑了动态表中存储的数据大小,又考虑了少量的开销。
解码器可以通过为动态表的最大大小设置适当的值来限制所使用的状态存储器的数量。 在HTTP/2中,这是通过为SETTINGS_HEADER_TABLE_SIZE
参数设置适当的值来实现的。 编码器可以通过发信号通知动态表大小小于解码器允许的状态来限制其使用的状态存储器的数量(请参见6.3节)。
可以通过顺序处理头字段来限制编码器或解码器消耗的临时内存量。 实现不需要保留头部字段的完整列表。 但是请注意,由于其他原因,应用程序可能有必要保留完整的头部列表。 即使HPACK不会强迫这种情况发生,应用程序约束也可能使之有必要。
7.4. 实现限制
HPACK的实现需要确保整数的大值,整数的长编码或长字符串文字不会造成安全漏洞。
一个实现必须为它接受的整数值和编码长度设置一个限制(请参阅第5.1节)。 以同样的方式,它必须为它接受的字符串文字长度设置一个限制(参见5.2节)。