Lỗ hổng khóa AES-256 trong PDF: chỉ còn 9 bit ngẫu nhiên thực sự

Phần mềm28 tháng 10, 2026·6 phút đọc

Nghiên cứu mới phát hiện nhiều trình đọc và chỉnh sửa PDF như MuPDF và Foxit tạo khóa mã hóa AES-256 từ nguồn ngẫu nhiên yếu, khiến khóa chỉ còn vài trăm đến vài tỷ khả năng. Điều này cho phép giải mã tài liệu có mật khẩu chỉ trong vài mili giây đến vài giây.

Lỗ hổng khóa AES-256 trong PDF: chỉ còn 9 bit ngẫu nhiên thực sự

Lỗ hổng khóa AES-256 trong PDF: khi "bảo mật 256 bit" chỉ còn 9 bit ngẫu nhiên thực sự

Một nghiên cứu mới công bố trên v12.sh đã hé lộ một vấn đề nghiêm trọng trong cách nhiều phần mềm PDF tạo khóa mã hóa. Thay vì sinh khóa AES-256 thực sự ngẫu nhiên, MuPDF và Foxit lại dựa vào nguồn ngẫu nhiên cực kỳ yếu — khiến khóa chỉ còn vài trăm đến vài tỷ khả năng có thể duyệt hết.

Hệ quả là những tài liệu PDF được cho là "bảo vệ bằng mật khẩu" vẫn có thể bị giải mã mà không cần biết mật khẩu, chỉ trong vài mili giây với máy tính thông thường.

Minh họa mã hóa PDFMinh họa mã hóa PDF

Cơ chế mã hóa PDF hoạt động thế nào?

Khi một file PDF được mã hóa bằng AES-256, mật khẩu không trực tiếp mã hóa nội dung. Thay vào đó, phần mềm sinh ra một khóa file ngẫu nhiên 32 byte để mã hóa các chuỗi và luồng dữ liệu trong tài liệu. Mật khẩu kết hợp với salt được băm để tạo ra một khóa khác, dùng để mã hóa chính khóa file 32 byte đó.

Cơ chế này đã trải qua nhiều phiên bản. Phiên bản 6 hiện hành băm mật khẩu một cách chậm có chủ đích nhằm chống tấn công brute-force, còn phiên bản 5 thì không.

Vấn đề nằm ở chỗ: toàn bộ độ bảo mật phụ thuộc vào việc khóa file 32 byte phải thực sự ngẫu nhiên. Nếu nguồn sinh số ngẫu nhiên yếu, kẻ tấn công có thể đoán ra khóa mà không cần mật khẩu.

MuPDF: chỉ 9 bit thực sự ảnh hưởng đến khóa

Các phiên bản MuPDF trước 1.28.5 sử dụng họ hàm drand48 để sinh số ngẫu nhiên. Bộ sinh này được khởi tạo một lần duy nhất bằng thời gian epoch tính theo giây hiện tại.

Đây là một bộ sinh đồng dư tuyến tính 48 bit. Khi MuPDF cần tạo khóa, nó lấy từng byte đầu ra từ bit 17 đến bit 24 của trạng thái bộ sinh. Vì phép nhân và phép cộng chỉ có thể "mang" bit lên cao chứ không xuống thấp, mọi bit phía trên bit 24 đều bị loại bỏ.

Kết quả: chỉ còn 25 bit có thể ảnh hưởng đến đầu ra, trong đó 16 bit thấp luôn được khởi tạo cố định là 0x330e. Điều này đồng nghĩa chỉ có 9 bit từ timestamp thực sự tạo ra sự khác biệt.

Những timestamp chênh lệch nhau đúng 512 giây sẽ tạo ra dòng byte giống hệt nhau.

Với không gian khóa nhỏ như vậy, việc brute-force toàn bộ 512 khóa tiềm năng chỉ mất khoảng 11 mili giây bằng một đoạn Python chưa tối ưu. Nghiên cứu còn chỉ ra rằng /ID của tài liệu — vốn được lưu dạng văn bản thuần trong trailer — cũng sinh từ cùng bộ ngẫu nhiên này, nên có thể dùng làm bước kiểm tra bổ sung.

Foxit: khóa là hàm băm của timestamp

Foxit PDF Reader phiên bản 2026.1.3.36551 lại tạo khóa file bằng cách băm thời gian hiện tại, 32 byte chưa khởi tạo và một chuỗi hằng. Trên thực tế, 32 byte "chưa khởi tạo" đó luôn bằng 0 và không đóng góp gì cho độ ngẫu nhiên.

Công thức tương đương:

key = SHA-256(timestamp_4byte || 32_byte_zero || "there")

Điều đáng chú ý là khối /Perms chứa dữ liệu đã biết trước, nên chỉ cần một phép băm và một lần giải mã khối AES là có thể kiểm tra xem khóa đoán được có đúng không — không cần băm mật khẩu, không cần giải mã toàn bộ tài liệu.

Bản triển khai bằng Rust chạy đa luồng trên chip Ryzen 9 7950X cho tốc độ 370–376 triệu timestamp mỗi giây. Kết quả đo:

  • Tìm timestamp của 10 năm trước (315 triệu khả năng): 0,84 giây
  • Quét toàn bộ timestamp từ năm 1970 đến nay: 4,85 giây

Với tài liệu được tạo gần đây, thời gian thực tế chỉ tính bằng mili giây.

Gốc rễ: một dòng code từ năm 2014

Điều đáng nói là mẫu code này xuất hiện trong nhiều bộ công cụ PDF khác nhau. Nó đã có mặt trong commit công khai đầu tiên của PDFium từ tháng 5/2014, và phần header của file ghi rõ nguồn gốc từ Foxit. Các trình chỉnh sửa khác có thể đã kế thừa lỗi này thông qua PDFium.

Biến m_EncryptKey trong code — chính là 32 byte giữa trong công thức — thực chất là nội dung chưa khởi tạo của buffer khóa đang được băm vào chính nó, và mặc định bằng 0.

OpenPDF và iText cũng không thoát

OpenPDF 3.0.5 và iText 9.7.1 đều định tuyến nguồn ngẫu nhiên mật mã qua lớp IVGenerator, một luồng ARC4 được khởi tạo bằng timestamp mili giây cộng với dung lượng bộ nhớ trống của JVM.

Cùng một luồng này vừa sinh khóa file, vừa sinh 32 byte salt được lưu dạng văn bản thuần trong /U và /O. Kẻ tấn công có thể kiểm tra các phỏng đoán timestamp và bộ nhớ trống dựa vào các salt này, rồi phục hồi khóa từ những byte liền kề.

Dù mức độ biến thiên ở đây lớn hơn nhiều so với MuPDF và Foxit, nó vẫn cho thấy cùng một vấn đề: lời hứa bảo mật 256 bit bị phá vỡ bởi nguồn ngẫu nhiên không an toàn.

Ý nghĩa với người dùng Việt Nam

Với các doanh nghiệp và cá nhân tại Việt Nam thường dùng PDF để lưu hợp đồng, tài liệu nội bộ hay hồ sơ có thông tin nhạy cảm, đây là cảnh báo đáng lưu tâm. Việc đặt mật khẩu cho file PDF không đồng nghĩa với việc tài liệu thực sự an toàn nếu công cụ tạo file dùng nguồn ngẫu nhiên yếu.

Các chuyên gia khuyến nghị:

  • Cập nhật phần mềm PDF lên phiên bản mới nhất, đặc biệt là MuPDF (lỗi đã được vá từ bản 1.28.5)
  • Ưu tiên các giải pháp mã hóa container (như file nén có mật khẩu với thuật toán mạnh) thay vì chỉ dựa vào mã hóa PDF gốc
  • Với tài liệu thực sự nhạy cảm, nên dùng công cụ mã hóa chuyên dụng thay vì tin tưởng hoàn toàn vào tính năng bảo vệ của trình đọc PDF

Kết luận

Cả ba trường hợp — MuPDF, Foxit, và các thư viện Java như OpenPDF/iText — đều cho thấy một nghịch lý: nâng cấp lên AES-256 tưởng chừng là bước tiến bảo mật so với các khóa cũ dẫn xuất từ mật khẩu người dùng, nhưng trên thực tế lại tạo ra khóa yếu hơn.

Các lỗ hổng đã được báo cáo cho nhà cung cấp và MuPDF đã vá. Tuy nhiên, những file PDF được tạo trước đó không thể được bảo vệ hồi tố — nghĩa là các tài liệu cũ vẫn có thể bị ảnh hưởng. Nhóm nghiên cứu đã công bố công cụ giải mã cho cả MuPDF và Foxit để cộng đồng kiểm chứng.

Chia sẻ:FacebookX
Nội dung tổng hợp bằng AI, mang tính tham khảo. Xem bài gốc ↗