Google 安全浏览 v5 预计客户端会维护本地数据库,但在客户端选择无存储空间的实时模式时除外。此本地数据库的格式和存储方式由客户端决定。从概念上讲,此本地数据库的内容可视为一个文件夹,其中包含各种列表,而这些文件的内容是 SHA256 哈希值或相应的前缀,其中四字节哈希前缀是最常用的哈希长度。
可用列表
更新:下表包含新引入的列表,这些列表遵循命名惯例,其中名称包含一个后缀,表示列表中应有的哈希长度。hashList
方法最初支持多种哈希长度,客户端可以通过指定查询参数 desiredHashLength 来请求特定的哈希长度。我们将其替换为更简洁的 API Surface,以支持单个哈希长度,而不是多个哈希长度。具有相同威胁类型但不同哈希长度的哈希列表将是单独命名的列表,并带有指示哈希长度的后缀进行限定。在此变更生效后,我们将废弃 hashList.get
和 hashLists.batchGet
方法中的 desiredHashLength 字段,因为给定列表仅支持单个哈希长度。我们预计会更新 hashList.hashListMetadata
字段以反映这项更改。
建议在 v5alpha1 中使用以下列表:
列表名称 | 相应的 v4 ThreatType 枚举 |
说明 |
---|---|---|
gc-32b |
收不到任何通知 | 此列表是全局缓存列表。这是一个特殊列表,仅在实时操作模式下使用。 |
se-4b |
SOCIAL_ENGINEERING |
此列表包含 SOCIAL_ENGINEERING 威胁类型的威胁。 |
mw-4b |
MALWARE |
此列表包含适用于桌面平台的 MALWARE 威胁类型的威胁。 |
uws-4b |
UNWANTED_SOFTWARE |
此列表包含桌面平台的 UNWANTED_SOFTWARE 威胁类型的威胁。 |
uwsa-4b |
UNWANTED_SOFTWARE |
此列表包含 Android 平台的 UNWANTED_SOFTWARE 威胁类型的威胁。 |
pha-4b |
POTENTIALLY_HARMFUL_APPLICATION |
此列表包含 Android 平台的 POTENTIALLY_HARMFUL_APPLICATION 威胁类型的威胁。 |
我们日后会提供更多列表,届时上表也会扩展。
数据库更新
客户端会定期调用 hashList.get 方法或 hashLists.batchGet 方法来更新数据库。由于典型的客户端会一次更新多个列表,因此建议使用 hashLists.batchGet 方法。
列表通过其独特的名称进行标识。这些名称是长度为几个字符的短 ASCII 字符串。
该 API 发布 GA 版后,列表名称将永远不会重命名。此外,列表一经显示便不会被移除(如果列表不再有用,它会变为空列表,但仍会保留)。因此,在 Google 安全浏览客户端代码中对这些名称进行硬编码是恰当的做法。
hashList.get 方法和 hashLists.batchGet 方法都支持增量更新。使用增量更新可节省带宽并提高性能。增量更新的工作原理是传送客户端列表版本与列表最新版本之间的差异。(如果客户端是新部署的,并且没有任何可用版本,则可以进行完整更新。)增量更新包含移除索引和添加内容。客户端应先从其本地数据库中移除指定索引位置的条目,然后再应用添加项。
最后,为防止数据损坏,客户端应根据服务器提供的校验和检查存储的数据。每当校验和不匹配时,客户端都应执行完整更新。
解码列表内容
解码哈希和哈希前缀
所有名单均采用特殊编码传送,以缩减大小。这种编码的工作原理是,从概念上讲,Google 安全浏览名单包含一组哈希值或哈希前缀,这些值在统计上与随机整数无法区分。如果我们对这些整数进行排序并取其相邻差值,那么在某种意义上,这样的相邻差值应该是“较小”的。然后,Golomb-Rice 编码会利用这一特性。
假设要使用 4 字节的哈希前缀传输三个主机-后缀路径-前缀表达式,即 a.example.com/
、b.example.com/
和 y.example.com/
。进一步假设,Rice 参数(记为 k)被选为
- 服务器首先会计算以下字符串的完整哈希值,分别为:
291bc5421f1cd54d99afcc55d166e2b9fe42447025895bf09dd41b2110a687dc a.example.com/
1d32c5084a360e58f1b87109637a6810acad97a861a7769e8f1841410d2a960c b.example.com/
f7a502e56e8b01c6dc242b35122683c9d25d07fb1f532d9853eb0ef3ff334f03 y.example.com/
然后,服务器会为上述每个内容分别构建 4 字节的哈希前缀,即 32 字节完整哈希的前 4 个字节,解读为大端字节序 32 位整数。大端序是指完整哈希的第一个字节成为 32 位整数的最高有效字节。此步骤会生成整数 0x291bc542、0x1d32c508 和 0xf7a502e5。
服务器必须按字典顺序对这三个哈希前缀进行排序(相当于大端序数字排序),排序结果为 0x1d32c508、0x291bc542、0xf7a502e5。第一个哈希前缀会以不变的形式存储在 first_value
字段中。
然后,服务器会计算两个相邻差异,分别为 0xbe9003a 和 0xce893da3。假设 k 被选为 30,服务器会将这两个数拆分为长度分别为 2 位和 30 位的商部分和余数部分。对于第一个数字,商部分为零,余数为 0xbe9003a;对于第二个数字,商部分为 3,因为最有显位的两位二进制数为 11,余数为 0xe893da3。对于给定的商 q
,它会使用恰好 1 + q
位编码为 (1 << q) - 1
;余数则直接使用 k 位编码。第一个数的分子部分编码为 0,余数部分为二进制 001011111010010000000000111010;第二个数的分子部分编码为 0111,余数部分为 001110100010010011110110100011。
将这些数字组成字节字符串时,使用的是小端字节序。从概念上讲,从最低有效位开始形成一个长位字符串可能更容易理解:我们取第一个数的分子部分,并将第一个数的余数部分附加到前面;然后,我们进一步将第二个数的分子部分附加到前面,并将余数部分附加到前面。这应该会生成以下大数字(添加了换行符和注释以便于理解):
001110100010010011110110100011 # Second number, remainder part
0111 # Second number, quotient part
001011111010010000000000111010 # First number, remainder part
0 # First number, quotient part
用一行代码编写时,代码如下所示
00111010001001001111011010001101110010111110100100000000001110100
显然,这个数字远远超出了单个字节可用的 8 位。然后,小端字节序编码会取该数字中的最低有效 8 位,并将其作为第一个字节输出,即 01110100。为方便起见,我们可以从最低有效位开始,将上述位字符串分组为 8 位一组:
0 01110100 01001001 11101101 00011011 10010111 11010010 00000000 01110100
然后,小端字节编码会从右侧获取每个字节,并将其放入字节串中:
01110100
00000000
11010010
10010111
00011011
11101101
01001001
01110100
00000000
可以看出,由于我们在概念上会将新部分附加到左侧的大数(即添加更多有效位),但我们会从右侧(即最低有效位)进行编码,因此可以增量地进行编码和解码。
最终会导致
additions_four_bytes {
first_value: 489866504
rice_parameter: 30
entries_count: 2
encoded_data: "t\000\322\227\033\355It\000"
}
客户端只需按上述步骤的反向顺序解码哈希前缀即可。
解码移除索引
删除索引使用与上述完全相同的技术编码,并使用 32 位整数。
更新频率
客户端应检查 minimum_wait_duration
字段中服务器返回的值,并使用该值来安排数据库的下一次更新。此值可能为零(minimum_wait_duration
字段完全缺失),在这种情况下,客户端应立即执行另一次更新。