Local Database

Google 安全瀏覽 v5 預期用戶端會維護本機資料庫,除非用戶端選擇不儲存即時模式。本機資料庫的格式和儲存方式由用戶端自行決定。從概念上來說,這個本機資料庫的內容可視為包含各種清單的資料夾,而這些檔案的內容則是 SHA256 雜湊,或其對應的前置字串,其中四個位元組的前置字串是最常用的雜湊長度。

可用的清單

系統會透過清單的專屬名稱來識別清單,這些名稱遵循命名慣例,其中的後置字串會代表清單中應有的雜湊長度。同樣屬於威脅類型但雜湊長度不同的雜湊清單,會以不同的名稱和後置字串 (表示雜湊長度) 命名。

以下清單可與雜湊清單方法搭配使用。

清單名稱 對應的 v4 ThreatType 列舉 說明
gc-32b 這份清單是 Global Cache 清單。這是一份特殊清單,僅用於即時模式運作。
se-4b SOCIAL_ENGINEERING 這份清單包含 SOCIAL_ENGINEERING 威脅類型的威脅。
mw-4b MALWARE 這份清單包含電腦平台的惡意軟體威脅類型威脅。
uws-4b UNWANTED_SOFTWARE 這份清單包含桌面平台的 UNWANTED_SOFTWARE 威脅類型。
uwsa-4b UNWANTED_SOFTWARE 這份清單包含 Android 平台的 UNWANTED_SOFTWARE 威脅類型威脅。
pha-4b POTENTIALLY_HARMFUL_APPLICATION 這份清單包含 Android 平台的 POTENTIALLY_HARMFUL_APPLICATION 威脅類型。

我們日後可能會提供其他清單,屆時上表會擴充,而 hashList.list 方法 的結果也會顯示與最新清單類似的結果。

資料庫更新

用戶端會定期呼叫 hashList.get 方法hashLists.batchGet 方法來更新資料庫。一般用戶端會想要一次更新多個清單,因此建議使用 hashLists.batchGet 方法

清單名稱永遠不會變更。此外,清單一旦顯示,就永遠不會移除 (如果清單不再實用,系統會將其設為空白,但仍會保留)。因此,在 Google 安全瀏覽用戶端程式碼中硬式編碼這些名稱是適當的做法。

hashList.get 方法hashLists.batchGet 方法都支援增量更新。使用增量更新可節省頻寬並提升效能。增量更新會在用戶端的清單版本與清單的最新版本之間提供差異。(如果用戶端是新部署的,且沒有任何可用的版本,則可進行完整更新)。這項增量更新包含移除索引和新增項目。系統會先要求用戶端從本機資料庫中移除指定索引的項目,然後套用新增項目。

最後,為避免資料毀損,用戶端應根據伺服器提供的總和檢查碼,檢查儲存的資料。只要總和檢查不相符,用戶端就應執行完整更新。

解碼清單內容

解碼雜湊和雜湊前置字串

所有清單都會使用特殊編碼來縮減大小。這種編碼方式的運作原理是,在概念上,Google 安全瀏覽服務清單包含一組雜湊或雜湊前置字串,這些雜湊與隨機整數在統計上無法區分。如果我們對這些整數進行排序,並計算相鄰的差異,這些相鄰的差異在某種程度上應是「小」的。Golomb-Rice 編碼會利用這個特性。

假設要使用 4 位元組雜湊前置字元傳送三個主機尾碼路徑前置字元運算式,即 a.example.com/b.example.com/y.example.com/。再假設 Rice 參數 (以 k 表示) 的值為

  1. 伺服器會先計算這些字串的完整雜湊,分別為:
291bc5421f1cd54d99afcc55d166e2b9fe42447025895bf09dd41b2110a687dc  a.example.com/
1d32c5084a360e58f1b87109637a6810acad97a861a7769e8f1841410d2a960c  b.example.com/
f7a502e56e8b01c6dc242b35122683c9d25d07fb1f532d9853eb0ef3ff334f03  y.example.com/

然後,伺服器會為上述每個項目建立 4 位元組的雜湊字首,也就是 32 位元組完整雜湊的前 4 位元組,並解讀為大端序 32 位元整數。大端序是指完整雜湊值的第一個位元組成為 32 位元整數的最高有效位元組。這個步驟會產生 0x291bc542、0x1d32c508 和 0xf7a502e5 等整數。

伺服器必須依字典順序排序這三個雜湊前置字元 (等同於 big endian 的數字排序),排序結果為 0x1d32c508、0x291bc542、0xf7a502e5。第一個雜湊前置字會儲存在 first_value 欄位中,不會變更。

接著,伺服器會計算兩個相鄰的差異值,分別為 0xbe9003a 和 0xce893da3。假設 k 的值為 30,伺服器會將這兩個數字分為商和餘數,分別為 2 位元和 30 位元長度。對於第一個數字,除數部分為零,餘數為 0xbe9003a;對於第二個數字,由於最重大的兩個位元在二進位為 11,因此除數部分為 3,餘數為 0xe893da3。對於給定的商 q,系統會使用 1 + q 位元編碼成 (1 << q) - 1,而餘數則會直接使用 k 位元編碼。第一個數字的除數部分會編碼為 0,餘數部分則為二進位 001011111010010000000000111010;第二個數字的除數部分會編碼為 0111,餘數部分則為 001110100010010011110110100011。

當這些數字形成位元組字串時,系統會使用 little endian。從概念上來說,您可能比較容易想像從最不重要的位元開始形成的長位元字串:我們取第一個數字的除數部分,並在第一個數字的餘數部分前方加上這個值;然後,我們再進一步在第二個數字的除數部分前方加上這個值,並在前方加上餘數部分。這應該會產生下列大數字 (為了清楚起見,我們加入了換行符號和註解):

001110100010010011110110100011 # Second number, remainder part
0111 # Second number, quotient part
001011111010010000000000111010 # First number, remainder part
0 # First number, quotient part

以單一行寫入的話,會是

00111010001001001111011010001101110010111110100100000000001110100

顯然,這個數字遠遠超過單一位元組可用的 8 位元。小端編碼會接著取出該數字中最低有效的 8 位元,並將其輸出為第一個位元組,也就是 01110100。為了方便說明,我們可以將上述位元字串分組,從最不重要的位元開始,每組八位元:

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 欄位完全缺少),在這種情況下,用戶端應立即執行其他更新。