Linux 用 checksum 驗證下載檔案完整性
使用 sha256sum 計算檔案摘要,並與 SHA256SUMS 清單比對;再修改 1 個位元組,觀察 FAILED 訊息,了解 checksum 能證明什麼。
在 2 分鐘內使用 checksum 驗證下載內容
若要使用 checksum 驗證下載內容,請先對收到的檔案計算雜湊值,再讓工具將該值與發布者列出的值進行比對。sha256sum 可完成這兩個步驟:單獨使用時會輸出摘要,搭配 -c 時則會讀取摘要清單,並回報哪些檔案相符。本指南會在自行建立的檔案上執行完整流程,接著刻意修改該檔案,讓您直接觀察驗證失敗的情況,而不只是閱讀說明。
請全程記住以下這句話。Checksum 只能告訴您目前持有的位元組,是否就是產生該摘要的位元組;它無法告訴您摘要由誰產生。第二個問題需要簽章,以及您信任的金鑰。本指南最後會明確說明兩者的界線。
建立練習用檔案
請在暫存目錄中操作,避免這裡的任何內容影響系統其他部分。以下所有命令都來自 GNU coreutils;這是任何 Ubuntu 或 Debian 伺服器都具備的基本命令集合,因此不需要安裝任何項目。
mkdir -p ~/checksum-demo
cd ~/checksum-demo
printf 'the payload of a totally ordinary download\n' > payload.txt
sha256sum payload.txt您會得到一行輸出:64 個十六進位字元、兩個空格,以及檔案名稱。這 64 個字元就是檔案的摘要值。再次執行該命令時,輸出內容會完全相同,因為雜湊運算具備確定性:相同輸入一律產生相同輸出。修改檔案中的一個字元後再次執行,摘要值不會只些微變動,而是看起來完全不同,因為輸入中的 1 個位元翻轉時,輸出中約有一半的位元也會翻轉。這項特性使 64 字元的字串得以代表 4 GB 的映像檔。
儲存 SHA256SUMS 檔案,然後進行檢查
畫面上的摘要隔天就沒有用了。請將摘要寫入檔案,並使用 sha256sum 本身寫出的格式,讓工具之後可以讀回檔案。
sha256sum payload.txt > SHA256SUMS
cat SHA256SUMS
sha256sum -c SHA256SUMSsha256sum -c 會讀取清單中的每一行,計算該行所指定檔案的雜湊值,並比較兩個摘要。檢查成功時,每個檔案會輸出一行:
payload.txt: OK也請檢查結束狀態,因為指令碼會讀取結束狀態,而不會讀取文字輸出。檢查成功後,echo $? 會輸出 0。SHA256SUMS 這個名稱是慣例,不是硬性規定,但各發行版與大多數發布頁面都會使用,因此也請採用這個名稱。如此一來,下一位使用者無須開啟檔案,就能知道其中儲存的內容。
翻轉 1 個位元組並觀察檢查失敗
現在刻意破壞檔案。這會在偏移量 5 寫入單一位元組,其他內容維持不變,因此檔案長度與檔名都會保留。
printf 'X' | dd of=payload.txt bs=1 seek=5 conv=notrunc status=none
cat payload.txt
sha256sum -c SHA256SUMSconv=notrunc 是關鍵旗標:若沒有它,dd 會在停止寫入的位置截斷檔案,而你測試的會是更明顯的損壞類型。此時檢查會輸出:
payload.txt: FAILED
sha256sum: WARNING: 1 computed checksum did NOT matchecho $? 會輸出 1。FAILED 表示檔案已讀取,但其摘要與清單中的摘要不一致。還原原始位元組,並確認檢查結果恢復為 OK:
printf 'the payload of a totally ordinary download\n' > payload.txt
sha256sum -c SHA256SUMS這就是完整的操作習慣。檔案中任何位置只要有 1 個位元組不同,就會產生 FAILED。下載因連線中斷而提前結束、鏡像站提供昨天建置的版本、代理伺服器在傳輸過程中改寫檔案,或磁碟回傳損壞的區塊,最後都會顯示在同一行。
清單列出您未下載的檔案時
實際的 SHA256SUMS 檔案會列出該專案發布的每個映像檔,而您只下載了其中一個。請在此重現這種情況。
printf 'a second file\n' > notes.txt
sha256sum payload.txt notes.txt > SHA256SUMS.all
rm notes.txt
sha256sum -c SHA256SUMS.allpayload.txt: OK
sha256sum: notes.txt: No such file or directory
notes.txt: FAILED open or read
sha256sum: WARNING: 1 listed file could not be readFAILED open or read與FAILED是不同的失敗情況,混淆兩者只會浪費時間。FAILED表示檔案內容不正確。FAILED open or read表示sha256sum根本找不到該檔案,因此沒有進行任何比對。在實際下載中,最常見的原因是目前工作目錄不正確,因為清單中的檔名是相對於執行命令的位置。切換到存放該檔案的目錄後再次執行。若只要檢查實際擁有的檔案,請指定檔案名稱:
sha256sum --ignore-missing -c SHA256SUMS.all此命令會輸出payload.txt: OK並以 0 結束。若清單中的檔名一個都不存在,--ignore-missing不會在零個檔案的情況下靜默成功。它會回報no file was verified並以非零狀態結束。這正是所需的行為,因為「檢查了零個檔案卻通過」才是最不容易察覺的失敗。
不需人工檢視即可貼上已發布的摘要
逐一比對64個十六進位字元時,這個習慣最容易失效。人們會檢查前4個字元和後4個字元,然後就認定兩者相符;而這正是蓄意攻擊者預期你採用的比對方式。讓工具負責比對。將你從發布者複製的摘要設為 EXPECTED,使用 EXPECTED= 接上貼上的值,然後建立 -c 所要求的單行內容:
printf '%s %s\n' "$EXPECTED" payload.txt > payload.sha256
cat payload.sha256
sha256sum -c payload.sha256摘要與檔名之間有2個空格,因此格式字串也包含2個空格。這就是 sha256sum 寫出的格式,也是 -c 解析的格式。只包含摘要而沒有其他內容的檔案,根本不是 checksum 行,因此檢查會以 no properly formatted checksum lines found 拒絕整個檔案,而不會猜測你指的是哪個檔案。有些專案發布的則是 BSD 標記格式,即 SHA256 (payload.txt) = 接上摘要。GNU coreutils 使用 sha256sum --tag payload.txt 寫出這種格式,並使用 -c 讀回,因此儲存任一種格式都可以。
檢查行為異常時,可使用 cat -A SHA256SUMS 檢視清單本身;它會以 $ 標記每一行的結尾,並顯示平常看不到的字元。以 ^M$ 結尾的行,表示從 Windows 編輯器帶入了 carriage return。GNU sha256sum 會忽略該行尾字元,仍輸出 OK,因此 CRLF 清單不是造成檢查失敗的原因,但 coreutils 以外的工具對此通常不那麼寬容。使用 tr -d '\r' < SHA256SUMS > SHA256SUMS.clean 將保留的副本正規化。
Checksum 能證明什麼,不能證明什麼?
Checksum 只能證明一件事:磁碟上的位元組,就是產生已發布摘要值的那些位元組。這能完整偵測意外損毀。若攻擊者替換了下載 mirror 上的檔案,卻無法修改發布摘要值的頁面,checksum 也能偵測這種粗心的攻擊。
Checksum 無法證明檔案的作者。摘要值描述的是位元組,不是人員。如果同一個頁面同時提供檔案與摘要值,那麼能修改其中一項的人,也能修改另一項;此時你的 OK 行只能表示 mirror 與自身一致。因此,執行 checksum 才有價值的規則是:從不同於檔案來源的位置取得摘要值。例如,映像檔來自 mirror 或 torrent,但摘要值取自專案自己的 TLS(transport layer security)網域。如此一來,攻擊者必須同時控制兩個位置,而不是一個。
演算法也很重要。截至 2026 年 8 月,SHA-256(secure hash algorithm, 256-bit output)尚無已知的碰撞,這就是發布者使用它的原因。MD5(message digest 5)與 SHA-1 的安全性不足:自 2004 年起,便能建構出具有相同 MD5 摘要值的兩個不同檔案;2020 年也發布了 chosen-prefix SHA-1 碰撞。MD5SUMS 檔案仍能偵測下載不完整,因為隨機損毀不是精心製作的碰撞。但它無法阻止試圖欺騙你的人。如果專案同時發布兩者,請使用 SHA-256 那一行。
簽章接手驗證工作
簽章能補上摘要留下的缺口。發布者使用私密金鑰簽署摘要檔案,您則使用其公開金鑰進行驗證:gpg --verify SHA256SUMS.asc SHA256SUMS。如果驗證成功,表示摘要清單確實來自持有該金鑰的人。接著,sha256sum -c SHA256SUMS 會將您磁碟上的檔案與清單建立關聯,整條信任鏈便會從金鑰一路延伸到檔案內容。
弱點會轉移到金鑰本身。若從提供檔案的同一個頁面取得金鑰,攻擊者就能同時控制這兩個環節。GnuPG 對此會明確提示,第一次驗證時會輸出 Good signature 以及 WARNING: This key is not certified with a trusted signature!。Good signature 表示數學驗證成立,但不表示該金鑰屬於您認定的專案。請從第二個來源取得指紋,例如位於不同網域的專案文件,或已經隨附該金鑰的 distribution package,然後比對完整指紋,不要只比對最後 8 個字元。這與 SSH 私密金鑰需要同樣謹慎,原因也相同:金鑰就是信任決策,後續所有環節都會繼承這項決策。
可重現建置將這個概念再往前推進。已發布的摘要仍會讓您依賴由某一台機器建置的 binary。若專案採用可重現建置,任何人都能編譯相同的原始碼並取得逐位元組相同的輸出,因此不同的建置者可以自行確認已發布的摘要,而不必要求您相信某一台伺服器的結果。隨著越來越多程式碼經由自動化 pipeline 和機器撰寫的 patch 送入系統,這一點每年都更加重要。決定哪些內容可納入建置是政策問題,而 AI 輔助程式碼的開放原始碼政策 也在同一條供應鏈上運作,只是方向相反。
套件管理器已經替你完成這項工作
在 Debian 和 Ubuntu 上,apt 每次安裝套件時都會自動執行這套驗證鏈。套件索引會為每個 .deb 檔案記錄 SHA-256 摘要。Release 檔案會記錄這些索引檔的摘要,而 InRelease 會記錄涵蓋 Release 的簽章;系統會使用 /usr/share/keyrings 和 /etc/apt/trusted.gpg.d 中的金鑰進行驗證。驗證鏈中斷時,apt 會明確指出原因:第三方套件庫缺少金鑰時顯示 The following signatures couldn't be verified because the public key is not available: NO_PUBKEY;取得的索引與已簽署的 Release 不相符時顯示 Hash Sum mismatch。後一種情況通常表示快取代理伺服器提供了過期檔案,或你在 mirror 同步期間取得了檔案。
當專案首頁要求你將 curl 的指令碼直接導入 shell 時,應以這套標準進行評估。這種做法不會驗證內容,你也完全看不到指令碼。伺服器還可能對指令碼回傳一份內容,對瀏覽器回傳另一份內容,而你事後沒有副本可供檢查。請使用 curl -fsSL <url> -o install.sh 下載至檔案、計算其雜湊值,使用 less 閱讀內容,確認後才執行。這個習慣大約只需二十秒;在伺服器上安裝任何其他軟體前,也應在 全新 VPS 的前十分鐘內養成這項習慣。
保留手動安裝項目的摘要清單
透過 apt 安裝的套件會被追蹤。自行複製到 /usr/local/bin 的二進位檔不會被追蹤,系統也不會監控它。摘要清單可讓你隨時檢查這些檔案:
printf 'a second file\n' > notes.txt
sha256sum *.txt > inventory.sha256
sha256sum -c --quiet inventory.sha256所有檔案都相符時,--quiet 不會輸出任何內容;如果有檔案不相符,則只會輸出驗證失敗的行。因此,沒有輸出表示驗證通過,而 echo $? 會透過 0 確認結果。這種形式適合放入排程工作。--status 會進一步完全不輸出內容,只留下結束狀態。將相同模式套用至實際檔案時使用 sha256sum /usr/local/bin/* > ~/local-bin.sha256,即可建立基準。路徑會完全依照輸入內容儲存在清單中,因此使用絕對路徑後,無論從哪個目錄執行檢查都能正常運作。
請明確了解這項基準的用途。它能偵測檔案是否變更,但無法偵測已取得 root 權限的攻擊者,因為該攻擊者同樣能改寫 inventory.sha256,就像改寫二進位檔一樣。如果希望這份清單具有實際意義,請將它存放在該機器之外。這也是更廣泛問題的一部分:你實際信任 VPS 的程度,以及還有誰能存取其底層磁碟。
FAQ
比對的 checksum 一致,是否代表下載內容安全?
不代表。這只表示你取得的位元組,與拿來比對的 digest 一致。如果攻擊者控制了發布該 digest 的頁面,他們可以發布自己檔案的 digest,讓你的檢查輸出 OK。一致只能證明內容相符。要證明安全,必須使用從其他來源取得的 key 驗證 signature;只有這樣,digest 才能繼承該信任。
為什麼 sha256sum -c 會輸出 FAILED open or read?
因為它根本沒有讀取該檔案。就在上一行,另一行訊息指出 No such file or directory,並列出它尋找的檔名。SHA256SUMS 檔案中的檔名是相對於執行命令所在目錄的路徑,因此請切換到存放下載檔案的目錄,再次執行命令。如果清單中也列出你未下載的檔案,請加入 --ignore-missing。沒有 open or read 的一般 FAILED 則是相反情況:檔案已讀取,但其 digest 不一致。
使用 MD5 驗證下載內容是否足夠?
若是檢查意外損壞,足夠。傳輸內容遭截斷或磁碟區塊損壞,不會剛好產生一致的 MD5 digest。但若要防範攻擊者,則不夠。自 2004 年起,已能建構出具有相同 MD5 digest 的不同檔案;2020 年也出現了 SHA-1 的 chosen-prefix collision。當專案同時發布兩者時,請採用 SHA-256;如果專案只提供 MD5,應將其視為發布流程老舊的訊號。
sha256sum -c 與 gpg --verify 有什麼差異?
sha256sum -c 證明檔案符合某個 digest。gpg --verify 證明 digest 檔案由特定 private key 的持有者簽署。兩者回答的是不同問題,因此專案同時提供兩者時,請一併執行。signature 讓 digest 清單可信,而 digest 清單接著讓下載的檔案可信。
如何用網頁上列出的 digest 檢查單一檔案?
不要用肉眼比對字元。將 digest 與檔名存成同一行,中間以兩個空格分隔,然後對該檔案執行 sha256sum -c,再查看它輸出的 OK 或 FAILED。使用 printf '%s %s\n' 建立該行,可避免格式錯誤,否則 sha256sum 會因為 no properly formatted checksum lines found 而拒絕該檔案。