Bài trước dùng hàm băm để lưu mật khẩu, một việc không cần giải mã. Khi dữ liệu cần được đọc lại, ta mã hóa nó. Mã hóa đối xứng (một khóa chung để khóa và mở, như AES) nhanh và mạnh, nhưng có một câu hỏi nó không trả lời được: hai bên lấy chung khóa từ đâu? Trình duyệt của bạn chưa bao giờ gặp máy chủ của viet.sarl, vậy mà sau vài chục mili giây, hai bên đã có chung một khóa bí mật mà không ai nghe lén trên đường truyền biết được. Bài này giải thích phép màu đó: mật mã khóa công khai.
Lộ trình: ý tưởng cặp khóa, RSA tự tính bằng số nhỏ, vì sao RSA "sách giáo khoa" không dùng được, Diffie-Hellman, đường cong elliptic, chữ ký số, đo tốc độ, độ dài khóa tương đương, rồi máy tính lượng tử và ML-KEM. Đo bằng Python 3.12 và thư viện cryptography 50 trên máy thật.
Ý tưởng: một cặp khóa
Thay vì một khóa chung, mỗi bên có một cặp khóa:
- Khóa công khai: đưa cho cả thế giới, in trên chứng chỉ, dán lên GitHub.
- Khóa bí mật: không bao giờ rời khỏi máy của chủ nhân.
Hai khóa liên hệ với nhau bằng một bài toán dễ theo một chiều, khó theo chiều ngược lại. Nhân hai số nguyên tố lớn thì dễ, nhưng phân tích tích của chúng ra thừa số thì cực khó. Từ cặp khóa đó có ba việc:
| Việc | Ai dùng khóa nào | Ví dụ |
|---|---|---|
| Mã hóa | Ai cũng mã hóa được bằng khóa công khai; chỉ chủ khóa bí mật giải mã được | Gửi thư cho người chưa gặp |
| Chữ ký | Chủ khóa bí mật ký; ai cũng kiểm tra được bằng khóa công khai | Chứng chỉ TLS, bản cập nhật phần mềm, commit git |
| Thỏa thuận khóa | Mỗi bên dùng khóa bí mật của mình và khóa công khai của bên kia, ra cùng một bí mật | Mọi kết nối TLS, SSH |
Trên thực tế, việc thứ nhất hiếm khi dùng trực tiếp cho dữ liệu. Mật mã khóa công khai chậm hơn mã hóa đối xứng hàng nghìn lần, nên nó chỉ dùng để thống nhất một khóa đối xứng, rồi khóa đó mã hóa dữ liệu thật.
RSA bằng số nhỏ
RSA (1977) dựa vào việc phân tích thừa số. Làm một lần bằng tay với số đủ nhỏ để thấy hết:
- Chọn hai số nguyên tố: p = 61, q = 53.
- n = p × q = 3233. Đây là phần công khai.
- φ(n) = (p − 1)(q − 1) = 3120. Chỉ ai biết p và q mới tính được.
- Chọn số mũ công khai e = 17 (nguyên tố cùng nhau với 3120).
- Tìm d sao cho e × d chia 3120 dư 1: d = 2753. Đây là khóa bí mật.
Khóa công khai là (n = 3233, e = 17), khóa bí mật là d = 2753. Với thông điệp m = 65:
c = pow(65, 17, 3233) # mã hóa: 2790
pow(2790, 2753, 3233) # giải mã: 65
s = pow(65, 2753, 3233) # ký bằng khóa bí mật: 588
pow(588, 17, 3233) # ai cũng kiểm được: 65
Mã hóa và ký chỉ là lũy thừa rồi lấy phần dư, theo hai chiều ngược nhau. Muốn tìm d từ (n, e), phải biết φ(n), tức phải phân tích 3233 thành 61 × 53. Với số 4 chữ số, việc này tức thì. Với n dài 2048 bit (617 chữ số thập phân), chưa ai làm được. Kỷ lục công khai hiện nay là 829 bit, mất khoảng 2.700 năm-lõi CPU.
RSA "sách giáo khoa" không dùng được
Công thức trên đúng về toán học nhưng không an toàn nếu dùng nguyên như vậy:
- Tất định: cùng thông điệp luôn ra cùng bản mã. Kẻ nghe lén đoán thử "có" hay "không", mã hóa cả hai bằng khóa công khai rồi so là biết.
- Có cấu trúc toán học: bản mã của các thông điệp liên quan với nhau cũng liên quan với nhau, nên kẻ tấn công có thể biến đổi bản mã một cách có chủ đích. Thí nghiệm nhỏ cho thấy nhân bản mã với một giá trị đã biết thì bản rõ giải ra bị nhân đôi.
Vì vậy RSA thật luôn đi với padding ngẫu nhiên có cấu trúc. Khi mã hóa dùng OAEP, khi ký dùng PSS. Bản padding cũ PKCS#1 v1.5 cho mã hóa đã gây ra hàng loạt lỗ hổng trong nhiều thập kỷ, nên đừng chọn nó cho hệ thống mới. Padding cũng giới hạn độ dài: với RSA-2048 và OAEP-SHA256, mỗi lần chỉ mã hóa được tối đa 190 byte (thử 191 byte thì thư viện từ chối). Đó là thêm một lý do để RSA chỉ bọc một khóa đối xứng, không bọc dữ liệu.
Diffie-Hellman: thống nhất bí mật qua kênh công khai
Năm 1976, Whitfield Diffie và Martin Hellman công bố cách để hai người chưa từng gặp nhau có chung một bí mật, dù mọi thứ họ gửi đều bị nghe thấy. Với số nhỏ, chọn số nguyên tố P = 23 và cơ số G = 5 (công khai):
| An | Bình | |
|---|---|---|
| Chọn bí mật | a = 6 | b = 15 |
| Gửi công khai | A = 5⁶ mod 23 = 8 | B = 5¹⁵ mod 23 = 19 |
| Tính bí mật chung | B^a = 19⁶ mod 23 = 2 | A^b = 8¹⁵ mod 23 = 2 |
Cả hai ra 2, vì (G^b)^a = (G^a)^b = G^(ab). Kẻ nghe lén thấy 23, 5, 8 và 19, nhưng muốn ra 2 thì phải tìm a từ 8 = 5^a mod 23. Đó là bài toán logarit rời rạc, dễ với 23 nhưng không giải được với số nguyên tố 2048 bit.
Diffie-Hellman một mình chỉ chống nghe lén, không chống kẻ đứng giữa. Một kẻ ở giữa có thể làm Diffie-Hellman riêng với An và riêng với Bình, rồi chuyển tiếp mọi thứ. Muốn chặn điều đó, ít nhất một bên phải ký phần mình gửi bằng một khóa mà bên kia tin. Đó chính là việc chứng chỉ làm trong TLS (bài 5), và khóa máy chủ làm trong SSH (bài 8).
Đường cong elliptic
Thay vì lũy thừa số nguyên theo modulo, mật mã đường cong elliptic (ECC) làm phép "cộng điểm" trên một đường cong dạng y² = x³ + ax + b, trên một trường hữu hạn. Cộng một điểm với chính nó k lần thì dễ. Biết điểm kết quả mà tìm lại k thì khó, theo đúng kiểu logarit rời rạc.
Ưu điểm lớn: bài toán trên đường cong khó hơn nhiều với cùng độ dài khóa, vì chưa ai tìm được thuật toán tắt nào tốt như với số nguyên. Khóa ngắn hơn, tính nhanh hơn:
| Độ an toàn (bit) | Đối xứng | RSA / DH | Đường cong elliptic |
|---|---|---|---|
| 112 | 3DES | 2048 | 224 |
| 128 | AES-128 | 3072 | 256 (P-256, X25519, Ed25519) |
| 192 | AES-192 | 7680 | 384 |
| 256 | AES-256 | 15360 | 512 |
Bảng theo khuyến nghị của NIST. RSA tăng gấp đôi độ an toàn thì khóa dài gấp 5 lần, còn ECC chỉ gấp đôi. Các đường cong dùng nhiều:
- P-256 (secp256r1): tiêu chuẩn NIST, chứng chỉ TLS của viet.sarl dùng nó.
- Curve25519 của Daniel Bernstein: thiết kế để khó dùng sai, chạy cùng thời gian với mọi đầu vào. X25519 dùng nó để thỏa thuận khóa, Ed25519 để ký.
- secp256k1: Bitcoin và Ethereum dùng. Một "ví tiền" thực chất chỉ là một khóa bí mật trên đường cong này.
Chữ ký số
Ký không phải là "mã hóa bằng khóa bí mật". Thuật toán ký nhận bản băm của dữ liệu (bài 2) và khóa bí mật, cho ra một chữ ký. Ai có khóa công khai cũng kiểm tra được. Chữ ký chứng minh hai điều: dữ liệu không bị sửa, và người ký có khóa bí mật.
Ba thuật toán ký hay gặp: RSA-PSS, ECDSA và Ed25519. ECDSA có một điểm yếu nổi tiếng: mỗi lần ký cần một số ngẫu nhiên mới, và nếu số này lặp lại hoặc đoán được, khóa bí mật bị lộ. Sony PlayStation 3 mất khóa ký năm 2010 vì dùng một số cố định cho mọi chữ ký. Ed25519 tránh hẳn chuyện này bằng cách sinh số đó một cách tất định từ khóa và thông điệp. Đó là lý do nó được khuyên dùng cho hệ thống mới.
Đo: RSA so với đường cong elliptic
Mỗi thao tác chạy hàng trăm tới hàng nghìn lần trên một lõi, ký trên thông điệp 100 byte:
| Thuật toán | Tạo khóa | Ký | Kiểm chữ ký | Kích thước chữ ký |
|---|---|---|---|---|
| RSA-2048 | 14 ms (trung vị, 12–67 ms) | 0,299 ms | 0,010 ms | 256 B |
| RSA-3072 | 127 ms (53–171 ms) | 0,876 ms | 0,019 ms | 384 B |
| ECDSA P-256 | 0,006 ms | 0,011 ms | 0,033 ms | 70 B |
| Ed25519 | 0,013 ms | 0,013 ms | 0,032 ms | 64 B |
| X25519 (thỏa thuận khóa) | 0,015 ms mỗi lần | 32 B khóa công khai |
Ba điều rút ra:
- Tạo khóa RSA rất chậm và thất thường, vì phải tìm ngẫu nhiên các số nguyên tố lớn: RSA-4096 mất từ 120 tới 750 ms trong năm lần đo. Tạo khóa đường cong elliptic chỉ là chọn một số ngẫu nhiên, nhanh hơn hàng nghìn lần. Điều này quan trọng vì TLS và SSH tạo một cặp khóa tạm mới cho mỗi kết nối.
- Ký bằng Ed25519 nhanh hơn RSA-2048 khoảng 23 lần, và chữ ký ngắn hơn 4 lần.
- Kiểm chữ ký RSA lại nhanh nhất (e = 65537 là một số mũ nhỏ). Vì vậy RSA vẫn hợp với việc "ký một lần, kiểm hàng triệu lần", như chữ ký của CA gốc trên chứng chỉ.
Máy tính lượng tử và ML-KEM
Năm 1994, Peter Shor chỉ ra rằng một máy tính lượng tử đủ lớn có thể phân tích thừa số và giải logarit rời rạc trong thời gian đa thức. Nghĩa là RSA, Diffie-Hellman và mọi đường cong elliptic đều sẽ bị phá. Mã hóa đối xứng và hàm băm thì chỉ yếu đi một nửa số bit, nên AES-256 vẫn an toàn.
Chưa ai có máy tính lượng tử đủ lớn. Nhưng có một mối đe dọa đã bắt đầu từ hôm nay: "thu trước, giải mã sau". Kẻ ghi lại lưu lượng TLS hôm nay có thể giải mã nó sau 10–15 năm nữa. Dữ liệu cần giữ bí mật lâu như hồ sơ y tế hay bí mật quốc gia đã có nguy cơ ngay từ bây giờ.
Năm 2024, NIST chuẩn hóa thuật toán hậu lượng tử đầu tiên cho việc trao đổi khóa: ML-KEM (trước đây tên là Kyber). Nó dựa trên một bài toán về lưới (lattice) mà chưa ai biết cách giải nhanh, kể cả bằng máy tính lượng tử. Thử ML-KEM-768 bằng cryptography:
k = mlkem.MLKEM768PrivateKey.generate()
shared, ciphertext = k.public_key().encapsulate() # bên gửi
k.decapsulate(ciphertext) == shared # True: bên nhận ra cùng bí mật
| X25519 | ML-KEM-768 | |
|---|---|---|
| Khóa công khai | 32 B | 1.184 B |
| Dữ liệu gửi lại | 32 B | 1.088 B |
| Tạo khóa / bọc / mở | ~0,015 ms | 0,035 / 0,009 / 0,013 ms |
ML-KEM nhanh ngang đường cong elliptic. Cái giá là kích thước: hơn 1 KB mỗi chiều thay vì 32 byte.
Vì ML-KEM còn mới, trình duyệt và máy chủ dùng kiểu lai: chạy cả X25519 lẫn ML-KEM, rồi trộn hai bí mật lại. Muốn phá kết nối phải phá cả hai. Kiểm tra viet.sarl bằng openssl s_client:
Negotiated TLS1.3 group: X25519MLKEM768
Kết nối tới trang này đã được bảo vệ trước máy tính lượng tử. Cloudflare bật sẵn, chủ trang không phải làm gì. Bài 8 sẽ cho thấy OpenSSH cũng đã chuyển sang kiểu lai tương tự.
Chữ ký hậu lượng tử (ML-DSA, SLH-DSA) cũng đã được chuẩn hóa, nhưng việc chuyển đổi chậm hơn. Chữ ký và khóa của chúng lớn hơn nhiều, trong khi một chứng chỉ TLS có tới vài chữ ký. Hơn nữa, chữ ký chỉ cần an toàn tại thời điểm kiểm tra, nên không bị "thu trước, giải mã sau".
Tóm lại
- Mật mã khóa công khai giải bài toán trao khóa: khóa công khai đưa cho cả thế giới, khóa bí mật giữ riêng. Nó dùng cho mã hóa, chữ ký và thỏa thuận khóa.
- RSA dựa vào phân tích thừa số. Tính bằng tay: n = 3233, e = 17, d = 2753. RSA thật phải có padding: OAEP khi mã hóa, PSS khi ký.
- Diffie-Hellman cho hai bên chung bí mật qua kênh công khai, nhưng phải có chữ ký đi kèm để chống kẻ đứng giữa.
- Đường cong elliptic: 256 bit an toàn ngang RSA-3072. Ký bằng Ed25519 nhanh hơn RSA-2048 khoảng 23 lần, chữ ký 64 B. Kiểm chữ ký RSA lại nhanh nhất.
- Máy tính lượng tử sẽ phá RSA và ECC. ML-KEM đã được chuẩn hóa, và viet.sarl đang dùng X25519MLKEM768 (lai).
Bài tiếp theo ghép mọi thứ từ đầu seri lại thành một giao thức thật: TLS 1.3. Từng bước bắt tay, chứng chỉ và chuỗi tin cậy, Let's Encrypt, và hai đoạn TLS phía sau ổ khóa của viet.sarl.