Kiến thức nền
Chữ ký số: Chạy mật mã khóa công khai ngược lại
Bạn ký bằng khóa riêng. Bất cứ ai xác minh bằng khóa công khai. Sự bất đối xứng đó là nền tảng cho cập nhật phần mềm, chứng chỉ TLS, commit git, và mọi hệ thống phải tin tưởng code mà không tin tưởng kênh.
TL;DR
RSA đối xứng theo một cách bất ngờ: khóa công khai và khóa riêng có thể hoán đổi được trong toán học. Mã hóa bằng khóa công khai và giải mã bằng khóa riêng cho bạn tính bảo mật. Đảo hướng — “mã hóa” bằng khóa riêng, “giải mã” bằng khóa công khai — cho bạn tính xác thực: bất cứ ai cũng có thể xác minh rằng chỉ chủ sở hữu khóa mới có thể tạo ra kết quả. Đây là một chữ ký số. Bạn không ký tài liệu trực tiếp; bạn ký một hash của nó. Kết quả là nền tảng cho chứng chỉ TLS, các bản build Apple đã ký, các gói Debian, JWT, ký commit git, giao dịch tiền điện tử, và mọi nơi khác mà code không được tin cậy phải được xác minh trước khi chạy.
Nguyên thủy, bóc trần
Bài viết RSA cho thấy hai hướng:
# Mã hóa: khóa công khai khóa, khóa riêng mở
ciphertext = pow(message, e, n)
plaintext = pow(ciphertext, d, n)
# Ký: khóa riêng khóa, khóa công khai mở
signature = pow(document_hash, d, n)
verified = pow(signature, e, n)
Toán học giống hệt. Cái thay đổi là hướng và, quan trọng nhất, kết quả có nghĩa là gì.
Khi bạn mã hóa bằng khóa công khai của ai đó, bạn đang nói: “Tôi muốn chỉ bạn có thể đọc cái này.” Khi bạn ký bằng khóa riêng của chính bạn, bạn đang nói: “Tôi muốn mọi người có thể xác minh tôi tạo ra cái này.” Cặp khóa làm cả hai công việc, vì lũy thừa mod n là giao hoán: (m^e)^d ≡ (m^d)^e (mod n).
Điều này cụ thể cho RSA. Không phải mọi hệ thống khóa công khai đều đối xứng như vậy — Diffie-Hellman, ví dụ, là giao thức thỏa thuận khóa thuần túy và không thể ký trực tiếp. Chữ ký được xây trên toán học khác (DSA, ECDSA, EdDSA) là các nguyên thủy khác mà tình cờ cung cấp cùng đảm bảo, không phải RSA-ngược-lại theo đúng nghĩa.
Tại sao bạn ký hash, không phải tài liệu
Nguyên thủy RSA chỉ hoạt động trên các số nhỏ hơn n. Cho RSA 2048-bit, đó là 256 byte. Các tài liệu thực lớn hơn nhiều.
Bạn có thể cắt tài liệu thành các khối và ký từng cái, nhưng đó là chậm (một lũy thừa modular cho mỗi 256 byte) và — quan trọng hơn — nó không chứng minh điều bạn muốn chứng minh. Ký các khối chứng minh bạn đã ký mỗi khối; nó không chứng minh bạn đã ký chuỗi khối này theo thứ tự này. Một kẻ tấn công có thể sắp xếp lại, bỏ một số, hoặc trộn các khối đã ký từ các tài liệu khác nhau.
Sửa là hash-rồi-ký:
- Tính một hash mật mã của toàn bộ tài liệu — một dấu vân tay kích thước cố định (thường 256 hoặc 512 bit) thay đổi hoàn toàn nếu bất kỳ bit nào của tài liệu thay đổi.
- Ký hash. Một lũy thừa modular, bất kể kích thước tài liệu.
import hashlib
def sign(document_bytes, d, n):
h = int.from_bytes(hashlib.sha256(document_bytes).digest(), 'big')
return pow(h, d, n)
def verify(document_bytes, signature, e, n):
h = int.from_bytes(hashlib.sha256(document_bytes).digest(), 'big')
return pow(signature, e, n) == h
Một thay đổi một bit trong tài liệu tạo ra một hash hoàn toàn khác, xác minh với một số hoàn toàn khác. Chữ ký thực sự là một cam kết với tài liệu chính xác này, không chỉ với nội dung tại thời điểm ký.
(Đây là RSA sách giáo khoa. Các phương án chữ ký thực sử dụng đệm có cấu trúc — RSA-PSS — để ngăn một lớp tấn công chống lại hash-rồi-ký thô. Cùng ý tưởng, cẩn thận hơn.)
Hàm hash thực sự hứa gì
Chữ ký số chỉ mạnh bằng hàm hash chúng sử dụng. Hash phải có ba tính chất:
- Kháng pre-image. Cho một hash
h, không thể tìm một thông điệpmsao chohash(m) = h. Không có điều này, một kẻ tấn công có thể giả mạo một tài liệu để khớp với một chữ ký hiện có. - Kháng pre-image thứ hai. Cho một thông điệp
mvà hash của nóhash(m), không thể tìm một thông điệp khácm'với cùng hash. Không có điều này, một kẻ tấn công được cho một tài liệu đã ký có thể tạo ra một tài liệu khác với cùng chữ ký. - Kháng va chạm. Không thể tìm bất kỳ cặp
m, m'vớihash(m) = hash(m'). Không có điều này, một kẻ tấn công có thể xây dựng hai tài liệu trước với cùng hash, bắt bạn ký một, và tuyên bố bạn đã ký cái kia.
Kháng va chạm là mạnh nhất và dễ phá nhất. Danh sách hàm hash kể câu chuyện:
- MD5 — hash 128-bit, bị phá. Va chạm được chứng minh năm 2004, vũ khí hóa chống ký code Windows năm 2008 (malware Flame), giờ chỉ được dùng cho checksum không-bảo-mật.
- SHA-1 — 160-bit, bị phá. Google chứng minh một va chạm năm 2017 (bài báo “SHAttered”). Git, sử dụng SHA-1, đã dần dần di chuyển.
- SHA-256, SHA-512 — một phần của họ SHA-2, hiện tại an toàn. Đây là thứ hầu hết ký hiện đại sử dụng.
- SHA-3 — một cấu trúc khác (dựa trên Keccak) được chuẩn hóa năm 2015 như một biện pháp phòng ngừa chống lại các điểm yếu SHA-2 chưa bao giờ thực sự xảy ra. Hiếm khi là mặc định nhưng có sẵn.
- BLAKE2, BLAKE3 — các lựa chọn hiện đại, nhanh hơn SHA-2 trên hầu hết phần cứng, được coi là an toàn.
Khi SHA-1 bị phá, mọi giao thức ký hash SHA-1 của các chứng chỉ đột ngột có một vấn đề di chuyển. Bản thân các chữ ký vẫn đúng về mặt mật mã — nhưng hash bên dưới thì không, nên các đảm bảo của chữ ký không giữ. “Phương án chữ ký của chúng tôi sử dụng SHA-1” là một câu gây ra nhiều năm cập nhật giao thức khẩn cấp.
Chuỗi chứng chỉ: Tin tưởng qua chữ ký
Chữ ký số phổ biến nhất bạn tương tác — mỗi khi bạn tải một trang web — là một chứng chỉ TLS. Chuỗi hoạt động như thế này:
Chứng chỉ Root CA (DigiCert, Let's Encrypt, v.v.)
│
│ ký
▼
Chứng chỉ Intermediate CA
│
│ ký
▼
Chứng chỉ server (cho example.com)
│
└─ chứa khóa công khai của server
Trình duyệt của bạn vận chuyển một danh sách các cơ quan chứng nhận gốc đáng tin — khoảng 100 chứng chỉ gốc được ghi vào OS hoặc trình duyệt. Khi bạn kết nối đến example.com:
- Server gửi chứng chỉ của nó, bao gồm khóa công khai, tên miền, và chữ ký.
- Chữ ký được tạo bởi khóa riêng của một intermediate CA; trình duyệt xác minh nó bằng khóa công khai của intermediate CA (cũng trong chuỗi).
- Chứng chỉ của intermediate được ký bởi một root CA, có khóa công khai trình duyệt đã tin tưởng.
- Mọi chữ ký xác minh → trình duyệt tin tưởng khóa công khai của server thuộc về
example.com→ TLS có thể tiếp tục.
Mỗi liên kết trong chuỗi là một chữ ký số trên một hash của nội dung chứng chỉ. Phá bất kỳ liên kết nào, bạn phá chuỗi. Toàn bộ PKI — cơ sở hạ tầng khóa công khai của web — là một cây khổng lồ các chữ ký RSA hoặc ECDSA, bắt nguồn ở khoảng 100 khóa mà các trình duyệt tin tưởng theo chỉ định.
Ký code
Mỗi khi điện thoại của bạn cài đặt một ứng dụng, nó xác minh một chữ ký:
- iOS và macOS. Mỗi ứng dụng được ký bằng một chứng chỉ nhà phát triển Apple, và chính Apple ký lại mọi ứng dụng App Store bằng khóa riêng của họ. OS từ chối chạy code không ký trừ khi bạn rõ ràng ghi đè. “Notarization” thêm một chữ ký thứ hai từ Apple xác nhận ứng dụng đã qua quét malware.
- Windows. Ký code là tùy chọn nhưng được khuyến khích mạnh — các installer không ký tạo ra các lời nhắc UAC đáng sợ. Trình điều khiển kernel yêu cầu chứng chỉ đã ký.
- Android. APK được ký bởi nhà phát triển. Cài đặt xác minh chữ ký. Cập nhật phải được ký bằng cùng khóa với bản gốc; đây là điều ngăn một nhà phát triển độc hại xuất bản một bản cập nhật “giả mạo”.
- Debian, Fedora, Arch. Mọi gói trong bản phân phối được ký bởi một khóa tin-cậy-bởi-phân-phối.
aptvàdnftừ chối cài đặt các gói mà chữ ký không xác minh.
Mô hình chung: đừng chạy code trừ khi ai đó bạn tin tưởng đã ký nó. Chữ ký không chứng minh code là tốt — một khóa nhà phát triển bị xâm phạm ký code xấu cũng vui vẻ như code tốt — nhưng nó chứng minh nguồn gốc. Bạn biết lỗi của ai bạn đang chạy.
Đây là mô hình internet lựa chọn cho phân phối phần mềm và đã bị tấn công từ mọi góc độ kể từ đó. Các xâm phạm chuỗi cung ứng (SolarWinds, backdoor XZ Utils, 3CX) tất cả đều có chữ ký hợp lệ, vì những kẻ tấn công đã xâm phạm các khóa ký. Đảm bảo của một chữ ký là cấu trúc, không phải ngữ nghĩa — nó chứng minh ai, không phải gì.
Git và JWT
Hai nơi bạn thấy chữ ký mỗi ngày ở mức thấp hơn:
- Commit và tag git đã ký.
git commit -Ský đối tượng commit bằng khóa GPG hoặc SSH của bạn. Bất cứ ai có khóa công khai của bạn có thể xác minh bạn đã làm commit. GitHub hiển thị một huy hiệu “Verified”. Điều này phần lớn bị phớt lờ trong mười lăm năm đầu của sự tồn tại của git và trở nên quan trọng sau khi rõ ràng rằng “tác giả commit” và “người thực tế đã tạo commit” không giống nhau, và các commit không ký có thể bị giả mạo một cách tầm thường. - JWT (JSON Web Token). Cấu trúc là
base64(header).base64(payload).base64(signature). Chữ ký là một chữ ký số trênheader.payload— thường là RS256 (RSA với SHA-256), ES256 (ECDSA với SHA-256), hoặc HS256 (HMAC-SHA256 với một bí mật chia sẻ, không thực sự là chữ ký khóa công khai nhưng sử dụng cùng cấu trúc). Một JWT được ký hợp lệ là một tuyên bố tự-chứa mà server có thể xác minh mà không cần tra cứu gì.
Ví dụ JWT là một minh họa tốt về cách nguyên thủy chữ ký dịch chuyển mô hình tin cậy của một hệ thống. Không có chữ ký, các phiên yêu cầu tra cứu cơ sở dữ liệu (session ID này có hợp lệ không?). Với chữ ký, phiên tự-mô-tả và có thể xác minh, vì token chứa bằng chứng riêng của nó rằng server đã phát hành.
Điều chữ ký không làm
Chữ ký số rộng rãi bị hiểu sai. Hai điều chúng rõ ràng không làm:
- Chúng không mã hóa. Một tài liệu đã ký hoàn toàn có thể đọc được bởi bất cứ ai. Nếu bạn cần nội dung bí mật, bạn mã hóa riêng, sử dụng một khóa khác (và thường một hệ thống mật mã khác — ký bằng RSA, mã hóa bằng AES).
- Chúng không đánh dấu thời gian. Chữ ký chứng minh ai đã ký, không phải khi nào. Để có “đã ký vào thời điểm T,” bạn cần một cơ quan đánh dấu thời gian riêng — một bên thứ ba đáng tin ký
(document_hash, timestamp)tại thời điểm ký. Không có điều này, một kẻ tấn công sau này xâm phạm khóa của bạn có thể tạo các chữ ký đã-lùi-ngày trông có vẻ hợp lệ.
Chúng cũng không chứng minh ý định. Một chữ ký chứng minh bạn đã giữ khóa khi nó được sử dụng. Nó không chứng minh bạn biết bạn đang ký gì, rằng bạn đã đọc tài liệu, hay thậm chí bạn đã biết bạn đang ký gì. Đây là lý do các token bảo mật phần cứng (YubiKey, smart card) thường yêu cầu một lần nhấn nút vật lý — đó là bằng chứng yếu về ý định, nhưng nó là một cái gì đó.
Mô hình rộng hơn
Bài viết RSA đóng khung chữ ký số là “mẹo thứ hai của RSA.” Khuôn khổ đó chính xác cho RSA nhưng bán rẻ tính tổng quát. Chữ ký số là một trong hai hay ba nguyên thủy nền tảng của mật mã hiện đại, và chúng xuất hiện ở những nơi ít liên quan đến mã hóa thông điệp:
- Giao dịch blockchain — mọi giao dịch bitcoin là một chuỗi các chữ ký ECDSA chứng minh quyền sở hữu.
- DNSSEC — các bản ghi DNS được ký, nên bạn có thể xác minh chúng không bị giả mạo giữa server có thẩm quyền và resolver của bạn.
- Cập nhật phần mềm — các trình tự-cập-nhật từ chối cài đặt các cập nhật không ký.
- SSH. Ngay cả xác thực server (known_hosts) được xác minh chống lại khóa host đã ký của server.
- Log minh bạch chứng chỉ — log chỉ-nối của mọi chứng chỉ từng được phát hành, mỗi mục log được ký, nên một CA lừa đảo không thể phát hành chứng chỉ bí mật.
Nguyên thủy đơn giản một cách lừa dối: một khóa để ký, một khóa khác để xác minh, một hash ở giữa. Mọi thứ phải xác minh nguồn gốc của dữ liệu trong một môi trường đối nghịch là một khách hàng xuôi dòng của ý tưởng đơn giản đó — bốn mươi chín tuổi, được phát minh vào một buổi sáng tháng Tư tại MIT, vẫn đang làm công việc.