Văn hay trong hiện tại, chữ tốt ở tương lai

Thuật toán Double Ratchet – Cơ chế mã hóa động từng tin nhắn

Double Ratchet là thuật toán kết hợp bộ tăng khóa đối xứng và Diffie–Hellman để cung cấp đồng thời bảo mật chuyển tiếp và khả năng phục hồi sau xâm phạm.

44 phút đọc.

0 lượt xem.

Thuật toán Double Ratchet là cơ chế mã hóa tiên tiến được thiết kế đặc biệt cho các ứng dụng nhắn tin an toàn, với mục tiêu đảm bảo tính bảo mật tối đa trong quá trình trao đổi thông điệp.

Giới thiệu

Thuật toán Double Ratchet được thiết kế để hai bên trao đổi tin nhắn được mã hóa dựa trên một khóa bí mật chung. Thông thường, các bên sử dụng một giao thức thỏa thuận khóa như X3DH để đồng thuận về khóa bí mật chung trước, sau đó dùng Double Ratchet để gửi và nhận tin nhắn được mã hóa trong suốt phiên liên lạc.

Nguyên lý hoạt động của thuật toán dựa trên việc liên tục tạo ra các khóa mới trong mỗi lần trao đổi tin nhắn, qua đó đảm bảo rằng ngay cả khi một khóa bị xâm phạm, các thông điệp trước và sau vẫn được bảo vệ an toàn. Cơ chế ratchet kép cho phép các bên liên tục làm mới khóa mã hóa một cách độc lập và an toàn. Tên gọi ratchet lấy cảm hứng từ cơ cấu bánh cóc cơ học – một thiết bị chỉ quay theo một chiều và không thể đảo ngược – để mô tả cách các khóa được tiến về phía trước nhưng không thể tính ngược lại.

Các bên tạo ra khóa mới cho mỗi tin nhắn để đảm bảo rằng các khóa trước không thể bị tính toán lại từ các khóa sau. Đồng thời, các bên cũng gửi các giá trị công khai Diffie–Hellman gắn kèm với mỗi tin nhắn. Kết quả của các phép tính Diffie–Hellman được trộn vào các khóa dẫn xuất, đảm bảo rằng các khóa sau không thể bị tính toán lại từ các khóa trước. Sự kết hợp hai hướng bảo vệ này – tiến và lùi – tạo nên cặp tính chất nền tảng của thuật toán: bảo mật chuyển tiếp (forward secrecy) và khả năng phục hồi sau xâm phạm (break-in recovery).

Ứng dụng phổ biến nhất của Double Ratchet được thể hiện trong ứng dụng nhắn tin Signal. Thuật toán được sử dụng để bảo vệ các cuộc trò chuyện, đảm bảo rằng ngay cả khi một phần của quá trình liên lạc bị xâm phạm, toàn bộ lịch sử tin nhắn vẫn được giữ an toàn. Về sau, các nền tảng lớn như WhatsApp và Facebook Messenger Secret Conversations cũng tích hợp thuật toán này. Đây là minh chứng rõ ràng rằng Double Ratchet không chỉ là một thành tựu học thuật mà còn là giải pháp thực tiễn đủ vững chắc để bảo vệ hàng tỷ cuộc trò chuyện mỗi ngày.

Kiến thức cơ bản

Để hiểu cách Double Ratchet vận hành, cần nắm rõ ba khái niệm nền tảng: chuỗi KDF – cơ chế sinh khóa tuần tự một chiều; bộ tăng khóa đối xứng – cách mỗi tin nhắn nhận được khóa mã hóa riêng; và mô hình phiên – cách hai bên tổ chức và đồng bộ trạng thái mã hóa trong suốt quá trình trao đổi.

Chuỗi KDF

Signal định nghĩa KDF là một hàm mật mã nhận vào một khóa KDF bí mật và ngẫu nhiên cùng với một số dữ liệu đầu vào, sau đó trả về dữ liệu đầu ra không thể phân biệt với dữ liệu ngẫu nhiên miễn là khóa không bị lộ – tức là một KDF phải đáp ứng yêu cầu của một PRF (pseudorandom function) mật mã. Nếu khóa không phải là bí mật và ngẫu nhiên, KDF vẫn phải cung cấp một hàm băm mật mã an toàn từ khóa và dữ liệu đầu vào. Các cấu trúc HMAC và HKDF, khi được khởi tạo bằng một thuật toán băm an toàn như SHA256 hay SHA512, đều đáp ứng định nghĩa của KDF này.

Signal sử dụng thuật ngữ chuỗi KDF khi một phần dữ liệu đầu ra từ KDF được sử dụng làm khóa đầu ra, còn một phần khác được sử dụng để thay thế khóa KDF – sau đó khóa KDF mới này có thể tiếp tục được dùng với đầu vào khác. Mỗi lần gọi KDF trong chuỗi được gọi là một bước tăng (ratchet step). Tính tuần tự một chiều của chuỗi KDF là yếu tố then chốt: biết khóa KDF tại bước t cho phép tính tất cả các đầu ra từ bước t trở đi, nhưng không thể tính ngược về bước t–1.

Chuỗi KDF có ba thuộc tính bảo mật cốt lõi. Thứ nhất là khả năng chống chịu (resilience): các khóa đầu ra trông giống ngẫu nhiên đối với kẻ tấn công không biết khóa KDF, ngay cả khi kẻ tấn công có thể kiểm soát dữ liệu đầu vào của KDF. Thứ hai là bảo mật tiến về phía trước (forward security): các khóa đầu ra trong quá khứ vẫn trông như ngẫu nhiên đối với kẻ tấn công ngay cả khi tại một thời điểm nào đó kẻ tấn công có thể biết khóa KDF hiện tại – điều này ngăn chặn việc giải mã hồi tố toàn bộ lịch sử hội thoại. Thứ ba là khả năng phục hồi sau xâm nhập (break-in recovery): các khóa đầu ra trong tương lai trông như ngẫu nhiên đối với kẻ tấn công ngay cả khi kẻ đó từng biết khóa KDF, miễn là các đầu vào tương lai có đủ độ ngẫu nhiên được bổ sung. Thuộc tính thứ ba này là điều khiến Double Ratchet vượt trội so với các sơ đồ sinh khóa đơn thuần – hệ thống không chỉ bảo vệ quá khứ mà còn tự phục hồi bảo mật về phía tương lai.

Trong một phiên Double Ratchet giữa Mỹ Anh và Đan Nguyên, mỗi bên lưu trữ một khóa KDF cho ba chuỗi: chuỗi gốc (root chain), chuỗi gửi (sending chain) và chuỗi nhận (receiving chain). Chuỗi gửi của Mỹ Anh khớp với chuỗi nhận của Đan Nguyên và ngược lại. Khi hai bên trao đổi tin nhắn, họ cũng trao đổi các khóa công khai Diffie–Hellman mới, và các bí mật đầu ra của Diffie–Hellman trở thành đầu vào cho chuỗi gốc. Các khóa đầu ra từ chuỗi gốc lần lượt trở thành các khóa KDF mới cho chuỗi gửi và chuỗi nhận – đây là bộ tăng Diffie–Hellman. Chuỗi gửi và chuỗi nhận được cập nhật mỗi khi tin nhắn được gửi và nhận, và các khóa đầu ra của chúng được dùng để mã hóa và giải mã tin nhắn – đây là bộ tăng khóa đối xứng.

Bộ tăng khóa đối xứng (Symmetric key ratchet)

Mỗi tin nhắn được gửi hoặc nhận đều được mã hóa bằng một khóa tin nhắn duy nhất. Các khóa tin nhắn này là các khóa đầu ra từ chuỗi KDF gửi và nhận. Các khóa KDF của những chuỗi này được gọi là khóa chuỗi (chain keys). Đặc điểm quan trọng là các đầu vào KDF cho chuỗi gửi và chuỗi nhận là cố định – không có yếu tố ngẫu nhiên bên ngoài được đưa vào – vì vậy những chuỗi này không cung cấp khả năng phục hồi sau xâm nhập một mình. Vai trò của bộ tăng khóa đối xứng là đơn giản và cụ thể: đảm bảo mỗi tin nhắn được mã hóa bằng một khóa duy nhất có thể bị xóa sau khi mã hóa hoặc giải mã, qua đó cung cấp bảo mật chuyển tiếp trong phạm vi một chuỗi gửi hoặc nhận.

Việc tính toán khóa chuỗi tiếp theo và khóa tin nhắn từ một khóa chuỗi nhất định là một bước tăng trong bộ tăng khóa đối xứng. Sơ đồ hoạt động có thể hình dung như sau: từ khóa chuỗi CKn, hàm KDF_CK tạo ra hai đầu ra – khóa chuỗi CKn+1 và khóa tin nhắn MKn. Khóa chuỗi CKn+1 sẽ được lưu để dùng cho bước tăng tiếp theo, trong khi CKn bị xóa. Khóa tin nhắn MKn được dùng để mã hóa tin nhắn thứ n, và có thể bị xóa ngay sau đó hoặc giữ lại tạm thời nếu cần xử lý tin nhắn đến không theo thứ tự.

Vì các khóa tin nhắn không được sử dụng để tạo ra bất kỳ khóa nào khác trong chuỗi, chúng có thể được lưu trữ độc lập mà không ảnh hưởng đến tính bảo mật của các khóa tin nhắn khác. Tính chất này – gọi là sự độc lập của khóa tin nhắn – cực kỳ hữu ích trong thực tế. Nếu tin nhắn thứ n bị mất hoặc đến trễ, người nhận có thể tiếp tục tiến chuỗi nhận để xử lý các tin nhắn n+1, n+2 mà không mất khả năng giải mã tin nhắn n khi nó cuối cùng đến. Người nhận chỉ cần lưu trữ khóa tin nhắn MKn để dùng sau, trong khi toàn bộ chuỗi vẫn tiến về phía trước bình thường. Giới hạn số lượng khóa tin nhắn được lưu trữ chờ – hằng số MAX_SKIP – ngăn chặn tấn công từ chối dịch vụ bằng cách gửi ồ ạt tin nhắn với số thứ tự rất lớn, buộc người nhận phải tính và lưu trữ quá nhiều khóa.

Mô hình phiên và vai trò hai bên

Trước khi khởi tạo phiên Double Ratchet, cả hai bên phải thống nhất về một khóa bí mật chung 32 byte SK thông qua một giao thức thỏa thuận khóa như X3DH. Ngoài SK, họ cũng cần thống nhất về khóa công khai Ratchet ban đầu của một bên – thường là Đan Nguyên, người ở trạng thái bị động (responder), chờ nhận tin nhắn đầu tiên. Trạng thái ban đầu không cân xứng giữa hai bên: Mỹ Anh, trong vai trò chủ động (initiator), có thể gửi tin nhắn ngay lập tức; còn Đan Nguyên không thể gửi cho đến khi nhận được ít nhất một tin nhắn từ Mỹ Anh.

Sự bất đối xứng ban đầu này xuất phát từ thiết kế của bộ tăng Diffie–Hellman: Mỹ Anh cần biết khóa công khai Ratchet của Đan Nguyên để thực hiện phép tính DH đầu tiên và tạo chuỗi gửi ban đầu. Đan Nguyên, ngược lại, chưa biết khóa Ratchet của Mỹ Anh và do đó chưa thể thiết lập chuỗi gửi của mình. Chỉ khi nhận được tin nhắn đầu tiên từ Mỹ Anh – mang theo khóa công khai Ratchet của cô – Đan Nguyên mới có đủ thông tin để thực hiện bước tăng DH đầu tiên, tạo chuỗi nhận và chuỗi gửi riêng của mình.

Trong mô hình phiên chuẩn, mỗi bên duy trì một đối tượng trạng thái (state object) chứa toàn bộ các biến cần thiết để mã hóa và giải mã tin nhắn: cặp khóa Ratchet hiện tại của bản thân, khóa công khai Ratchet của đối tác, khóa gốc, khóa chuỗi gửi và nhận, số thứ tự tin nhắn, và tập hợp khóa tin nhắn đang chờ xử lý. Trạng thái này thay đổi sau mỗi tin nhắn gửi hoặc nhận: các khóa chuỗi tiến một bước, và khi có khóa Ratchet mới từ đối tác, toàn bộ chuỗi gửi và nhận được thay thế thông qua bước tăng DH. Tính đúng đắn của giao thức phụ thuộc vào việc hai bên luôn giữ trạng thái nhất quán với nhau – nếu một bên mất đồng bộ do lỗi phần cứng hay khôi phục từ bản sao lưu cũ, các tin nhắn tiếp theo sẽ không giải mã được và cần cơ chế phục hồi ở tầng cao hơn như thuật toán Sesame.

Bộ tăng Diffie – Hellman (Diffie – Hellman ratchet)

Bộ tăng Diffie–Hellman giải quyết giới hạn cốt lõi của bộ tăng khóa đối xứng: nếu một kẻ tấn công đánh cắp các khóa chuỗi gửi và nhận của một bên, chúng có thể tính toán tất cả các khóa tin nhắn trong tương lai và giải mã toàn bộ tin nhắn sau đó. Để ngăn chặn điều này, Double Ratchet kết hợp bộ tăng khóa đối xứng với bộ tăng DH, cung cấp khả năng phục hồi sau xâm nhập mà bộ tăng đối xứng đơn thuần không thể đảm bảo.

Nguyên lý hoạt động của bộ tăng DH

Để triển khai bộ tăng DH, mỗi bên tạo một cặp khóa DH bao gồm khóa công khai và khóa riêng Diffie–Hellman, được gọi là cặp khóa tăng hiện tại (ratchet key pair). Mỗi tin nhắn từ một bên sẽ bắt đầu với một tiêu đề chứa khóa công khai tăng hiện tại của người gửi. Khi nhận được một khóa công khai tăng mới từ phía đối phương, một bước tăng DH sẽ được thực hiện, thay thế cặp khóa tăng hiện tại của bên nhận bằng một cặp khóa mới hoàn toàn.

Điều này tạo ra một hành vi ping-pong đặc trưng, trong đó các bên lần lượt thay thế cặp khóa tăng của mình. Hành vi này có hệ quả bảo mật quan trọng: một kẻ nghe lén có thể tạm thời xâm nhập vào một trong hai bên và biết được giá trị của khóa riêng tăng hiện tại, nhưng khóa riêng đó cuối cùng sẽ bị thay thế bởi một khóa mới không bị xâm nhập. Khi đó, phép tính DH giữa các cặp khóa tăng sẽ tạo ra một đầu ra DH mà kẻ tấn công không thể biết được – bởi đầu ra DH phụ thuộc vào cả khóa riêng của bên này lẫn khóa riêng tương ứng của bên kia, và kẻ tấn công chỉ biết một trong hai. Đây chính là cơ chế phục hồi sau xâm nhập: sau đủ nhiều bước trao đổi, bảo mật được khôi phục tự động ngay cả khi không có hành động thủ công từ người dùng.

Quá trình khởi tạo diễn ra không cân xứng: Mỹ Anh được khởi tạo với khóa công khai tăng của Đan Nguyên, nhưng khóa công khai tăng của Mỹ Anh vẫn chưa được Đan Nguyên biết. Là một phần của quá trình khởi tạo, Mỹ Anh thực hiện một phép tính DH giữa khóa riêng tăng của cô và khóa công khai tăng của Đan Nguyên để tạo đầu ra DH đầu tiên. Đầu ra này được đưa vào chuỗi gốc để tạo khóa gốc mới RK và khóa chuỗi gửi CKs đầu tiên cho Mỹ Anh. Các tin nhắn ban đầu của Mỹ Anh chứa khóa công khai tăng của cô; khi Đan Nguyên nhận được tin nhắn đầu tiên, anh thực hiện bước tăng DH đầu tiên của mình, tính đầu ra DH giống với đầu ra ban đầu của Mỹ Anh, rồi tạo cặp khóa tăng mới và tính đầu ra DH thứ hai. Chu trình trao đổi khóa công khai theo kiểu ping-pong này tiếp diễn trong suốt phiên liên lạc.

Chuỗi gốc và quản lý khóa

Thay vì lấy trực tiếp khóa chuỗi từ các đầu ra DH, các đầu ra DH được sử dụng làm đầu vào KDF cho một chuỗi gốc (root chain) riêng biệt, và các đầu ra KDF từ chuỗi gốc này mới được sử dụng làm khóa chuỗi gửi và nhận mới. Việc đưa thêm một lớp chuỗi KDF trung gian này vào giữa đầu ra DH và khóa chuỗi có tác dụng cải thiện đáng kể cả khả năng chống chịu lẫn khả năng phục hồi sau xâm nhập so với việc dùng đầu ra DH trực tiếp.

Chuỗi gốc được duy trì liên tục trong suốt phiên và chỉ tiến khi có bước tăng DH. Khóa gốc RK là trạng thái tích lũy của toàn bộ lịch sử trao đổi DH: mỗi đầu ra DH mới được trộn vào RK thông qua KDF_RK, tạo ra RK mới và một cặp khóa chuỗi gửi-nhận mới. Đầu ra thứ nhất của KDF_RK trong một bước tăng DH trở thành khóa chuỗi nhận mới (dùng cho chuỗi nhận tương ứng với đầu ra DH từ khóa Ratchet cũ của đối tác), và đầu ra thứ hai trở thành khóa chuỗi gửi mới (dùng cho chuỗi gửi với cặp khóa Ratchet vừa tạo mới). Do đó, một bước tăng DH đầy đủ bao gồm hai lần cập nhật chuỗi KDF gốc.

Giá trị RK sau mỗi bước tăng DH không thể suy ra từ các đầu ra DH đơn lẻ mà phụ thuộc vào toàn bộ chuỗi các đầu ra DH trước đó. Đây là một tính chất thiết yếu: ngay cả khi kẻ tấn công biết đầu ra DH của một bước cụ thể, họ vẫn không thể tính được RK tương ứng nếu không biết RK của bước trước. Điều này làm cho việc tấn công một phần chuỗi không đủ để phá vỡ bảo mật toàn phiên – kẻ tấn công cần biết toàn bộ lịch sử từ đầu phiên, hoặc biết khóa thiết bị tại một thời điểm cụ thể.

Xử lý tin nhắn đến không theo thứ tự (Out-of-order messages)

Trong môi trường mạng thực tế, tin nhắn có thể bị mất hoặc đến không theo thứ tự do các yếu tố như độ trễ mạng, gói tin bị hỏng hay sự cố tạm thời. Double Ratchet xử lý tình huống này một cách tường minh và toàn diện. Mỗi tiêu đề tin nhắn chứa hai giá trị bổ sung: số thứ tự của tin nhắn trong chuỗi gửi hiện tại (N = 0, 1, 2…) và độ dài của chuỗi gửi trước đó (PN). Giá trị PN cho phép người nhận biết bao nhiêu tin nhắn thuộc về chuỗi Ratchet cũ và bao nhiêu thuộc về chuỗi mới khi có bước tăng DH được kích hoạt.

Khi nhận được một tin nhắn kích hoạt bước tăng DH, người nhận tính số tin nhắn bị bỏ qua trong chuỗi cũ bằng PN trừ đi độ dài hiện tại của chuỗi nhận, và số tin nhắn bị bỏ qua trong chuỗi mới bằng giá trị N trong tiêu đề. Với mỗi tin nhắn bị bỏ qua, người nhận tính khóa tin nhắn tương ứng và lưu vào từ điển MKSKIPPED, lập chỉ mục theo cặp (khóa công khai Ratchet, số thứ tự N). Khi một tin nhắn bị bỏ qua cuối cùng đến, hàm TrySkippedMessageKeys kiểm tra từ điển và giải mã tin nhắn bằng khóa đã lưu. Cơ chế này đảm bảo không có tin nhắn nào bị mất vĩnh viễn chỉ vì thứ tự giao hàng không tuần tự, trong khi vẫn duy trì tất cả các tính chất bảo mật của giao thức. Giới hạn MAX_SKIP ngăn kẻ tấn công khai thác cơ chế này để gây tấn công từ chối dịch vụ bằng cách gửi tin nhắn với số thứ tự rất lớn, buộc người nhận phải tính và lưu trữ số lượng khóa không thực tế.

Triển khai thuật toán

Phần này mô tả đặc tả kỹ thuật đầy đủ của thuật toán Double Ratchet theo hướng dẫn của Signal Foundation, bao gồm tập hợp các hàm mật mã cần thiết, cấu trúc dữ liệu trạng thái, quy trình khởi tạo và các hàm mã hóa – giải mã cụ thể.

Một hằng số MAX_SKIP cũng cần được định nghĩa. Hằng số này chỉ định số lượng tối đa các khóa tin nhắn có thể bị bỏ qua trong một chuỗi. Giá trị này nên đủ lớn để chịu được việc mất hoặc trì hoãn tin nhắn thông thường, nhưng đủ nhỏ để ngăn chặn việc tính toán quá mức do một kẻ gửi độc hại gây ra.

Chức năng bên ngoài

Để khởi tạo Double Ratchet, cần định nghĩa các hàm sau.

– GENERATE_DH(): Trả về một cặp khóa Diffie–Hellman mới.

– DH(dh_pair, dh_pub): Trả về đầu ra của phép tính Diffie–Hellman giữa khóa riêng tư từ cặp khóa DH dh_pair và khóa công khai DH dh_pub. Nếu hàm DH từ chối các khóa công khai không hợp lệ, hàm này có thể ném ra một ngoại lệ để chấm dứt quá trình xử lý.

– KDF_RK(rk, dh_out): Trả về một cặp (khóa gốc 32 byte, khóa chuỗi 32 byte) làm đầu ra của việc áp dụng một hàm dẫn xuất khóa (KDF) với khóa gốc 32 byte rk lên đầu ra Diffie–Hellman dh_out.

– KDF_CK(ck): Trả về một cặp (khóa chuỗi 32 byte, khóa tin nhắn 32 byte) làm đầu ra của việc áp dụng một KDF với khóa chuỗi 32 byte ck lên một hằng số.

– ENCRYPT(mk, plaintext, associated_data): Trả về một bản mã hóa AEAD của plaintext bằng khóa tin nhắn mk. Trường associated_data được xác thực nhưng không được đưa vào bản mã. Vì mỗi khóa tin nhắn chỉ được sử dụng một lần, nonce AEAD có thể được xử lý theo nhiều cách: cố định ở một giá trị hằng số; được tạo ra từ mk cùng với một khóa mã hóa AEAD độc lập; được tạo ra như một đầu ra bổ sung từ KDF_CK; hoặc được chọn ngẫu nhiên và truyền đi.

– DECRYPT(mk, ciphertext, associated_data): Trả về bản giải mã AEAD của ciphertext bằng khóa tin nhắn mk. Nếu xác thực thất bại, một ngoại lệ sẽ được ném ra để chấm dứt quá trình xử lý.

– HEADER(dh_pair, pn, n): Tạo một tiêu đề tin nhắn mới chứa khóa công khai DH Ratchet từ cặp khóa trong dh_pair, độ dài chuỗi trước đó pn, và số thứ tự tin nhắn n. Đối tượng tiêu đề được trả về chứa khóa công khai Ratchet dh và các số nguyên pnn.

– CONCAT(ad, header): Mã hóa một tiêu đề tin nhắn thành một chuỗi byte có thể phân tích, thêm chuỗi byte ad vào phía trước, và trả về kết quả. Nếu ad không được đảm bảo là một chuỗi byte có thể phân tích, một giá trị độ dài nên được thêm vào đầu ra để đảm bảo đầu ra có thể được phân tích như một cặp duy nhất (ad, header).

Biến trạng thái

Mỗi bên theo dõi các biến trạng thái sau trong suốt vòng đời của phiên Double Ratchet. Các biến này tập thể tạo nên đối tượng trạng thái của mỗi bên.

– DHs: Cặp khóa Ratchet DH (khóa Ratchet gửi hoặc tự thân).

– DHr: Khóa công khai Ratchet DH (nhận hoặc từ xa).

– RK: Khóa gốc 32 byte.

– CKs, CKr: Các khóa chuỗi 32 byte dành cho việc gửi và nhận.

– Ns, Nr: Số thứ tự tin nhắn dành cho việc gửi và nhận.

– PN: Số lượng tin nhắn trong chuỗi gửi trước đó.

– MKSKIPPED: Từ điển chứa các khóa tin nhắn bị bỏ qua, được lập chỉ mục theo khóa công khai Ratchet và số thứ tự tin nhắn. Nếu có quá nhiều phần tử được lưu trữ, một ngoại lệ sẽ được ném ra.

Trong đoạn mã Python tiếp theo, các biến trạng thái được truy cập như là các thành viên của một đối tượng trạng thái.

Khởi tạo

Trước khi khởi tạo, cả hai bên phải sử dụng một giao thức thỏa thuận khóa để thống nhất về một khóa bí mật chung 32 byte (SK) và khóa công khai Ratchet của Đan Nguyên. Các giá trị này sẽ được sử dụng để khởi tạo khóa chuỗi gửi của Mỹ Anh và khóa gốc của Đan Nguyên. Các khóa chuỗi của Đan Nguyên và khóa chuỗi nhận của Mỹ Anh sẽ để trống, vì chúng sẽ được thiết lập trong bước Ratchet DH đầu tiên của mỗi bên.

(Giả sử Mỹ Anh bắt đầu gửi tin nhắn trước và Đan Nguyên không gửi tin nhắn cho đến khi nhận được một trong các tin nhắn của Mỹ Anh. Để cho phép Đan Nguyên gửi tin nhắn ngay sau khi khởi tạo, khóa chuỗi gửi của Đan Nguyên và khóa chuỗi nhận của Mỹ Anh có thể được khởi tạo bằng một khóa bí mật chung. Tuy nhiên, để đơn giản hóa, chúng ta sẽ không xem xét điều này thêm.)

Sau khi Mỹ Anh và Đan Nguyên đồng thuận về SK và khóa công khai Ratchet của Đan Nguyên, Mỹ Anh gọi RatchetInitMỹAnh và Đan Nguyên gọi RatchetInitĐanNguyên():

def RatchetInitMỹAnh(state, SK, DanNguyen_dh_public_key):
	state.DHs = GENERATE_DH()
	state.DHr = DanNguyen_dh_public_key
	state.RK, state.CKs = KDF_RK(SK, DH(state.DHs, state.DHr))
	state.CKr = None
	state.Ns = 0
	state.Nr = 0
	state.PN = 0
	state.MKSKIPPED = {}

def RatchetInitĐanNguyên(state, SK, DanNguyen_dh_key_pair):
	state.DHs = DanNguyen_dh_key_pair
	state.DHr = None
	state.RK = SK
	state.CKs = None
	state.CKr = None
	state.Ns = 0
	state.Nr = 0
	state.PN = 0
	state.MKSKIPPED = {}

Mã hóa tin nhắn

Hàm RatchetEncrypt() được gọi để mã hóa tin nhắn. Hàm này thực hiện một bước Ratchet với khóa đối xứng, sau đó mã hóa tin nhắn bằng khóa tin nhắn được tạo ra. Ngoài nội dung tin nhắn, hàm này còn nhận vào một chuỗi byte AD, chuỗi này sẽ được thêm vào phần tiêu đề để tạo dữ liệu liên kết cho quá trình mã hóa AEAD:

def RatchetEncrypt(state, plaintext, AD):
	state.CKs, mk = KDF_CK(state.CKs)
	header = HEADER(state.DHs, state.PN, state.Ns)
	state.Ns += 1
	return header, ENCRYPT(mk, plaintext, CONCAT(AD, header))

Giải mã tin nhắn

Hàm RatchetDecrypt() được gọi để giải mã tin nhắn. Hàm này thực hiện theo trình tự sau: nếu tin nhắn tương ứng với một khóa tin nhắn bị bỏ qua trước đó, hàm sẽ giải mã tin nhắn, xóa khóa đó và trả về nội dung; nếu một khóa Ratchet mới đã được nhận, hàm sẽ lưu các khóa tin nhắn bị bỏ qua từ chuỗi hiện tại rồi thực hiện bước Ratchet DH; sau đó hàm lưu các khóa tin nhắn bị bỏ qua từ chuỗi nhận, thực hiện bước tăng khóa đối xứng để dẫn xuất khóa tin nhắn liên quan và giải mã. Nếu có ngoại lệ xảy ra, tin nhắn bị loại bỏ và các thay đổi trạng thái không được lưu lại.

def RatchetDecrypt(state, header, ciphertext, AD):
	plaintext = TrySkippedMessageKeys(state, header, ciphertext, AD)
	if plaintext != None:
		return plaintext
	if header.dh != state.DHr:
		SkipMessageKeys(state, header.pn)
		DHRatchet(state, header)
	SkipMessageKeys(state, header.n)
	state.CKr, mk = KDF_CK(state.CKr)
	state.Nr += 1
	return DECRYPT(mk, ciphertext, CONCAT(AD, header))

Hàm TrySkippedMessageKeys() kiểm tra xem tin nhắn có thể được giải mã bằng một khóa tin nhắn bị bỏ qua trước đó hay không. Nếu có, nó giải mã tin nhắn, xóa khóa tin nhắn đó khỏi bộ nhớ, rồi trả về nội dung tin nhắn.

def TrySkippedMessageKeys(state, header, ciphertext, AD):
	if (header.dh, header.n) in state.MKSKIPPED:
		mk = state.MKSKIPPED[header.dh, header.n]
		del state.MKSKIPPED[header.dh, header.n]
		return DECRYPT(mk, ciphertext, CONCAT(AD, header))
	else:
		return None

Hàm SkipMessageKeys() xử lý việc bỏ qua các khóa tin nhắn bị mất. Nếu số lượng tin nhắn bị bỏ qua vượt quá MAX_SKIP, một ngoại lệ sẽ được ném ra. Nếu CKr không rỗng, hàm này sẽ tạo và lưu trữ các khóa tin nhắn bị bỏ qua cho đến khi đạt đến giới hạn được chỉ định.

def SkipMessageKeys(state, until):
	if state.Nr + MAX_SKIP < until:
		raise Error()
	if state.CKr != None:
		while state.Nr < until:
			state.CKr, mk = KDF_CK(state.CKr)
			state.MKSKIPPED[state.DHr, state.Nr] = mk
			state.Nr += 1

Hàm DHRatchet() thực hiện một bước Ratchet Diffie–Hellman khi nhận được một khóa Ratchet mới. Nó cập nhật các biến trạng thái, thực hiện phép tính DH để tạo khóa gốc mới, rồi dẫn xuất các khóa chuỗi mới.

def DHRatchet(state, header):
	state.PN = state.Ns
	state.Ns = 0
	state.Nr = 0
	state.DHr = header.dh
	state.RK, state.CKr = KDF_RK(state.RK, DH(state.DHs, state.DHr))
	state.DHs = GENERATE_DH()
	state.RK, state.CKs = KDF_RK(state.RK, DH(state.DHs, state.DHr))

Double Ratchet với mã hóa tiêu đề (header encryption)

Tiêu đề của tin nhắn chứa các khóa công khai Ratchet và các giá trị (PN, N). Trong một số trường hợp, có thể cần mã hóa tiêu đề để kẻ nghe lén không thể xác định tin nhắn nào thuộc về phiên nào hoặc thứ tự của các tin nhắn trong một phiên. Việc tiêu đề bị lộ có thể giúp kẻ tấn công phân tích lưu lượng mạng và suy ra thông tin nhạy cảm ngay cả khi nội dung tin nhắn hoàn toàn được bảo vệ.

Tổng quan

Với mã hóa tiêu đề, mỗi bên lưu trữ một khóa tiêu đề đối xứng và khóa tiêu đề tiếp theo cho cả hai hướng gửi và nhận. Khóa tiêu đề gửi được sử dụng để mã hóa tiêu đề cho chuỗi gửi hiện tại.

Khi người nhận nhận được một tin nhắn, trước tiên cô ấy phải liên kết tin nhắn với phiên Double Ratchet tương ứng (giả sử cô ấy có các phiên khác nhau với các bên khác nhau). Cách thực hiện điều này nằm ngoài phạm vi của bài viết này, mặc dù giao thức Pond cung cấp một số ý tưởng.

Sau khi liên kết tin nhắn với một phiên, người nhận cố gắng giải mã tiêu đề bằng khóa tiêu đề nhận, khóa tiêu đề tiếp theo của phiên đó và bất kỳ khóa tiêu đề nào tương ứng với các tin nhắn đã bị bỏ qua.

Giải mã thành công bằng khóa tiêu đề tiếp theo cho thấy người nhận phải thực hiện một bước DH Ratchet. Trong bước DH Ratchet, các khóa tiêu đề tiếp theo thay thế các khóa tiêu đề hiện tại và các khóa tiêu đề tiếp theo mới được tạo ra từ đầu ra bổ sung của root KDF.

Trong sơ đồ dưới đây, Mỹ Anh đã được khởi tạo với khóa công khai Ratchet của Đan Nguyên và các bí mật chung cho khóa gốc ban đầu, khóa tiêu đề gửi (HK) và khóa tiêu đề nhận tiếp theo (NHK). Như một phần của quá trình khởi tạo, Mỹ Anh tạo ra cặp khóa Ratchet của cô ấy và cập nhật chuỗi gốc để tạo ra khóa gốc mới, khóa chuỗi gửi và khóa tiêu đề gửi tiếp theo (NHK).

Khi Mỹ Anh gửi tin nhắn đầu tiên A1, cô ấy mã hóa tiêu đề của nó bằng khóa tiêu đề gửi mà cô ấy đã được khởi tạo. Nếu Mỹ Anh tiếp theo nhận được phản hồi B1 từ Đan Nguyên, tiêu đề của nó sẽ được mã hóa bằng khóa tiêu đề nhận tiếp theo mà cô ấy đã được khởi tạo. Mỹ Anh thực hiện một bước DH Ratchet, trong đó các khóa tiêu đề tiếp theo được chuyển thành các khóa tiêu đề hiện tại và các khóa tiêu đề tiếp theo mới được tạo ra.

Các hàm bên ngoài

Các hàm bổ sung được yêu cầu để mã hóa tiêu đề:

– HENCRYPT(hk, plaintext): Trả về bản mã hóa AEAD của plaintext với khóa tiêu đề hk. Vì cùng một hk sẽ được sử dụng nhiều lần, nonce AEAD phải là một giá trị không lặp lại có trạng thái hoặc một giá trị ngẫu nhiên không lặp lại được chọn với ít nhất 128 bit entropy.

– HDECRYPT(hk, ciphertext): Trả về bản giải mã xác thực của ciphertext với khóa tiêu đề hk. Nếu xác thực thất bại hoặc nếu khóa tiêu đề hk trống (None), trả về None.

– KDF_RK_HE(rk, dh_out): Trả về một khóa gốc mới, khóa chuỗi và khóa tiêu đề tiếp theo như đầu ra của việc áp dụng KDF được khóa bởi khóa gốc rk vào đầu ra Diffie–Hellman dh_out.

Biến trạng thái

Các biến trạng thái bổ sung được yêu cầu:

– HKs, HKr: Khóa tiêu đề 32 byte cho gửi và nhận.

– NHKs, NHKr: Khóa tiêu đề tiếp theo 32 byte cho gửi và nhận.

Định nghĩa của biến sau bị thay đổi:

– MKSKIPPED: Từ điển các khóa tin nhắn bị bỏ qua, được lập chỉ mục theo khóa tiêu đề và số tin nhắn. Gây ra ngoại lệ nếu có quá nhiều phần tử được lưu trữ.

Khởi tạo

Một số bí mật chung bổ sung phải được sử dụng để khởi tạo các khóa tiêu đề:

– Khóa tiêu đề gửi của Mỹ Anh và khóa tiêu đề nhận tiếp theo của Đan Nguyên phải được đặt cùng giá trị, để tin nhắn đầu tiên của Mỹ Anh kích hoạt một bước DH Ratchet cho Đan Nguyên.

– Khóa tiêu đề nhận tiếp theo của Mỹ Anh và khóa tiêu đề gửi tiếp theo của Đan Nguyên phải được đặt cùng giá trị, để sau bước DH Ratchet đầu tiên của Đan Nguyên, tin nhắn tiếp theo của Đan Nguyên kích hoạt một bước DH Ratchet cho Mỹ Anh.

Khi Mỹ Anh và Đan Nguyên đã đồng ý về SK, khóa công khai Ratchet của Đan Nguyên và các giá trị bổ sung này, Mỹ Anh gọi RatchetInitMỹAnhHE và Đan Nguyên gọi RatchetInitĐanNguyênHE:

def RatchetInitMỹAnhHE(state, SK, DanNguyen_dh_public_key, shared_hka, shared_nhkb):
	state.DHRs = GENERATE_DH()
	state.DHRr = DanNguyen_dh_public_key
	state.RK, state.CKs, state.NHKs = KDF_RK_HE(SK, DH(state.DHRs, state.DHRr))
	state.CKr = None
	state.Ns = 0
	state.Nr = 0
	state.PN = 0
	state.MKSKIPPED = {}
	state.HKs = shared_hka
	state.HKr = None
	state.NHKr = shared_nhkb

def RatchetInitĐanNguyênHE(state, SK, DanNguyen_dh_key_pair, shared_hka, shared_nhkb):
	state.DHRs = DanNguyen_dh_key_pair
	state.DHRr = None
	state.RK = SK
	state.CKs = None
	state.CKr = None
	state.Ns = 0
	state.Nr = 0
	state.PN = 0
	state.MKSKIPPED = {}
	state.HKs = None
	state.NHKs = shared_nhkb
	state.HKr = None
	state.NHKr = shared_hka

Mã hóa tin nhắn

Hàm RatchetEncryptHE() được gọi để mã hóa tin nhắn với mã hóa tiêu đề:

def RatchetEncryptHE(state, plaintext, AD):
	state.CKs, mk = KDF_CK(state.CKs)
	header = HEADER(state.DHRs, state.PN, state.Ns)
	enc_header = HENCRYPT(state.HKs, header)
	state.Ns += 1
	return enc_header, ENCRYPT(mk, plaintext, CONCAT(AD, enc_header))

Giải mã tin nhắn

Hàm RatchetDecryptHE() được gọi để giải mã tin nhắn với mã hóa tiêu đề:

def RatchetDecryptHE(state, enc_header, ciphertext, AD):
	plaintext = TrySkippedMessageKeysHE(state, enc_header, ciphertext, AD)
	if plaintext != None:
		return plaintext
	header, dh_ratchet = DecryptHeader(state, enc_header)
	if dh_ratchet:
		SkipMessageKeysHE(state, header.pn)
		DHRatchetHE(state, header)
	SkipMessageKeysHE(state, header.n)
	state.CKr, mk = KDF_CK(state.CKr)
	state.Nr += 1
	return DECRYPT(mk, ciphertext, CONCAT(AD, enc_header))

def TrySkippedMessageKeysHE(state, enc_header, ciphertext, AD):
	for ((hk, n), mk) in state.MKSKIPPED.items():
		header = HDECRYPT(hk, enc_header)
		if header != None and header.n == n:
			del state.MKSKIPPED[hk, n]
			return DECRYPT(mk, ciphertext, CONCAT(AD, enc_header))
	return None

def DecryptHeader(state, enc_header):
	header = HDECRYPT(state.HKr, enc_header)
	if header != None:
		return header, False
	header = HDECRYPT(state.NHKr, enc_header)
	if header != None:
		return header, True
	raise Error()

def SkipMessageKeysHE(state, until):
	if state.Nr + MAX_SKIP < until:
		raise Error()
	if state.CKr != None:
		while state.Nr < until:
			state.CKr, mk = KDF_CK(state.CKr)
			state.MKSKIPPED[state.HKr, state.Nr] = mk
			state.Nr += 1

def DHRatchetHE(state, header):
	state.PN = state.Ns
	state.Ns = 0
	state.Nr = 0
	state.HKs = state.NHKs
	state.HKr = state.NHKr
	state.DHRr = header.dh
	state.RK, state.CKr, state.NHKr = KDF_RK_HE(state.RK, DH(state.DHRs, state.DHRr))
	state.DHRs = GENERATE_DH()
	state.RK, state.CKs, state.NHKs = KDF_RK_HE(state.RK, DH(state.DHRs, state.DHRr))

Cân nhắc triển khai

Việc triển khai Double Ratchet đúng chuẩn đòi hỏi tuân thủ không chỉ đặc tả thuật toán mà còn cả một loạt quyết định kỹ thuật quan trọng về lựa chọn primitives mật mã, cách tích hợp với X3DH và các lưu ý cụ thể nhằm đảm bảo an toàn trong môi trường thực tế.

Tích hợp với X3DH

Double Ratchet đóng vai trò như một giao thức sau X3DH (post-X3DH), nhận khóa phiên SK do X3DH đàm phán và sử dụng nó làm khóa gốc ban đầu. Sự phân chia trách nhiệm giữa hai giao thức là rõ ràng: X3DH giải quyết bài toán thiết lập phiên bất đồng bộ và xác thực lẫn nhau; Double Ratchet đảm nhận toàn bộ quá trình mã hóa từng tin nhắn sau đó. Ranh giới này cho phép mỗi giao thức được tối ưu hóa và kiểm toán độc lập.

Các đầu ra sau từ X3DH được Double Ratchet sử dụng trực tiếp: đầu ra SK từ X3DH trở thành đầu vào SK cho khởi tạo Double Ratchet; đầu ra AD từ X3DH trở thành đầu vào AD cho mã hóa và giải mã; khóa tiền ký của Đan Nguyên từ X3DH (SPKB) trở thành khóa công khai Ratchet ban đầu của Đan Nguyên để khởi tạo Double Ratchet. Bất kỳ tin nhắn Double Ratchet nào được mã hóa bằng chuỗi gửi ban đầu của Mỹ Anh có thể đóng vai trò như một mã hóa ban đầu cho X3DH, tức là tin nhắn đầu tiên trong phiên vừa được bảo vệ bởi cả hai giao thức đồng thời.

Để xử lý khả năng mất hoặc nhận sai thứ tự tin nhắn ban đầu, mô hình được khuyến nghị là Mỹ Anh lặp lại gửi cùng một thông tin X3DH ban đầu ghép vào tất cả tin nhắn Double Ratchet của cô cho đến khi nhận được phản hồi Double Ratchet đầu tiên từ Đan Nguyên. Điều này đảm bảo rằng dù tin nhắn đầu tiên có bị mất, Đan Nguyên vẫn có đủ thông tin để tạo phiên khớp từ bất kỳ tin nhắn tiếp theo nào.

Các thuật toán mật mã khuyến nghị

Các lựa chọn sau được khuyến nghị để triển khai các hàm mật mã:

GENERATE_DH(): Được khuyến nghị để tạo một cặp khóa dựa trên đường cong elliptic Curve25519 hoặc Curve448.

DH(dh_pair, dh_pub): Được khuyến nghị để trả về đầu ra từ hàm X25519 hoặc X448. Không cần kiểm tra các khóa công khai không hợp lệ khi dùng X25519 hay X448.

KDF_RK(rk, dh_out): Được khuyến nghị triển khai bằng HKDF với SHA256 hoặc SHA512, sử dụng rk làm muối HKDF, dh_out làm dữ liệu đầu vào khóa HKDF và một chuỗi byte cụ thể cho ứng dụng làm thông tin HKDF.

KDF_CK(ck): HMAC với SHA256 hoặc SHA512 được khuyến nghị, sử dụng ck làm khóa HMAC và sử dụng các hằng số riêng biệt làm đầu vào (ví dụ: một byte đơn 0x01 làm đầu vào để tạo khóa tin nhắn, và một byte đơn 0x02 làm đầu vào để tạo khóa chuỗi tiếp theo).

ENCRYPT(mk, plaintext, associated_data): Được khuyến nghị triển khai với một sơ đồ mã hóa AEAD dựa trên SIV hoặc sự kết hợp của CBC với HMAC. Những sơ đồ này cung cấp một số khả năng chống lạm dụng trong trường hợp một khóa vô tình được sử dụng nhiều lần. Một khuyến nghị cụ thể dựa trên CBC và HMAC: HKDF được sử dụng với SHA256 hoặc SHA512 để tạo 80 byte đầu ra; đầu ra HKDF được chia thành khóa mã hóa 32 byte, khóa xác thực 32 byte và IV 16 byte; văn bản gốc được mã hóa bằng AES-256 ở chế độ CBC với đệm PKCS#7; HMAC được tính toán bằng khóa xác thực với đầu vào là associated_data ghép vào bản mã.

Lưu ý triển khai thực tế

Một số lưu ý thực tiễn quan trọng không được đề cập trực tiếp trong đặc tả nhưng có ý nghĩa đáng kể đối với triển khai sản xuất. Thứ nhất là vấn đề an toàn luồng (thread safety): trong các ứng dụng nhắn tin thực tế, nhiều luồng có thể đồng thời cố gắng gửi hoặc nhận tin nhắn, dẫn đến việc cùng lúc đọc và ghi đối tượng trạng thái. Mọi thao tác trên đối tượng trạng thái phải được bảo vệ bằng cơ chế khóa (mutex hoặc tương đương) để đảm bảo tính nhất quán. Một bước tăng Ratchet không hoàn chỉnh – do bị gián đoạn giữa chừng – có thể đưa đối tượng trạng thái vào trạng thái không nhất quán không thể phục hồi.

Thứ hai là vấn đề lưu trữ bền vững (persistent storage): đối tượng trạng thái phải được lưu xuống đĩa hoặc bộ nhớ bền vững sau mỗi lần thay đổi, trước khi tin nhắn được chấp nhận là đã giải mã thành công. Nếu ứng dụng bị tắt đột ngột sau khi cập nhật trạng thái trong bộ nhớ nhưng trước khi ghi xuống bộ nhớ bền vững, trạng thái bộ nhớ sẽ mất và người dùng sẽ không thể giải mã các tin nhắn tiếp theo khi khởi động lại. Để tránh điều này, triển khai nên sử dụng cơ chế ghi nguyên tử (atomic write) – ghi vào tệp tạm và sau đó thay thế tệp cũ – đảm bảo không bao giờ có trạng thái không nhất quán trên đĩa.

Thứ ba là vấn đề xóa an toàn khóa (secure key erasure): sau khi một khóa tin nhắn hoặc khóa chuỗi không còn cần thiết, nó phải được xóa sạch khỏi bộ nhớ thay vì chỉ được giải phóng bình thường. Trong nhiều ngôn ngữ lập trình và hệ điều hành, dữ liệu được giải phóng nhưng chưa bị ghi đè vẫn có thể được đọc từ bộ nhớ nếu kẻ tấn công có quyền truy cập thấp vào hệ thống. Các thư viện mật mã chuyên dụng thường cung cấp hàm xóa an toàn (như memzero hay secure_memset) để xử lý trường hợp này. Đây không phải là yêu cầu của đặc tả thuật toán nhưng là điều kiện thiết yếu để triển khai thực sự cung cấp bảo mật chuyển tiếp theo đúng nghĩa.

Các vấn đề bảo mật

Mặc dù Double Ratchet được thiết kế rất cẩn thận, vẫn tồn tại một số vấn đề bảo mật cần nắm rõ khi triển khai và vận hành trong môi trường thực tế. Hiểu các giới hạn này giúp người triển khai đưa ra các quyết định bổ sung phù hợp để đảm bảo bảo mật toàn diện.

Xóa an toàn

Thuật toán Double Ratchet được thiết kế để cung cấp bảo mật chống lại kẻ tấn công ghi lại các tin nhắn được mã hóa và sau đó xâm nhập vào thiết bị của người gửi hoặc người nhận vào một thời điểm sau đó. Tuy nhiên, bảo mật này có thể bị phá vỡ nếu văn bản gốc đã bị xóa hoặc các khóa có thể được kẻ tấn công khôi phục bằng cách truy cập cấp thấp vào thiết bị bị xâm nhập.

Cụ thể, kẻ tấn công có thể cố gắng khôi phục dữ liệu đã xóa từ bộ nhớ RAM qua một cuộc tấn công cold boot (đọc nội dung RAM ngay sau khi tắt điện đột ngột, khi dữ liệu chưa bị xóa hoàn toàn), hoặc tìm kiếm các vùng nhớ đã giải phóng nhưng chưa bị ghi đè. Ngoài ra, nếu hệ điều hành không mã hóa swap file hay phân vùng ngủ đông (hibernation), trạng thái bộ nhớ của ứng dụng – bao gồm cả khóa mã hóa và văn bản tin nhắn – có thể tồn tại trên đĩa dưới dạng văn bản thuần túy sau khi ứng dụng kết thúc. Việc bật mã hóa toàn bộ đĩa là biện pháp giảm thiểu cần thiết, cùng với việc triển khai xóa an toàn khóa và văn bản trong bộ nhớ. Việc khôi phục dữ liệu đã xóa từ phương tiện lưu trữ là một chủ đề phức tạp nằm ngoài phạm vi bài viết này, nhưng người triển khai nên tham khảo các tài liệu chuyên biệt về forensics kỹ thuật số để hiểu đầy đủ bề mặt tấn công.

Khôi phục sau khi bị xâm phạm (Recovery from compromise)

Bộ tăng DH được thiết kế để khôi phục bảo mật trước một kẻ nghe lén thụ động, kẻ quan sát các tin nhắn được mã hóa sau khi đã xâm nhập vào một hoặc cả hai bên của phiên giao tiếp. Tuy nhiên, cần phân biệt rõ giữa kẻ nghe lén thụ độngkẻ tấn công duy trì quyền truy cập liên tục. Double Ratchet cung cấp phục hồi hiệu quả trong trường hợp đầu: nếu kẻ tấn công chỉ quan sát trạng thái tại một thời điểm rồi rời đi, các bước tăng DH tiếp theo sẽ loại bỏ dần ảnh hưởng của sự xâm nhập đó. Nhưng nếu kẻ tấn công duy trì quyền truy cập liên tục vào thiết bị, khả năng phục hồi không còn hiệu quả.

Mặc dù có cơ chế giảm thiểu này, việc bị xâm nhập khóa bí mật hoặc tính toàn vẹn của thiết bị sẽ có tác động nghiêm trọng. Kẻ tấn công có thể sử dụng các khóa bị xâm nhập để mạo danh bên bị xâm nhập khi tạo phiên mới với X3DH; thay thế khóa Ratchet của mình thông qua một cuộc tấn công trung gian chủ động liên tục để duy trì nghe lén; hoặc sửa đổi trình tạo số ngẫu nhiên của bên bị xâm nhập để các khóa riêng Ratchet trong tương lai trở nên dự đoán được – đây là biện pháp tấn công tinh vi nhất và khó phát hiện nhất trong ba hình thức trên. Nếu một bên nghi ngờ khóa hoặc thiết bị của mình đã bị xâm nhập, họ phải thay thế chúng ngay lập tức và thông báo cho các đối tác liên lạc.

Phân tích mật mã và khóa công khai của bộ tăng

Vì tất cả các tính toán bộ tăng DH đều được trộn vào khóa gốc, một kẻ tấn công có thể giải mã một phiên bằng phân tích mật mã thụ động có thể mất khả năng này nếu họ không quan sát được một số khóa công khai của bộ tăng. Điều này xảy ra khi kẻ tấn công chỉ quan sát được một phần lưu lượng của phiên: các bước tăng DH không được quan sát sẽ đóng góp entropy vào RK mà kẻ tấn công không thể tái tạo. Tuy nhiên, đây không phải là một biện pháp đối phó đáng tin cậy chống lại phân tích mật mã, vì trong thực tế kẻ tấn công thường có khả năng quan sát toàn bộ lưu lượng mạng. Nếu có bất kỳ điểm yếu nào được phát hiện trong bất kỳ thuật toán mật mã nào mà một phiên giao tiếp dựa vào, thì phiên đó nên bị hủy bỏ và thay thế bằng một phiên mới sử dụng mật mã mạnh hơn, thay vì dựa vào đặc tính che giấu thụ động này.

Xóa các khóa tin nhắn bị bỏ qua (Deletion of skipped message keys)

Việc lưu trữ các khóa tin nhắn bị bỏ qua (trong từ điển MKSKIPPED) tạo ra ba loại rủi ro cần xử lý một cách cẩn thận trong quá trình triển khai. Thứ nhất, một kẻ gửi độc hại có thể khiến người nhận lưu trữ một số lượng lớn các khóa tin nhắn bị bỏ qua bằng cách gửi tin nhắn với số thứ tự N rất lớn, có thể dẫn đến tấn công từ chối dịch vụ do tiêu tốn dung lượng lưu trữ và tài nguyên tính toán – giới hạn MAX_SKIP là biện pháp đối phó chính cho rủi ro này.

Thứ hai, các tin nhắn bị mất có thể đã được kẻ tấn công quan sát và ghi lại, ngay cả khi chúng không đến được người nhận. Nếu kẻ tấn công sau đó xâm nhập vào người nhận và lấy được các khóa tin nhắn bị bỏ qua đang lưu trong MKSKIPPED, chúng có thể giải mã các tin nhắn đó. Rủi ro thứ hai này chỉ xảy ra nếu đồng thời có cả hai điều kiện: tin nhắn bị chặn trên đường truyền và người nhận bị xâm nhập sau đó.

Để giảm thiểu rủi ro đầu, các bên nên đặt giới hạn hợp lý cho mỗi phiên về số lượng khóa tin nhắn bị bỏ qua sẽ được lưu trữ – ví dụ không quá 1000. Để giảm thiểu rủi ro thứ hai, các bên nên xóa các khóa tin nhắn bị bỏ qua sau một khoảng thời gian thích hợp, được kích hoạt bởi bộ đếm thời gian hoặc bằng cách đếm số lượng sự kiện như tin nhắn nhận được hay các bước tăng DH.

Hoãn việc tạo khóa bộ tăng mới (Deferring new ratchet key generation)

Trong mỗi bước tăng DH, một cặp khóa Ratchet mới và chuỗi gửi tin nhắn được tạo ra. Vì chuỗi gửi tin nhắn chưa cần sử dụng ngay lập tức, các bước này có thể bị hoãn lại cho đến khi bên gửi chuẩn bị gửi một tin nhắn mới. Chiến lược này giúp tăng nhẹ mức độ bảo mật bằng cách rút ngắn thời gian tồn tại của các khóa Ratchet trong bộ nhớ: thay vì tạo cặp khóa mới ngay khi nhận tin nhắn – và giữ nó trong bộ nhớ trong khoảng thời gian không xác định cho đến khi tin nhắn tiếp theo cần gửi – cặp khóa mới chỉ được tạo ra ngay trước khi cần dùng. Điều này giúp giảm cửa sổ thời gian mà khóa riêng Ratchet mới tồn tại trong bộ nhớ và có thể bị kẻ tấn công truy cập. Tuy nhiên, chiến lược này làm tăng độ phức tạp của việc quản lý trạng thái vì cần thêm một cờ trạng thái để theo dõi liệu bước tạo khóa mới đã được thực hiện hay chưa.

Cắt bớt thẻ xác thực (Truncating authentication tags)

Nếu hàm ENCRYPT được triển khai bằng CBC và HMAC, việc cắt bớt đầu ra cuối cùng của HMAC xuống 128 bit để giảm kích thước tin nhắn là chấp nhận được về mặt bảo mật. Với 128 bit, kẻ tấn công cần trung bình 2^127 lần thử để giả mạo một thẻ xác thực hợp lệ, một con số hoàn toàn vượt ngoài tầm với của bất kỳ phương tiện tính toán nào hiện có và trong tương lai gần. Việc cắt bớt nhiều hơn có thể được chấp nhận trong một số trường hợp cụ thể nhưng cần phân tích cẩn thận tùy theo ngữ cảnh ứng dụng và mô hình đối thủ. Trong mọi trường hợp, HMAC cuối cùng không nên bị cắt xuống dưới 64 bit – dưới mức đó, không gian tìm kiếm đủ nhỏ để các cuộc tấn công giả mạo trở nên khả thi. Nếu hàm ENCRYPT được triển khai theo cách khác, chẳng hạn AES-GCM hay ChaCha20-Poly1305, thì việc cắt bớt thẻ xác thực yêu cầu phân tích phức tạp hơn và không được khuyến nghị chung.

Nhận dạng dấu vết triển khai (Implementation fingerprinting)

Nếu giao thức này được sử dụng trong môi trường có các bên ẩn danh, cần đảm bảo rằng các triển khai hoạt động giống nhau trong mọi trường hợp để tránh để lộ thông tin nhận dạng qua hành vi quan sát được từ bên ngoài. Cụ thể, nếu hai triển khai khác nhau xử lý các tình huống ngoại lệ theo cách khác nhau (ví dụ: một triển khai giữ lại 500 khóa bị bỏ qua trong khi triển khai kia chỉ giữ 200), một kẻ quan sát có thể phân biệt hai loại khách hàng chỉ qua cách chúng phản ứng với các kịch bản tin nhắn đặc biệt. Trong bối cảnh ẩn danh, các triển khai được khuyến nghị tuân theo chính xác các thuật toán, sử dụng các giới hạn giống nhau cho số lượng khóa tin nhắn bị bỏ qua được lưu trữ, và áp dụng chính sách xóa khóa tin nhắn bị bỏ qua giống nhau. Chính sách xóa nên dựa trên các sự kiện có tính chất xác định (như tin nhắn nhận được) thay vì dựa trên thời gian, vì đồng hồ của thiết bị có thể là một nguồn fingerprinting khác nếu các thiết bị có múi giờ hay tốc độ đồng hồ khác nhau.

Kết luận

Double Ratchet là thành tựu thiết kế giao thức nổi bật nhất trong mật mã học ứng dụng thập niên 2010: kết hợp hai cơ chế tăng khóa bổ sung cho nhau – bộ tăng đối xứng cung cấp bảo mật chuyển tiếp nhanh và hiệu quả giữa các bước DH, bộ tăng DH cung cấp khả năng phục hồi sau xâm phạm và làm mới entropy theo mỗi vòng trao đổi – đạt đồng thời forward secrecy và future secrecy trong một cơ chế thống nhất. Không có giao thức nào trước Double Ratchet cung cấp cả hai tính chất này một cách hiệu quả và có thể triển khai thực tiễn.

Các cân nhắc triển khai – từ xóa an toàn khóa, giới hạn khóa bị bỏ qua, chiến lược hoãn tạo khóa đến phòng chống fingerprinting – minh họa rằng bảo mật giao thức không chỉ nằm trong thuật toán mà còn trong các chi tiết triển khai thường bị bỏ qua. Hiểu đầy đủ Double Ratchet là hiểu tại sao mỗi tin nhắn Signal được bảo vệ bởi khóa riêng của nó, tại sao việc xâm phạm một thiết bị không tiết lộ các tin nhắn đã xóa, và tại sao hệ thống tự phục hồi về trạng thái bảo mật sau khi xâm phạm chấm dứt.

Thuật toán Double Ratchet mã hóa tin nhắn an toàn – developer, bao mat, mat ma hoc, signal protocol, ma hoa thong tin, bao mat thong tin, double ratchet, kdf, x3dh, forward secrecy, future secrecy, key agreement, key agreement protocol, shared secret key, khoa bi mat chung, giao thuc bao mat, giao thuc nhan tin, ma hoa dau cuoi, tin nhan ma hoa dau cuoi.
Thuật toán Double Ratchet mã hóa tin nhắn an toàn.
  • bao-mat (17)
  • mat-ma-hoc (17)
  • signal-protocol (15)
  • ma-hoa-thong-tin (8)

  • bao-mat-thong-tin (8)

  • double-ratchet (5)

  • kdf (2)

  • x3dh (5)

  • forward-secrecy (10)

  • future-secrecy (3)

  • key-agreement (2)

  • key-agreement-protocol (2)

  • shared-secret-key (2)

  • khoa-bi-mat-chung (2)

  • ma-hoa-dau-cuoi (7)

  • tin-nhan-ma-hoa-dau-cuoi (2)

  • symmetric-ratchet (1)

  • dh-ratchet (1)

  • break-in-recovery (1)

Chuyên mục mat-ma-hoc

So sánh Signal, WhatsApp, Telegram và iMessage về kiến trúc bảo mật

So sánh Signal, WhatsApp, Telegram và iMessage về kiến trúc bảo mật

So sánh chi tiết kiến trúc bảo mật của bốn ứng dụng nhắn tin phổ biến nhất – giao thức mã hóa, mặc định E2E, siêu dữ liệu thu thập, mã nguồn mở và chính sách dữ liệu – giúp người dùng lựa chọn phù hợp với mô hình đối thủ.

So sánh Signal, WhatsApp, Telegram và iMessage về kiến trúc bảo mật
Bảo mật siêu dữ liệu – giới hạn không thể vượt qua của mã hóa đầu cuối

Bảo mật siêu dữ liệu – giới hạn không thể vượt qua của mã hóa đầu cuối

Phân tích tại sao siêu dữ liệu – ai liên lạc với ai, khi nào, tần suất – có thể nhạy cảm hơn nội dung tin nhắn; các loại rò rỉ siêu dữ liệu ở tầng ứng dụng, mạng và thông báo đẩy; và các biện pháp giảm thiểu như Sealed Sender và Tor.

Bảo mật siêu dữ liệu – giới hạn không thể vượt qua của mã hóa đầu cuối

Chuyên mục future-secrecy

Theo dõi hành trình

Hãy để lại thông tin, khi có gì mới thì Nhà văn sẽ gửi thư đến bạn để cập nhật. Cam kết không gửi email rác.

Họ và tên

Email liên lạc

Đôi dòng chia sẻ