Bạn tải một file cài đặt về. Bên cạnh nó, trang web ghi một dòng checksum SHA256 dài loằng ngoằng và bảo "kiểm tra để chắc file không hỏng". Bạn chạy sha256sum, thấy khớp, yên tâm cài. Câu hỏi của bài này: cái checksum đó thật ra bảo vệ bạn khỏi chuyện gì — và khỏi chuyện gì thì không?

Chủ đề là ký và xác minh tạo tác (artifact): quy trình mà nhà phát hành gắn một chữ ký số vào bản phát hành, để người dùng kiểm rằng file đúng là của họ và chưa bị đụng vào. Tôi dựng toàn bộ quy trình trong container dùng một lần bằng Ed25519, thử ba cách tấn công, rồi so với checksum. Đo 3 lần, tự dọn. Không nhắm vào hệ thống của ai khác.

Quy trình ký, đo bằng ba cách tấn công

Nhà phát hành sinh một cặp khoá: khoá riêng giữ kín, khoá công công bố rộng rãi. Họ ký artifact bằng khoá riêng, tạo ra một chữ ký 64 byte. Người dùng có khoá công thì kiểm được chữ ký đó. Tôi dựng đúng như vậy rồi tấn công.

Cách 1 — người dùng thật kiểm bản gốc:

verify artifact.bin bang khoa cong -> Signature Verified Successfully

Bản gốc, chữ ký gốc, khoá công thật: hợp lệ. Đây là đường đi bình thường.

Cách 2 — kẻ tấn công sửa file, giữ nguyên chữ ký cũ:

sua 1 byte -> Signature Verification Failure

Chỉ đổi một byte đầu file, để nguyên chữ ký cũ, và xác minh thất bại ngay. Đây là điểm cốt lõi: chữ ký gắn chặt với đúng từng byte của nội dung. Kẻ tấn công chèn mã độc vào file cài đặt thì chữ ký cũ không còn khớp, người dùng biết ngay khi verify.

Cách 3 — kẻ tấn công tự ký bằng khoá riêng của chính hắn:

ke tu ky bang khoa cua han, verify bang khoa cong that -> Signature Verification Failure

Kẻ tấn công thông minh hơn: hắn sửa file rồi tự ký lại bằng khoá riêng của hắn, để chữ ký "khớp" với file đã sửa. Nhưng khi người dùng kiểm bằng khoá công thật của nhà phát hành, nó vẫn thất bại — vì chữ ký của hắn chỉ khớp với khoá công của hắn, không khớp khoá công thật. Đây là chỗ chữ ký chứng minh danh tính: không chỉ "file không đổi" mà "file được ký bởi đúng người giữ khoá riêng kia".

Cả ba kết quả giống hệt qua 3 lần đo. Điều kiện để kẻ tấn công qua được cửa này chỉ có một: lấy được khoá riêng. Không có khoá riêng, hắn không thể tạo một chữ ký khớp khoá công thật — dù hắn kiểm soát hoàn toàn file và cả trang tải về.

Tương phản: checksum chứng minh ít hơn bạn tưởng

Giờ quay lại cái checksum SHA256 ở đầu bài. Tôi tính checksum bản gốc và bản đã sửa:

checksum ban goc:  a8e13e28fddbed7d...636fa0b7
checksum ban sua:  28a357a8cfb86d7f...06953ec6

Đúng là chúng khác nhau — nên nếu file hỏng do đường truyền, checksum sẽ lệch và bạn biết. Checksum làm tốt việc đó: bắt hỏng ngẫu nhiên. Nhưng nó có một lỗ hổng chí mạng trước kẻ tấn công chủ động: kẻ nào sửa được file thì cũng tính lại checksum mới và thay luôn dòng checksum trên trang. Người dùng tải file độc, tính SHA256, so với dòng checksum (cũng đã bị thay) — và thấy khớp. Checksum không đòi bí mật gì cả; sha256sum ai chạy cũng ra, kể cả kẻ tấn công.

Đó là khác biệt cột lõi giữa hai thứ trông na ná nhau:

  • Checksum chứng minh toàn vẹn — file này khớp với chuỗi băm này. Nhưng vì ai cũng tính được, nó không nói gì về việc ai tạo ra chuỗi băm đó.
  • Chữ ký chứng minh toàn vẹn VÀ danh tính — file này chưa đổi, được ký bởi người giữ khoá riêng. Vì tạo chữ ký cần bí mật (khoá riêng), kẻ tấn công không làm giả được.

Nói ngắn: checksum chống tai nạn, chữ ký chống đối thủ. Một trang phát hành nghiêm túc thường có cả hai — checksum để bắt lỗi tải, và chữ ký (hoặc checksum được ký) để chống giả mạo. Nếu chỉ có checksum trần, bạn đang tin vào chính cái trang mà kẻ tấn công cần chiếm.

Nhưng khoá công đó, bạn tin từ đâu?

Có một mắt xích mà phép đo trên giả định sẵn: người dùng đã có khoá công thật của nhà phát hành. Cả sự bảo đảm nằm ở đó. Nếu kẻ tấn công lừa được bạn dùng khoá công của hắn ngay từ đầu, thì mọi thứ hắn ký sẽ "verify thành công" — vì bạn đang kiểm bằng đúng khoá của hắn. Chữ ký chỉ mạnh bằng độ tin của khoá công bạn dùng để kiểm.

Đây là bài toán phân phối khoá, và nó không có lời giải miễn phí. Vài cách thực tế: khoá công đi kèm hệ điều hành hoặc trình quản lý gói (như khoá GPG của kho apt, được cài sẵn lúc bạn cài máy — tin một lần lúc đầu); khoá công bố qua nhiều kênh độc lập để kẻ tấn công khó chiếm hết; hoặc mô hình mới hơn như Sigstore, ký "không khoá" (keyless) gắn danh tính vào tài khoản định danh và ghi mọi chữ ký vào một sổ công khai minh bạch (transparency log) để ai cũng soi được. Điểm chung: bạn phải neo lòng tin vào một chỗ nào đó khó giả mạo hơn chính file tải về. Không neo được thì chữ ký chỉ dời vấn đề từ "tin file" sang "tin khoá", chứ không xoá được nó.

Ghép lại

Sơ đồ ký và xác minh artifact so với checksum

Ba điều rút ra:

  1. Chữ ký gắn với từng byte. Sửa một byte là chữ ký gãy — toàn vẹn được bảo đảm bằng toán học, không phải bằng thiện chí.
  2. Chữ ký gắn với danh tính. Kẻ tấn công tự ký bằng khoá của hắn vẫn trượt khi kiểm bằng khoá công thật. Điều duy nhất phá được là để lộ khoá riêng — nên bảo vệ khoá riêng chính là bảo vệ cả chuỗi (nối thẳng với hai bài trước về bí mật).
  3. Checksum không thay được chữ ký. Nó bắt hỏng ngẫu nhiên, không chống được kẻ kiểm soát nơi phát hành. Đừng nhầm "khớp checksum" với "an toàn".

Mô-típ của Series lặp lại: công cụ đã có sẵn và đúng (openssl, cosign, minisign ký/kiểm trong một dòng), lỗ hổng nằm ở chỗ người ta dừng ở checksum vì nó trông cũng giống "một chuỗi băm để kiểm". Giống nhau bề ngoài, khác nhau ở chỗ có cần bí mật để tạo ra hay không — và đó là toàn bộ vấn đề.

Thử ba mươi giây

Tự ký một file bằng Ed25519 và thử phá:

openssl genpkey -algorithm ed25519 -out priv.pem
openssl pkey -in priv.pem -pubout -out pub.pem
echo "ban phat hanh 1.0" > artifact.bin
openssl pkeyutl -sign -inkey priv.pem -rawin -in artifact.bin -out artifact.sig

# verify ban goc -> Verified Successfully
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in artifact.bin -sigfile artifact.sig

# doi 1 byte roi verify lai -> Verification Failure
echo "ban phat hanh 1.1" > artifact.bin
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in artifact.bin -sigfile artifact.sig

Nếu lệnh thứ hai báo thành công còn lệnh thứ ba báo thất bại, bạn vừa tự tay chứng minh: chữ ký biết file đã đổi, kể cả khi bạn chỉ đổi đúng một dòng.