Bảo Mật Khi Gửi Mật Khẩu Đăng Nhập
Bài viết này thực ra được khởi thảo từ tháng 10 năm ngoái, khi tôi đang say sưa với loạt phim “24 Giờ Chống Khủng Bố”. Những suy nghĩ kỳ lạ liên tục hiện lên trong đầu: Liệu thế giới này có phải đang đầy rẫy gián điệp? Liệu có phương thức liên lạc nào thực sự an toàn? Trước khi chúng ta sở hữu được phần cứng hỗ trợ mã hóa lượng tử, có lẽ thứ duy nhất có thể tin tưởng chính là các thuật toán mã hóa dựa trên nền tảng toán học.
Bài viết lúc đó dài đến mức bản thân tôi cũng cảm thấy quá đỗi viển vông, nên đã không công bố. Tuy nhiên trong buổi họp tuần này, vấn đề bảo mật khi gửi mật khẩu đăng nhập trong game lại được đặt lên bàn cân. Một đồng nghiệp đã được phân công nghiên cứu sâu hơn về chủ đề này, nhân dịp này tôi xin ghi lại một số suy nghĩ cũ để làm tư liệu
Điều cơ bản nhất cần lưu ý: Trong quá trình đăng nhập, tuyệt đối không được để thông tin tên người dùng và mật khẩu ở dạng rõ (plaintext) lộ ra ngoài mạng internet. Đây là yêu cầu tối thiểu, nhưng đáng tiếc là các sản phẩm trước đây của chúng ta chưa làm tốt. Nhiều trường hợp chỉ sử dụng các thuật toán mã hóa tự thiết kế để mã hóa thông tin, sau đó server giải mã bằng cùng phương pháp đó.
Hình thức này phụ thuộc hoàn toàn vào việc phần mềm client không bị hacker reverse-engineering. Một khi kẻ xấu đã bẻ khóa được thuật toán, chỉ cần nghe lén luồng dữ liệu truyền đi là có thể khôi phục mật khẩu người dùng.
Công nghệ vật lý ngăn chặn nghe lén đường truyền trong tương lai gần gần như không khả thi với đại chúng. Ngay cả khi chúng ta hoàn toàn tin tưởng thiết bị client không bị can thiệp, cũng không thể biết liệu dữ liệu truyền đi có bị theo dõi hay không. Vì vậy, chúng ta chỉ còn biết dựa vào toán học để bảo vệ thông tin.
SSL chính là công cụ lý tưởng cho việc này, nhưng vì nhiều lý do khách quan, hiện tại chúng ta chưa thể triển khai trong hệ thống game client/server. Vậy tại sao không tự xây dựng một giải pháp tạm thời trước? Mục tiêu trước mắt đơn giản chỉ là đảm bảo người dùng có thể gửi thông tin đăng nhập một cách an toàn.
Thuật toán nổi tiếng nhất trong lĩnh vực này là RSA. Bất kỳ sinh viên ngành công nghệ thông tin nào cũng từng nghe qua. Tôi lần đầu biết đến RSA vào thời điểm chuẩn bị thi đại học, khi đọc cuốn “Con Đường Tương Lai” của Bill Gates. Phần giới thiệu về hệ thống mã hóa bất đối xứng (public-private key) khiến tôi vô cùng hứng thú. Sau này vào đại học, tôi đã dành thời gian nghiên cứu các tài liệu chuyên sâu tại thư viện trường để hiểu rõ hơn về RSA.
Có thể hình dung RSA như một chiếc hộp có khóa đặc biệt: Chỉ người tạo hộp mới mở được, nhưng bất kỳ ai cũng có thể bỏ vật vào qua khe hở mà không cần chìa khóa. Nếu A muốn nhận thông tin bảo mật từ B, A sẽ tạo một chiếc hộp RSA gửi cho B. B bỏ thông tin vào rồi gửi lại cho A, lúc này chỉ A mới có thể mở hộp lấy thông tin.
Tuy nhiên RSA có nhược điểm về tốc độ tính toán. Việc thực hiện các phép toán lũy thừa số lớn tiêu tốn nhiều tài nguyên hệ thống. Vì vậy RSA không phù hợp với các luồng dữ liệu có băng thông cao. Tiếp tục với ẩn dụ chiếc hộp, RSA giống như một chiếc hộp an toàn nhưng việc đóng/mở rất phức tạp. Do đó trong thực tế, người ta thường dùng RSA để mã hóa một khóa phiên (session key) ngẫu nhiên, sau đó dùng khóa này với thuật toán đối xứng (như DES) để truyền dữ liệu.
Vậy liệu RSA có phù hợp để mã hóa thông tin đăng nhập người dùng? Về lý thuyết hoàn toàn khả thi vì lượng dữ liệu nhỏ. Tuy nhiên chúng ta cần xem xét các phương án khác.
Vấn đề cốt lõi của RSA nằm ở việc nếu có đủ năng lực tính toán, về mặt lý thuyết hoàn toàn có thể trích xuất private key từ public key. Vì vậy các hệ thống RSA đều thiết lập thời hạn sử dụng cho cặp khóa. Giả sử mỗi lần truyền thông, chúng ta dùng RSA để mã hóa một khóa phiên ngẫu nhiên, sau đó dùng khóa này với DES để truyền dữ liệu - trong một khoảng thời gian nhất định, dữ liệu này được coi là an toàn.
Nhưng nếu có kẻ xấu ghi lại toàn bộ luồng dữ liệu từ đầu? Dù lúc đó chúng chưa thể giải mã, nhưng với việc tính toán không ngừng nghỉ để phân tích số nguyên lớn, đến một ngày nào đó khi private key bị bẻ khóa, toàn bộ dữ liệu lịch sử sẽ bị phơi bày. Ngay cả khi bạn đã thay thế cặp khóa mới trước thời hạn hết hạn, dữ liệu cũ vẫn có nguy cơ bị lộ. Đặc biệt, việc sử dụng public key càng nhiều sẽ càng làm tăng khả năng bị phân tích.
Giải pháp? Nếu muốn tránh việc toàn bộ lịch sử bị giải mã cùng lúc, chúng ta có thể thay đổi cặp khóa định kỳ. Tuy nhiên việc tạo cặp khóa RSA tốn kém tài nguyên tính toán.
May mắn thay, các nhà toán học đã tìm ra giải pháp ưu việt hơn - giao thức trao đổi khóa Diffie-Hellman. Giao thức này cho phép A và B tạo ra một khóa chung mà không cần truyền trực tiếp khóa qua kênh không an toàn. Cụ thể:
- A tạo một “chiếc hộp Diffie-Hellman” đặc biệt, dùng một số ngẫu nhiên làm khóa và gửi cho B.
- B nhận hộp, thêm khóa riêng của mình vào, sau đó gửi lại cho A.
- Nhờ tính chất toán học đặc biệt, cả A và B đều có thể tính ra cùng một khóa chung dựa trên hai khóa riêng này.
Khác với RSA, mỗi lần trao đổi khóa Diffie-Hellman đều tạo ra một khóa phiên mới. Dù việc tạo “chiếc hộp” ban đầu tốn kém (phải sinh số nguyên tố và căn nguyên thủy), nhưng việc “thêm khóa” vào hộp lại rất nhẹ nhàng. Điều này cho phép mỗi phiên truyền thông đều có khóa riêng, đảm bảo ngay cả khi một phiên bị bẻ khóa, các phiên khác vẫn an toàn.
Tuy nhiên giao thức này cũng có điểm yếu: Không thể nhúng thông tin nhận dạng người dùng vào khóa, khiến nó dễ bị tấn công man-in-the-middle. Giải pháp kết hợp Diffie-Hellman với RSA hoặc các giao thức cải tiến như OAKLEY sẽ giúp xác thực danh tính người dùng, nhưng đây là chủ đề cho một bài viết khác.
Hai năm trước, khi chơi với điện thoại Palm, tôi từng ấp ủ ý tưởng xây dựng ứng dụng nhắn tin mã hóa đơn giản. Hai người bạn chỉ cần gặp mặt trực tiếp để trao đổi khóa bí mật qua cổng hồng ngoại,