Tài liệu tham chiếu triển khai mã hóa
Phần mã hóa đang thực sự chạy hẹp hơn phần mã hóa đã được viết, và khoảng cách đó chính là lý do trang này tồn tại. Sealed box của libsodium là cơ chế mã hóa tin nhắn duy nhất mà Railgun thực thi — và nó chỉ chạy trên nhánh máy khách nơi mô-đun native libsignal không có mặt. Trên bản dựng máy tính để bàn Electron, nơi libsignal thực sự được nạp, trình xử lý mã hóa tin nhắn trực tiếp vẫn chỉ là một placeholder mã hóa base64 phần văn bản thuần. Tin nhắn cộng đồng và tin nhắn kênh không được mã hóa trên bất kỳ nhánh nào. Giao thức kiểu Signal được mô tả ở nửa sau trang này đã được viết và rà soát, và chưa được đấu nối vào bất kỳ máy khách nào bạn có thể tải về.
Đây là tài liệu tham chiếu kiến trúc, không phải một cuộc kiểm toán an ninh độc lập. Mục 1 và 2 mô tả những gì thực sự được phát hành. Mục 4 đến 7 mô tả một thiết kế mà Railgun đang hướng tới và được gắn nhãn Dự kiến xuyên suốt. Không nội dung nào ở đây được phép hiểu là tuyên bố rằng một máy khách Railgun hiện đang bảo đảm một thuộc tính được đánh dấu là dự kiến.
Những gì được phát hành hôm nay
| Nhánh | Cơ chế | Trạng thái |
|---|---|---|
| Tin nhắn trực tiếp — bản dựng trình duyệt và bản phát triển | Sealed box của libsodium — thỏa thuận khóa X25519, mã hóa có xác thực XSalsa20-Poly1305. Được chọn khi mô-đun native libsignal không có mặt. | Đã mã hóa |
| Tin nhắn trực tiếp — bản dựng máy tính để bàn Electron | libsignal được nạp, nên encryptDm được định tuyến tới trình xử lý IPC của Electron — một placeholder mã hóa base64 phần văn bản thuần. Máy chủ chuyển tiếp nhận được văn bản đọc được. | Không được mã hóa |
| Tin nhắn cộng đồng và tin nhắn kênh | Được gửi dưới dạng văn bản thuần bọc base64. Phần mã hóa kênh đã được viết nhưng trả về một dấu hiệu văn bản thuần. | Không được mã hóa |
| X3DH + Double Ratchet | Được triển khai dựa trên libsignal, chỉ được import dưới dạng kiểu dữ liệu. Không có nhánh chạy nào khởi tạo nó. | Dự kiến |
| Lưu trữ khóa riêng tư | Khóa được tạo và giữ trên thiết bị. Không có nhánh mã nào truyền đi khóa riêng tư hay khóa định danh. | Đã triển khai |
1. Sealed box — phần mã hóa đang chạy
Ở những nơi Railgun thực sự mã hóa một tin nhắn trực tiếp, nó dùng sealed box của libsodium — cấu trúc crypto_box_seal. Cấu trúc này kết hợp thỏa thuận khóa X25519 với mã hóa có xác thực XSalsa20-Poly1305. Đây là mật mã học thực thụ, đã được rà soát kỹ. Nó cũng là một cấu trúc cố ý đơn giản, và những giới hạn của nó là điều đáng lưu ý. Máy khách nào chạm tới nó là nội dung của mục 2: bản dựng trình duyệt và bản phát triển thì có; bản dựng máy tính để bàn Electron thì không.
Sealed box được dựng như thế nào
// Sender has only the recipient's public key, PK_r
(pk_e, sk_e) = crypto_box_keypair() // fresh, single-use
nonce = BLAKE2b(pk_e || PK_r) // derived, not random
ct = crypto_box(plaintext, nonce, PK_r, sk_e)
sealed = pk_e || ct // ephemeral key is prepended
erase(sk_e)
Người nhận khôi phục khóa công khai tạm thời từ phần đầu tin nhắn, tính lại đúng bí mật chung bằng khóa riêng tư của mình, rồi mở hộp. Thẻ Poly1305 khiến một bản mã bị sửa đổi sẽ không mở được, thay vì giải mã ra dữ liệu rác.
Điều bạn nhận được
- • Tính bí mật trước máy chủ chuyển tiếp và bất kỳ ai trên đường truyền.
- • Tính toàn vẹn — hành vi can thiệp bị phát hiện, không bị âm thầm giải mã.
- • Ẩn danh người gửi: khóa tạm thời không tiết lộ điều gì về người đã gửi.
- • Không cần thiết lập phiên, nên nó vẫn hoạt động khi người nhận đang ngoại tuyến.
Điều bạn không nhận được
- • Không có bí mật chuyển tiếp. Khóa riêng tư dài hạn của người nhận mở được mọi tin nhắn từng gửi cho họ. Nếu khóa đó rò rỉ, các bản mã đã bị thu thập cũng rò rỉ theo.
- • Không có xác thực người gửi. Sealed box ẩn danh theo đúng cấu trúc của nó; nó không chứng minh được gì về người đã viết ra tin nhắn.
- • Không có khả năng phục hồi sau khi bị xâm phạm. Không có cơ chế ratchet nào để vượt qua một khóa đã bị lộ.
Ba lỗ hổng đó chính là những gì X3DH và Double Ratchet sinh ra để bịt lại, và đó là lý do công việc này đang được tiến hành. Cho tới khi nó hoàn tất, bản tóm tắt trung thực về một tin nhắn trực tiếp dùng sealed box là: mạnh trước kẻ quan sát mạng thụ động và trước máy chủ chuyển tiếp, yếu trước kẻ tấn công cuối cùng lấy được khóa thiết bị của người nhận. Bản tóm tắt đó chỉ áp dụng ở nơi đoạn mã này thực sự chạy.
2. Những gì không được mã hóa
Tin nhắn cộng đồng và tin nhắn kênh — phần lớn những gì đa số người dùng Railgun thực sự gõ ra — hiện không được mã hóa trên bất kỳ nhánh máy khách nào. Hàm mã hóa kênh có tồn tại, và nó thẳng thắn về chính mình: nó ghi một cảnh báo vào nhật ký và trả về văn bản thuần được bọc base64 phía sau một chuỗi đánh dấu, để không ai ở phía sau nhầm nó là bản mã.
// What encryptChannel actually returns
logger.warn("Channel encryption not yet implemented")
ciphertext = base64("[DEVCRYPTO:PLAINTEXT]" + plaintext)
Hệ quả thực tế: với nội dung cộng đồng và nội dung kênh, máy chủ Railgun ở đúng vị trí của bất kỳ máy chủ trò chuyện thông thường nào. Nó đọc được những gì bạn viết ở đó. Phân phối khóa nhóm — một khóa riêng cho mỗi kênh được trao cho thành viên bên trong các sealed box — là giải pháp dự kiến và chưa được phát hành.
Tin nhắn trực tiếp trên bản dựng máy tính để bàn Electron
Lỗ hổng thứ hai là tin nhắn trực tiếp trên chính máy khách mà phần lớn người dùng chạy, và điều này rất dễ bị phát biểu ngược, nên đây là logic lựa chọn đúng như mã thực thi. initCrypto() hỏi tiến trình chính xem libsignal có được nạp hay không. @signalapp/libsignal-client là một phụ thuộc thật và bắt buộc khi khởi động, nên trên một bản dựng Electron đã phát hành thì nó có được nạp — và chính câu trả lời đó chọn phần triển khai IPC của Electron thay vì phần triển khai sealed box. Hàm encryptDm của nó chuyển tiếp tới trình xử lý crypto:encryptDm, vốn là một placeholder:
// crypto:encryptDm, Electron main process
const plaintextBytes = new TextEncoder().encode(plaintext)
ciphertext: toBase64(plaintextBytes) // not encryption
Vậy nên các sealed box ở mục 1 chỉ được dùng đến khi libsignal không có mặt — trong trình duyệt và trong môi trường phát triển. Trên máy khách máy tính để bàn, một tin nhắn trực tiếp đến máy chủ chuyển tiếp dưới dạng văn bản thuần được mã hóa base64. libsignal được nạp ở đó để tạo một khóa định danh và một gói prekey; nó không thiết lập phiên nào và không ratchet gì cả — crypto:ensureDmSession là một dòng nhật ký gỡ lỗi không có thân hàm.
3. Curve25519 — đường cong nền tảng
Cả các sealed box đang được phát hành lẫn thiết kế X3DH chưa được phát hành đều dựa trên cùng một đường cong elliptic: Curve25519, do Daniel J. Bernstein thiết kế cho trao đổi khóa Diffie-Hellman tốc độ cao. Mục này mô tả bản thân đường cong, và áp dụng cho cả hai.
Phương trình đường cong
y² = x³ + 486662x² + x
over the prime field 𝔽ₚ where p = 2²⁵⁵ − 19
Số nguyên tố 2²⁵⁵ − 19 được chọn vì nó cho phép số học modulo cực nhanh. Hệ số 486662 được chọn vì là giá trị nhỏ nhất tạo ra một đường cong an toàn với các thuộc tính an toàn cần thiết.
Sinh khóa
1. Khóa riêng tư: Sinh 32 byte ngẫu nhiên bằng một CSPRNG (bộ sinh số giả ngẫu nhiên an toàn về mặt mật mã). Kẹp khóa bằng cách xóa 3 bit thấp nhất và bit cao nhất, đồng thời đặt bit cao thứ hai. Điều này bảo đảm khóa là bội của 8 và nằm trong khoảng hợp lệ.
a = random(32 bytes)
a[0] &= 248 // Clear low 3 bits
a[31] &= 127 // Clear high bit
a[31] |= 64 // Set second-highest bit
2. Khóa công khai: Tính phép nhân vô hướng của khóa riêng tư với điểm cơ sở G = 9.
A = a · G // Scalar multiplication on Curve25519
Tính an toàn dựa trên Bài toán logarit rời rạc trên đường cong elliptic (ECDLP): cho trước A và G, việc khôi phục a là bất khả thi về mặt tính toán. Curve25519 cung cấp khoảng 128 bit an toàn.
Trao đổi khóa Diffie-Hellman
Hai bên (Alice và Bob) có thể tính ra một bí mật chung mà không bao giờ truyền nó đi:
Alice: a (private), A = a·G (public)
Bob: b (private), B = b·G (public)
Alice computes: S = a · B = a · (b·G)
Bob computes: S = b · A = b · (a·G)
Both arrive at S = ab·G — the same shared secret
Một kẻ nghe lén biết A và B (đều công khai) không thể tính được S nếu không giải được ECDLP. Đây là giả thiết Computational Diffie-Hellman (CDH).
Sealed box chính là phép trao đổi này với một phía được làm thành tạm thời rồi vứt bỏ. X3DH ở phần dưới là phép trao đổi này được thực hiện bốn lần trên các cặp khóa khác nhau, để kết quả vừa che giấu nội dung vừa xác thực cả hai bên.
4. X3DH — Diffie-Hellman ba lần mở rộng
DỰ KIẾNX3DH giải một bài toán khó: hai người thiết lập một phiên mã hóa bằng cách nào khi một trong hai đang ngoại tuyến? Diffie-Hellman truyền thống đòi hỏi cả hai bên phải trực tuyến. X3DH dùng các gói khóa đã tải lên trước để cho phép thỏa thuận khóa bất đồng bộ.
Các loại khóa
Khóa định danh (IK)
Cặp khóa Curve25519 dài hạn. Được tạo một lần, định danh người dùng về mặt mật mã. Không bao giờ thay đổi trừ khi người dùng đăng ký lại.
Khóa trước có chữ ký (SPK)
Cặp khóa Curve25519 trung hạn, được ký bằng Khóa định danh. Được luân chuyển định kỳ. Chữ ký chứng minh nó thuộc về người giữ khóa định danh.
Khóa trước dùng một lần (OPK)
Các cặp khóa Curve25519 tạm thời được tải lên theo lô. Mỗi cặp dùng một lần rồi bị xóa. Cung cấp thêm một lớp bí mật chuyển tiếp cho tin nhắn đầu tiên.
Khóa tạm thời (EK)
Một cặp khóa Curve25519 mới do người gửi tạo cho mỗi phiên mới. Không bao giờ được lưu ở phía máy chủ.
Bắt tay X3DH
Khi Alice muốn nhắn tin cho Bob (người có thể đang ngoại tuyến), cô lấy gói khóa trước của Bob từ máy chủ và thực hiện bốn phép tính Diffie-Hellman:
// Alice has: IKₐ (her identity key), EKₐ (fresh ephemeral)
// Bob published: IKᵦ, SPKᵦ, OPKᵦ
DH1 = DH(IKₐ, SPKᵦ) // her identity ↔ his signed pre-key
DH2 = DH(EKₐ, IKᵦ) // her ephemeral ↔ his identity
DH3 = DH(EKₐ, SPKᵦ) // her ephemeral ↔ his signed pre-key
DH4 = DH(EKₐ, OPKᵦ) // her ephemeral ↔ his one-time pre-key
SK = KDF(DH1 || DH2 || DH3 || DH4)
// SK is the shared secret, derived via HKDF-SHA-256
Tại sao cần bốn phép DH?
- DH1 cung cấp xác thực lẫn nhau (cả hai khóa định danh đều tham gia)
- DH2 bảo đảm khóa tạm thời được gắn với danh tính của Bob
- DH3 cung cấp bí mật chuyển tiếp thông qua khóa tạm thời
- DH4 cung cấp thêm bí mật chuyển tiếp. Nếu không có OPK nào khả dụng, X3DH vẫn hoạt động chỉ với DH1–DH3
DH1 là phép toán mà sealed box không có. Đó là lý do nhánh đang phát hành không thể cho bạn biết một tin nhắn đến từ ai.
KDF — Hàm dẫn xuất khóa
Các đầu ra DH thô được kết hợp bằng HKDF-SHA-256 (Hàm dẫn xuất khóa dựa trên HMAC). HKDF trích entropy từ chuỗi đầu ra DH nối lại và mở rộng nó thành một bí mật chung ngẫu nhiên đều:
PRK = HKDF-Extract(salt="", input=DH1||DH2||DH3||DH4)
SK = HKDF-Expand(PRK, info="RailGunX3DH", length=32)
5. Thuật toán Double Ratchet
DỰ KIẾNSau khi X3DH thiết lập bí mật chung ban đầu, Double Ratchet tiếp quản. Nó dùng hai "ratchet" cài vào nhau để dẫn xuất một khóa duy nhất mới cho mỗi tin nhắn. Thiết kế này bảo vệ các tin nhắn trước đó và nhằm khôi phục sự bảo vệ cho các tin nhắn tương lai sau một bước ratchet mới.
Ratchet DH (bất đối xứng)
Mỗi khi chiều của cuộc hội thoại thay đổi (Alice → Bob, rồi Bob → Alice), một lần trao đổi khóa Diffie-Hellman mới diễn ra với các khóa tạm thời mới. Điều này "ratchet" khóa gốc tiến về phía trước:
dh_out = DH(my_ratchet_key, their_ratchet_key)
root_key, chain_key = KDF(root_key, dh_out)
Đây chính là thứ sẽ mang lại an toàn sau khi bị xâm phạm: kẻ tấn công đánh cắp trạng thái hiện tại sẽ mất quyền truy cập ngay khi một bước ratchet DH mới diễn ra.
Ratchet đối xứng (chuỗi băm)
Giữa các bước ratchet DH, một ratchet dựa trên hàm băm dẫn xuất một khóa tin nhắn mới từ khóa chuỗi cho mỗi tin nhắn:
message_key = HMAC-SHA256(chain_key, 0x01)
chain_key = HMAC-SHA256(chain_key, 0x02)
Khóa chuỗi cũ bị xóa sau mỗi bước. Khóa tin nhắn mã hóa đúng một tin nhắn, rồi bị xóa. Đây là cơ chế bí mật chuyển tiếp.
Tiến trình ratchet
Khi hình dung ra, Double Ratchet trông như thế này. Mỗi mũi tên là một hàm một chiều — bạn có thể đi tới nhưng không bao giờ đi ngược lại:
Root Key Chain:
RK₀ ──DH──→ RK₁ ──DH──→ RK₂ ──DH──→ RK₃ ...
Each RK step produces a Chain Key:
↓ ↓ ↓
CK₀ CK₁ CK₂
↓ ↓ ↓
Each CK step produces Message Keys:
CK₀→MK₀ CK₁→MK₃ CK₂→MK₅
CK₀→MK₁ CK₁→MK₄ CK₂→MK₆
CK₀→MK₂ CK₂→MK₇
Mỗi MKₙ sẽ mã hóa đúng một tin nhắn rồi bị xóa, nên kẻ tấn công lấy được MK₄ không thể dẫn xuất ra MK₃ hay MK₅. Trên nhánh sealed box đang phát hành hôm nay, không có khóa riêng cho từng tin nhắn để đánh cắp và không có chuỗi nào để lần theo — chỉ có một khóa dài hạn duy nhất mở được mọi thứ.
6. Bí mật chuyển tiếp — cần những gì
DỰ KIẾNBí mật chuyển tiếp nghĩa là việc các khóa dài hạn bị lộ không làm lộ các khóa phiên trong quá khứ. Railgun hiện không có tính chất này. Có hai cơ chế mang lại nó, và cả hai đều thuộc về giao thức dự kiến ở trên:
Khóa tạm thời trong X3DH
Người gửi tạo một cặp khóa tạm thời mới cho mỗi phiên mới và xóa nửa khóa riêng tư sau khi bắt tay. Kẻ tấn công về sau đánh cắp khóa định danh của cả hai bên vẫn không thể dựng lại bí mật của phiên. Sealed box thực sự có dùng một khóa tạm thời mới ở phía người gửi — nhưng nửa còn lại của phép trao đổi, ở phía người nhận, là khóa dài hạn của họ, và đó chính xác là lý do tính chất này không thành lập.
Xóa khóa ratchet
Double Ratchet xóa các khóa chuỗi và khóa tin nhắn cũ sau khi dùng. Chuỗi KDF là một chiều — cho trước CKₙ, bạn tính được CKₙ₊₁ nhưng không tính được CKₙ₋₁, nên một lần bị xâm phạm tại thời điểm T không để lộ bất cứ thứ gì đã gửi trước T.
An toàn sau khi bị xâm phạm
Ratchet DH còn mang lại bí mật về sau: kẻ tấn công xâm phạm được trạng thái phiên hiện tại sẽ mất nó ở bước ratchet kế tiếp, bước này đưa vào phần ngẫu nhiên mà kẻ đó không có. Không có ratchet, một khóa đã bị lộ vẫn hữu ích cho kẻ tấn công vô thời hạn.
7. Kiến trúc chuyển tiếp và hàng đợi
Railgun chuyển tiếp các phong bì và có thể xếp chúng vào hàng đợi tạm thời để gửi cho thiết bị ngoại tuyến. Bộ chuyển tiếp được xây dựng sao cho không cần văn bản thuần của tin nhắn, nhưng hiện tại nó vẫn nhận văn bản thuần trong hai trường hợp: tin nhắn kênh, thứ mà không máy khách nào mã hóa, và tin nhắn trực tiếp từ bản dựng máy tính để bàn Electron, nơi trình xử lý IPC mã hóa base64 chứ không mã hóa thật. Một tin nhắn trực tiếp đi theo nhánh sealed box thì đến nơi dưới dạng bản mã mà bộ chuyển tiếp không mở được.
Máy chủ thấy những gì
Máy chủ lưu giữ:
- • Các gói khóa công khai
- • Thông tin đăng ký người dùng
- • Siêu dữ liệu và thành viên của cộng đồng/kênh
- • Bản mã sealed box mà nó không mở được (tin nhắn trực tiếp từ nhánh trình duyệt/phát triển)
- • Văn bản thuần của tin nhắn trực tiếp được mã hóa base64 từ bản dựng máy tính để bàn Electron
- • Nội dung tin nhắn kênh, cho tới khi mã hóa kênh được phát hành
- • Bản ghi kiểm toán xác thực
Máy chủ không bao giờ nhận:
- • Khóa riêng tư hay khóa định danh
- • Trạng thái phiên của máy khách
- • Một bản sao lâu dài lịch sử tin nhắn của bạn
Tin nhắn được chuyển tiếp theo thời gian thực tới người nhận đang trực tuyến qua WebSocket. Với thiết bị ngoại tuyến, các phong bì được xếp hàng trong Redis với TTL tối đa 30 ngày và bị xóa khi thiết bị xác nhận đã nhận. Không có kho lưu trữ tin nhắn phía máy chủ — lịch sử máy khách nằm trong bộ nhớ cục bộ trên chính thiết bị của bạn, nên mất thiết bị là mất lịch sử.
8. Tóm tắt các thuộc tính an toàn
| Thuộc tính | Cơ chế | Trạng thái |
|---|---|---|
| Tính bí mật của tin nhắn trực tiếp | Sealed box chỉ trên nhánh trình duyệt/phát triển; trình xử lý máy tính để bàn Electron mã hóa base64 | Một phần |
| Tính toàn vẹn của tin nhắn trực tiếp | Thẻ Poly1305 trên nhánh sealed box; nhánh máy tính để bàn Electron gửi base64 không được xác thực | Một phần |
| Khóa ở lại trên thiết bị | Kho khóa cục bộ; không có nhánh mã nào truyền đi khóa riêng tư | Đã triển khai |
| Gửi khi ngoại tuyến | Phong bì được xếp hàng trong Redis với TTL 30 ngày, xóa khi có xác nhận | Đã triển khai |
| Tính bí mật của kênh | Phân phối khóa nhóm chưa được triển khai; nội dung được gửi ở dạng đọc được | Chưa triển khai |
| Xác thực người gửi | Sealed box ẩn danh theo đúng cấu trúc | Chưa triển khai |
| Bí mật chuyển tiếp | Cần Double Ratchet, thứ mà không máy khách nào khởi tạo | Dự kiến |
| Phục hồi sau khi bị xâm phạm | Cần ratchet DH, thứ mà không máy khách nào khởi tạo | Dự kiến |
| Kiểm toán độc lập | Không tuyên bố có bất kỳ cuộc kiểm toán mật mã bên thứ ba nào đã hoàn thành | Chưa hoàn thành |
| Bảo đảm hậu lượng tử | Không tuyên bố bất kỳ bảo đảm an toàn hậu lượng tử tổng quát nào | Không tuyên bố |
Trang này đến từ đâu
Mọi trạng thái ở trên đều được đọc ra từ mã nguồn máy khách chứ không phải từ một tài liệu thiết kế. Kho mã máy khách hiện chưa công khai, nên chúng tôi không thể đề nghị bạn tự kiểm chứng — và đó là lý do để xem trang này như một sự công bố, chứ không phải một bằng chứng. Mọi câu hỏi về một tuyên bố cụ thể xin gửi tới security@railgun.chat.