Tài liệu này xác định chính thức hàm toán học được biểu thị bằng khoá Truyền trực tuyến AES-GCM-HKDF, được mã hoá ở định dạng proto là type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey
.
Phương thức mã hoá này dựa trên HRRV151. Để phân tích bảo mật, chúng tôi tham khảo HS202.
Khoá và thông số
Khoá được mô tả bằng các phần sau (tất cả kích thước trong tài liệu này đều tính bằng kibibyte):
- \(\mathrm{KeyValue}\), một chuỗi byte.
- \(\mathrm{CiphertextSegmentSize} \in \{1, 2, \ldots, 2^{31}-1\}\).
- \(\mathrm{DerivedKeySize} \in \{16, 32\}\).
- \(\mathrm{HkdfHashType} \in \{\mathrm{SHA1}, \mathrm{SHA256}, \mathrm{SHA512}\}\).
Ngoài ra, khoá hợp lệ phải đáp ứng các thuộc tính sau:
- \(\mathrm{len}(\mathrm{KeyValue}) \geq \mathrm{DerivedKeySize}\).
- \(\mathrm{CiphertextSegmentSize} > \mathrm{DerivedKeySize} + 24\) (Điều này bằng \(\mathrm{len}(\mathrm{Header}) + 16\) như giải thích sau).
Các khoá không đáp ứng bất kỳ thuộc tính nào trong số này sẽ bị Tink từ chối, khi khoá được phân tích cú pháp hoặc khi tạo nguyên hàm tương ứng.
Hàm mã hoá
Để mã hoá một thông báo \(\mathrm{Msg}\) có dữ liệu liên kết \(\mathrm{AssociatedData}\), chúng ta tạo một tiêu đề, chia thông báo thành các phân đoạn, mã hoá từng phân đoạn và nối các phân đoạn đã mã hoá.
Tạo tiêu đề
Chúng ta chọn một chuỗi ngẫu nhiên đồng nhất \(\mathrm{Salt}\) có độ dài \(\mathrm{DerivedKeySize}\) và một chuỗi ngẫu nhiên đồng nhất \(\mathrm{NoncePrefix}\) có độ dài 7.
Sau đó, chúng ta đặt \(\mathrm{Header} := \mathrm{len}(\mathrm{Header}) \| \mathrm{Salt} \| \mathrm{NoncePrefix}\), trong đó độ dài của tiêu đề được mã hoá dưới dạng một byte. Lưu ý rằng \(\mathrm{len}(\mathrm{Header}) \in \{24, 40\}\).
Tiếp theo, chúng ta sử dụng HKDF3 với hàm băm do \(\mathrm{HkdfHashType}\)cung cấp và các dữ liệu đầu vào \(\mathrm{ikm} := \mathrm{KeyValue}\), \(\mathrm{salt} := \mathrm{Salt}\)và \(\mathrm{info} := \mathrm{AssociatedData}\), với độ dài đầu ra \(\mathrm{DerivedKeySize}\). Chúng ta gọi kết quả là \(\mathrm{DerivedKey}\).
Chia thư
Tiếp theo, thông báo \(\mathrm{Msg}\) được chia thành các phần: \(\mathrm{Msg} = M_0 \| M_1 \| \cdots \| M_{n-1}\).
Chiều dài của các đoạn này được chọn để đáp ứng:
- \(\mathrm{len}(M_0) \in \{0,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{len}(\mathrm{Header}) - \mathrm{16}\}\).
- Nếu \(n>1\), thì \(\mathrm{len}(M_1), \ldots, \mathrm{len}(M_{n-1}) \in \{1,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{16}\}\).
- Nếu \(n>1\), thì \(\mathrm{len}(M_{0}), \ldots, \mathrm{len}(M_{n-2})\) phải có độ dài tối đa theo các quy tắc ràng buộc ở trên.
\(n\) có thể là tối đa \(2^{32}\). Nếu không, quá trình mã hoá sẽ không thành công.
Mã hoá các khối
Để mã hoá phân đoạn \(M_i\), chúng ta tính toán \(\mathrm{IV}_i := \mathrm{NoncePrefix}
\| \mathrm{i} \| b\), trong đó \(\mathrm{i}\) là 4 byte theo mã hoá big-endian và
byte $b$ là 0x00
nếu $i < n-1$ và 0x01
nếu không.
Sau đó, chúng ta mã hoá \(M_i\) bằng AES-GCM4, trong đó khoá là \(\mathrm{DerivedKey}\), vectơ khởi tạo là \(\mathrm{IV}_i\)và dữ liệu liên kết là chuỗi trống. \(C_i\) là kết quả của quá trình mã hoá này (tức là nối \(C\) và \(T\) trong mục 5.2.1.2 của tài liệu tham khảo AES-GCM được liên kết).
Kết hợp các đoạn đã mã hoá
Cuối cùng, tất cả các phân đoạn được nối với nhau dưới dạng \(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\), đây là văn bản đã mã hoá cuối cùng.
Giải mã
Quá trình giải mã sẽ đảo ngược quá trình mã hoá. Chúng ta sử dụng tiêu đề để lấy\(\mathrm{NoncePrefix}\)và giải mã từng phân đoạn văn bản đã mã hoá.
API có thể (và thường) cho phép truy cập ngẫu nhiên hoặc truy cập vào đầu tệp mà không cần kiểm tra phần cuối tệp. Điều này là có chủ ý vì bạn có thể giải mã \(M_i\) từ \(C_i\)mà không cần giải mã tất cả các khối văn bản đã mã hoá trước đó và còn lại.
Tuy nhiên, các API phải cẩn thận để không cho phép người dùng nhầm lẫn lỗi kết thúc tệp và lỗi giải mã: trong cả hai trường hợp, API có thể phải trả về lỗi và việc bỏ qua sự khác biệt có thể khiến đối thủ có thể cắt bớt tệp một cách hiệu quả.
Tuần tự hoá và phân tích cú pháp khoá
Để chuyển đổi tuần tự một khoá ở định dạng "Tink Proto", trước tiên, chúng ta ánh xạ các tham số theo cách rõ ràng vào proto được cung cấp tại aes_gcm_hkdf_streaming.proto. Bạn cần đặt trường version
thành 0. Sau đó, chúng ta sẽ chuyển đổi tuần tự thông tin này bằng cách chuyển đổi tuần tự proto thông thường và nhúng chuỗi thu được vào giá trị của trường của proto KeyData. Chúng ta đặt trường type_url
thành type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey
. Sau đó, chúng ta đặt key_material_type
thành SYMMETRIC
và nhúng giá trị này vào một tập hợp khoá. Chúng ta thường đặt output_prefix_type
thành RAW
. Trường hợp ngoại lệ là nếu khoá được phân tích cú pháp với một giá trị khác được đặt cho output_prefix_type
, thì Tink có thể ghi RAW
hoặc giá trị trước đó.
Để phân tích cú pháp một khoá, chúng ta đảo ngược quy trình trên (theo cách thông thường khi phân tích cú pháp proto). Trường key_material_type
sẽ bị bỏ qua. Bạn có thể bỏ qua giá trị của output_prefix_type
hoặc từ chối các khoá có output_prefix_type
khác với RAW
. Bạn phải từ chối các khoá có version
khác 0.
Vấn đề đã biết
Việc triển khai hàm mã hoá ở trên dự kiến sẽ không an toàn với việc phân nhánh. Xem phần An toàn khi phân nhánh.
Tài liệu tham khảo
-
Hoang, Reyhanitabar, Rogaway, Vizar, 2015. Mã hoá được xác thực trực tuyến và khả năng chống lại hành vi sử dụng sai số chỉ dùng một lần. CRYPTO 2015. https://eprint.iacr.org/2015/189 ↩
-
Hoang, Shen, 2020. Bảo mật của tính năng mã hoá trực tuyến trong Thư viện Tink của Google. https://eprint.iacr.org/2020/1019 ↩
-
RFC 5869. Hàm dẫn xuất khoá trích xuất và mở rộng (HKDF) dựa trên HMAC. https://www.rfc-editor.org/rfc/rfc5869 ↩
-
NIST SP 800-38D. Đề xuất về Chế độ hoạt động của thuật toán mật mã khối: Chế độ Galois/Counter (GCM) và GMAC. https://csrc.nist.gov/pubs/sp/800/38/d/final ↩