Cách xác minh file tải về bằng checksum trên Linux
Hướng dẫn dùng sha256sum để kiểm tra tính toàn vẹn của file. Bạn sẽ học cách so sánh hash, xử lý lỗi FAILED khi file bị hỏng và hiểu rõ ranh giới giữa checksum và chữ ký số.
Xác minh file tải về bằng checksum trong hai phút
Để xác minh file tải về bằng checksum, hãy băm (hash) file bạn nhận được và dùng một công cụ để so sánh hash đó với hash mà nhà phát hành đã công bố. sha256sum thực hiện cả hai phần việc này: tự nó sẽ in ra một digest, và với -c nó sẽ đọc một danh sách các digest rồi báo cáo file nào khớp. Hướng dẫn này thực hiện toàn bộ quy trình trên một file bạn tự tạo, sau đó cố tình làm hỏng file đó để bạn thấy lỗi xảy ra thay vì chỉ đọc lý thuyết.
Hãy ghi nhớ một câu này trong suốt quá trình. Checksum cho bạn biết các byte bạn đang giữ có phải là các byte đã tạo ra digest đó hay không, và nó không cho biết gì về người đã tạo ra chúng. Câu hỏi thứ hai đó cần một chữ ký và một key mà bạn tin tưởng. Phần cuối của hướng dẫn này chỉ rõ ranh giới giữa hai khái niệm đó nằm ở đâu.
Tạo một file để thực hành
Hãy làm việc trong một thư mục tạm để không ảnh hưởng đến phần còn lại của hệ thống. Mọi lệnh dưới đây đều thuộc GNU coreutils, bộ lệnh cơ bản có sẵn trên bất kỳ server Ubuntu hoặc Debian nào, vì vậy bạn không cần cài đặt thêm gì cả.
mkdir -p ~/checksum-demo
cd ~/checksum-demo
printf 'the payload of a totally ordinary download\n' > payload.txt
sha256sum payload.txtBạn sẽ nhận được một dòng: 64 ký tự thập lục phân, hai khoảng trắng, sau đó là tên file. 64 ký tự đó là digest của file. Chạy lại lệnh này và dòng kết quả vẫn y hệt, vì hashing có tính xác định: cùng một đầu vào luôn cho ra cùng một đầu ra. Thay đổi một ký tự của file và chạy lại, digest sẽ không thay đổi một chút. Nó trông hoàn toàn khác biệt, vì chỉ cần lật một bit đầu vào là khoảng một nửa số bit đầu ra sẽ bị lật. Đặc tính đó giúp một chuỗi 64 ký tự trở thành đại diện hữu dụng cho một file image dung lượng 4 GB.
Lưu file SHA256SUMS và kiểm tra
Một mã hash hiển thị trên màn hình sẽ vô dụng sau một ngày. Hãy ghi nó vào một file, theo đúng định dạng mà sha256sum tạo ra, để công cụ có thể đọc lại sau này.
sha256sum payload.txt > SHA256SUMS
cat SHA256SUMS
sha256sum -c SHA256SUMSsha256sum -c đọc từng dòng trong danh sách, băm file được chỉ định trên dòng đó, rồi so sánh hai mã hash. Một lần chạy thành công sẽ in ra một dòng cho mỗi file:
payload.txt: OKHãy kiểm tra cả exit status, vì script sẽ đọc giá trị này thay vì đọc văn bản. echo $? sẽ in ra 0 sau khi chạy xong mà không có lỗi. Tên file SHA256SUMS là một quy ước thay vì quy tắc bắt buộc, nhưng các bản phân phối và hầu hết các trang release đều sử dụng nó, vì vậy bạn cũng nên dùng để người khác biết file đó chứa gì mà không cần mở ra xem.
Thay đổi một byte và quan sát lỗi kiểm tra
Bây giờ hãy cố tình làm hỏng file. Lệnh này ghi một byte duy nhất tại offset 5 và giữ nguyên mọi thứ khác, vì vậy file vẫn giữ nguyên độ dài và tên gọi.
printf 'X' | dd of=payload.txt bs=1 seek=5 conv=notrunc status=none
cat payload.txt
sha256sum -c SHA256SUMSconv=notrunc là flag quan trọng: nếu thiếu nó, dd sẽ cắt ngắn file tại điểm dừng ghi, và bạn sẽ chỉ đang kiểm tra một loại hư hỏng rõ ràng hơn nhiều. Lệnh kiểm tra bây giờ in ra:
payload.txt: FAILED
sha256sum: WARNING: 1 computed checksum did NOT matchecho $? in ra 1. FAILED nghĩa là file đã được đọc nhưng mã digest không khớp với mã trong danh sách. Hãy khôi phục lại các byte gốc và xác nhận lệnh kiểm tra quay trở lại OK:
printf 'the payload of a totally ordinary download\n' > payload.txt
sha256sum -c SHA256SUMSĐó là toàn bộ thói quen này. Chỉ cần một byte khác biệt, ở bất kỳ đâu trong file, cũng sẽ tạo ra FAILED. Một bản tải xuống bị ngắt quãng do mất kết nối, một mirror đang cung cấp bản build cũ, một proxy đã ghi đè file trong quá trình truyền tải, hoặc một ổ đĩa trả về bad block: tất cả đều dẫn đến cùng một kết quả đó.
Khi danh sách liệt kê một file bạn chưa tải về
Một file SHA256SUMS chuẩn từ bản phân phối sẽ liệt kê mọi image mà dự án phát hành, và bạn chỉ tải về một trong số đó. Hãy tái hiện tình huống đó tại đây.
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 là một lỗi khác với FAILED, và việc nhầm lẫn giữa hai lỗi này gây lãng phí thời gian. FAILED nghĩa là các byte bị sai. FAILED open or read nghĩa là sha256sum chưa bao giờ nhận được file, nên không có gì được so sánh. Với một bản tải về thực tế, nguyên nhân thường gặp là do thư mục làm việc, vì các tên trong danh sách là đường dẫn tương đối so với nơi bạn chạy lệnh. Hãy chuyển vào thư mục chứa file và chạy lại lệnh. Để chỉ kiểm tra những gì bạn thực sự có, hãy yêu cầu lệnh thực hiện điều đó:
sha256sum --ignore-missing -c SHA256SUMS.allLệnh đó in ra payload.txt: OK và thoát với mã 0. Nếu không có tên nào trong danh sách tồn tại, --ignore-missing sẽ không âm thầm thành công trên không file nào. Nó báo rằng no file was verified và thoát với mã khác 0, đây chính là hành vi bạn cần, vì một lần kiểm tra không thực hiện gì cả là lỗi mà bạn sẽ không bao giờ nhận ra.
Dán một digest đã công bố mà không cần kiểm tra bằng mắt
Việc so sánh 64 ký tự thập lục phân bằng mắt thường là nơi thói quen này bộc lộ điểm yếu. Người dùng thường chỉ kiểm tra bốn ký tự đầu và bốn ký tự cuối rồi kết luận là khớp, và đó chính xác là kiểu so sánh mà một kẻ tấn công có chủ đích đã tính toán trước. Hãy để công cụ thực hiện việc so sánh. Gán EXPECTED bằng giá trị digest bạn đã sao chép từ nhà phát hành, sử dụng EXPECTED= theo sau là giá trị vừa dán, sau đó tạo dòng đơn mà -c yêu cầu:
printf '%s %s\n' "$EXPECTED" payload.txt > payload.sha256
cat payload.sha256
sha256sum -c payload.sha256Có hai dấu cách nằm giữa digest và tên file, đó là lý do tại sao chuỗi định dạng chứa hai dấu cách. Đó là định dạng mà sha256sum ghi ra và -c phân tích. Một file chỉ chứa digest mà không có gì khác thì không phải là một dòng checksum hợp lệ, vì vậy lệnh kiểm tra sẽ từ chối toàn bộ file với thông báo no properly formatted checksum lines found thay vì đoán xem bạn đang ám chỉ file nào. Một số dự án công bố theo kiểu gắn thẻ BSD, SHA256 (payload.txt) = theo sau là digest. GNU coreutils ghi định dạng đó bằng sha256sum --tag payload.txt và đọc lại bằng -c, vì vậy bạn có thể lưu theo bất kỳ định dạng nào.
Khi việc kiểm tra có biểu hiện lạ, hãy xem danh sách bằng cat -A SHA256SUMS, lệnh này đánh dấu cuối mỗi dòng bằng $ và hiển thị các ký tự mà bình thường bạn không thấy được. Một dòng kết thúc bằng ^M$ nghĩa là đã dính ký tự carriage return từ trình soạn thảo trên Windows. GNU sha256sum bỏ qua ký tự thừa đó và vẫn in ra OK, vì vậy một danh sách CRLF không phải là nguyên nhân làm hỏng việc kiểm tra của bạn, mặc dù các công cụ bên ngoài coreutils thường khắt khe hơn về vấn đề này. Hãy chuẩn hóa bản sao bạn lưu giữ bằng tr -d '\r' < SHA256SUMS > SHA256SUMS.clean.
Checksum chứng minh điều gì và không chứng minh điều gì?
Checksum chỉ chứng minh một điều: các byte trên ổ đĩa của bạn chính là các byte đã tạo ra mã digest được công bố. Điều này giải quyết hoàn toàn các hư hỏng ngẫu nhiên. Nó cũng giải quyết trường hợp một kẻ tấn công bất cẩn thay thế file trên mirror tải xuống nhưng không thể can thiệp vào trang web công bố mã digest đó.
Nó không chứng minh bất cứ điều gì về quyền tác giả. Một mã digest là dữ liệu về các byte, không phải dữ liệu về con người. Nếu một trang web cung cấp cả file và mã digest, thì bất kỳ ai có thể thay đổi cái này đều có thể thay đổi cái kia, và dòng OK của bạn chỉ có nghĩa là mirror đó tự khớp với chính nó. Vì vậy, đây là quy tắc giúp việc chạy checksum trở nên hữu ích: hãy lấy mã digest từ một nguồn khác với nguồn bạn lấy file. Ví dụ: lấy từ tên miền chính của dự án qua TLS (transport layer security) trong khi file image được tải từ một mirror hoặc torrent. Lúc này, kẻ tấn công phải kiểm soát được hai nơi thay vì một.
Thuật toán cũng rất quan trọng. SHA-256 (secure hash algorithm, đầu ra 256-bit) chưa có va chạm (collision) nào được biết đến tính đến tháng 8 năm 2026, đó là lý do tại sao các nhà phát hành sử dụng nó. MD5 (message digest 5) và SHA-1 không còn an toàn: hai file khác nhau có cùng mã MD5 đã có thể tạo ra từ năm 2004, và một va chạm SHA-1 theo tiền tố được chọn đã được công bố vào năm 2020. Một file MD5SUMS vẫn có thể phát hiện việc tải xuống bị cắt ngắn, vì hỏng hóc ngẫu nhiên không phải là một va chạm được dàn dựng. Nó không thể ngăn chặn kẻ đang cố tình đánh lừa bạn. Khi một dự án công bố cả hai, hãy chọn dòng SHA-256.
Khi chữ ký số đảm nhận vai trò
Chữ ký số lấp đầy khoảng trống mà mã băm (digest) để lại. Nhà phát hành ký vào file chứa mã băm bằng private key, và bạn kiểm tra bằng public key của họ: gpg --verify SHA256SUMS.asc SHA256SUMS. Nếu quá trình kiểm tra thành công, danh sách các mã băm đó chắc chắn đến từ người nắm giữ key này. Sau đó, sha256sum -c SHA256SUMS liên kết file trên ổ cứng của bạn với danh sách đó, tạo ra một chuỗi xác thực từ key cho đến từng byte dữ liệu.
Điểm yếu lúc này chuyển sang phía key. Việc tải key từ cùng một trang web cung cấp file sẽ tạo cơ hội cho kẻ tấn công kiểm soát cả hai thành phần. GnuPG thể hiện rõ điều này, và lần xác thực đầu tiên sẽ in ra Good signature cùng với WARNING: This key is not certified with a trusted signature!. Good signature chỉ có nghĩa là các phép toán khớp nhau. Nó không đảm bảo key đó thuộc về dự án mà bạn đang hướng tới. Hãy lấy fingerprint từ một nguồn thứ hai, ví dụ như tài liệu của dự án trên một domain khác hoặc từ một gói cài đặt đã bao gồm sẵn key, sau đó so sánh toàn bộ fingerprint thay vì chỉ 8 ký tự cuối. Đây là sự cẩn trọng tương tự như đối với SSH private key, vì cùng một lý do: key chính là quyết định tin cậy, và mọi thứ phía sau đều kế thừa sự tin cậy đó.
Các bản build có thể tái lập (reproducible builds) đẩy ý tưởng này đi xa hơn một bước. Một mã băm được công bố vẫn chỉ ràng buộc bạn với một file binary do một máy tính tạo ra. Khi quá trình build của một dự án có thể tái lập, bất kỳ ai cũng có thể biên dịch lại cùng mã nguồn đó và nhận được kết quả giống hệt từng byte. Nhờ vậy, các bên xây dựng độc lập có thể xác nhận mã băm đã công bố thay vì bắt bạn phải tin vào lời của một máy chủ duy nhất. Điều này ngày càng quan trọng khi mã nguồn được đưa vào thông qua các pipeline tự động và các bản vá do máy tính viết. Việc quyết định những gì bạn chấp nhận đưa vào một bản build là một vấn đề về chính sách, và các chính sách mã nguồn mở cho code hỗ trợ bởi AI giải quyết cùng chuỗi cung ứng đó từ phía ngược lại.
Trình quản lý gói của bạn đã thực hiện việc này cho bạn
Trên Debian và Ubuntu, apt tự động chạy chuỗi này mỗi khi cài đặt mà không cần yêu cầu. Chỉ mục gói chứa một mã băm SHA-256 cho mỗi tệp .deb. Tệp Release chứa mã băm của các tệp chỉ mục đó, và InRelease chứa chữ ký số cho Release, được kiểm tra đối chiếu với các key trong /usr/share/keyrings và /etc/apt/trusted.gpg.d. Khi chuỗi này bị đứt gãy, apt sẽ thông báo: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY khi thiếu key của kho lưu trữ bên thứ ba, hoặc Hash Sum mismatch khi chỉ mục bạn tải về không khớp với Release đã được ký, điều này thường có nghĩa là caching proxy đã trả về một tệp cũ hoặc bạn đã truy cập vào mirror ngay lúc nó đang đồng bộ hóa.
Đó là tiêu chuẩn để đối chiếu khi trang chủ của một dự án yêu cầu bạn pipe một script từ curl trực tiếp vào shell. Không có gì xác thực các byte dữ liệu đó và bạn cũng không bao giờ nhìn thấy chúng. Máy chủ cũng có thể trả về nội dung này cho script nhưng lại trả về nội dung khác cho trình duyệt, và bạn không có bản sao để kiểm tra sau đó. Hãy tải về tệp bằng curl -fsSL <url> -o install.sh, băm tệp đó, đọc nội dung bằng less, và chỉ chạy nó sau khi đã kiểm tra. Thói quen này chỉ tốn khoảng hai mươi giây, và đây cũng là thói quen đáng bắt đầu trên một VPS mới trong mười phút đầu tiên, trước khi cài đặt bất cứ thứ gì khác lên máy chủ.
Duy trì danh sách mã băm (digest) cho các phần mềm cài đặt thủ công
Các gói cài đặt qua apt đều được theo dõi. Một file binary bạn copy vào /usr/local/bin thì không, và hệ thống không giám sát nó. Danh sách mã băm giúp bạn biến nó thành thứ có thể kiểm tra theo yêu cầu:
printf 'a second file\n' > notes.txt
sha256sum *.txt > inventory.sha256
sha256sum -c --quiet inventory.sha256--quiet không in ra gì khi mọi file khớp, và chỉ in ra các dòng bị lỗi khi có sự sai lệch, vì vậy sự im lặng là dấu hiệu thành công và echo $? xác nhận điều đó bằng 0. Đây là định dạng để đưa vào một tác vụ định kỳ. --status còn triệt để hơn khi không in ra bất cứ thứ gì, chỉ để lại trạng thái thoát (exit status). Hãy trỏ cùng mẫu đó vào các file thực tế bằng sha256sum /usr/local/bin/* > ~/local-bin.sha256 và bạn sẽ có một baseline. Các đường dẫn được lưu trong danh sách chính xác như cách bạn nhập, vì vậy đường dẫn tuyệt đối giúp việc kiểm tra hoạt động từ bất kỳ thư mục nào.
Hãy hiểu rõ giá trị của baseline này. Nó phát hiện một file bị thay đổi. Nó không phát hiện được kẻ tấn công đã có quyền root, vì kẻ đó có thể ghi đè inventory.sha256 dễ dàng như cách chúng ghi đè file binary. Hãy lưu danh sách này bên ngoài máy chủ nếu bạn muốn nó thực sự có ý nghĩa, đây là một phần của câu hỏi rộng hơn về mức độ tin tưởng thực sự vào một VPS và những ai khác có thể truy cập vào ổ đĩa bên dưới nó.
FAQ
Kiểm tra checksum khớp có nghĩa là file tải về an toàn không?
Không. Nó chỉ có nghĩa là các byte bạn có khớp với mã băm mà bạn đã so sánh. Nếu kẻ tấn công kiểm soát trang web công bố mã băm đó, chúng sẽ công bố mã băm của file do chúng tạo ra và lệnh kiểm tra của bạn sẽ in ra OK. Kết quả khớp chỉ là một khẳng định về tính nhất quán. Một khẳng định về tính an toàn cần có chữ ký được xác thực bằng một key mà bạn lấy từ nguồn khác, và chỉ khi đó mã băm mới kế thừa sự tin cậy đó.
Tại sao sha256sum -c lại in ra FAILED open or read?
Vì nó chưa đọc file đó. Một dòng riêng biệt ngay phía trên nó sẽ ghi No such file or directory kèm theo tên file mà nó đã tìm kiếm. Các tên file bên trong file SHA256SUMS là đường dẫn tương đối so với thư mục bạn đang chạy lệnh, vì vậy hãy di chuyển vào thư mục chứa file tải về và chạy lại lệnh. Nếu danh sách đó còn chứa các file bạn chưa tải về, hãy thêm --ignore-missing. Một thông báo FAILED đơn thuần không có open or read là trường hợp ngược lại: file đã được đọc, nhưng mã băm của nó không khớp.
MD5 có đủ tốt để xác thực file tải về không?
Đối với lỗi vô tình thì có. Một quá trình truyền tải bị ngắt quãng hoặc một block đĩa lỗi sẽ không tạo ra mã băm MD5 khớp một cách ngẫu nhiên. Đối với kẻ tấn công thì không. Hai file khác nhau có cùng mã băm MD5 đã có thể tạo ra từ năm 2004, và SHA-1 đã bị bẻ gãy bởi tấn công va chạm (collision) vào năm 2020. Hãy chọn dòng SHA-256 khi một dự án công bố cả hai, và coi việc một dự án chỉ dùng MD5 là dấu hiệu của một quy trình phát hành cũ.
Sự khác biệt giữa sha256sum -c và gpg --verify là gì?
sha256sum -c chứng minh một file khớp với mã băm. gpg --verify chứng minh một file chứa mã băm đã được ký bởi người nắm giữ một private key cụ thể. Chúng trả lời các câu hỏi khác nhau, vì vậy hãy chạy cả hai khi dự án cung cấp cả hai. Chữ ký làm cho danh sách mã băm trở nên đáng tin cậy, và danh sách mã băm sau đó làm cho file tải về trở nên đáng tin cậy.
Làm thế nào để kiểm tra một file với mã băm được in trên trang web?
Đừng so sánh các ký tự bằng mắt thường. Hãy lưu mã băm và tên file trên một dòng duy nhất, cách nhau bởi hai dấu cách, sau đó chạy sha256sum -c với file đó và đọc kết quả OK hoặc FAILED mà nó in ra. Việc tạo dòng bằng printf '%s %s\n' giúp tránh các lỗi định dạng khiến sha256sum từ chối file với thông báo no properly formatted checksum lines found.