Khoá được gói bằng phần cứng

Giống như hầu hết phần mềm mã hoá tệp và ổ đĩa, tính năng mã hoá bộ nhớ của Android thường dựa vào các khoá mã hoá thô có trong bộ nhớ hệ thống để có thể thực hiện quá trình mã hoá. Ngay cả khi quá trình mã hoá được thực hiện bằng phần cứng chuyên dụng thay vì bằng phần mềm, thì phần mềm thường vẫn cần quản lý các khoá mã hoá thô.

Theo truyền thống, đây không được coi là vấn đề vì các khoá không có mặt trong cuộc tấn công ngoại tuyến. Đây là loại tấn công chính mà tính năng mã hoá bộ nhớ được thiết kế để bảo vệ. Tuy nhiên, có mong muốn tăng cường khả năng bảo vệ trước các loại tấn công khác, chẳng hạn như tấn công khởi động nguội và tấn công trực tuyến, trong đó kẻ tấn công có thể làm rò rỉ bộ nhớ hệ thống mà không làm ảnh hưởng hoàn toàn đến thiết bị.

Để giải quyết vấn đề này, Android 11 đã giới thiệu tính năng hỗ trợ cho các khoá được gói bằng phần cứng, trong đó có hỗ trợ phần cứng. Các khoá được gói bằng phần cứng là các khoá bộ nhớ mà chỉ phần cứng chuyên dụng mới biết ở dạng thô; phần mềm chỉ thấy và hoạt động với các khoá này ở dạng được gói (đã mã hoá). Phần cứng này phải có khả năng tạo và nhập các khoá bộ nhớ, gói các khoá bộ nhớ ở dạng tạm thời và dài hạn, lấy các khoá con, trực tiếp lập trình một khoá con vào một công cụ mã hoá nội dòng và trả về một khoá con riêng biệt cho phần mềm.

Lưu ý: Công cụ mã hoá nội dòng (hoặc phần cứng mã hoá nội dòng) đề cập đến phần cứng mã hoá/giải mã dữ liệu trong khi dữ liệu đang trên đường đến/từ thiết bị bộ nhớ. Thông thường, đây là bộ điều khiển máy chủ UFS hoặc eMMC triển khai các tiện ích mã hoá do thông số kỹ thuật JEDEC tương ứng xác định.

Thiết kế

Phần này trình bày thiết kế của tính năng các khoá được gói bằng phần cứng, bao gồm cả những yêu cầu về hỗ trợ phần cứng. Thảo luận này tập trung vào tính năng mã hoá dựa trên tệp (FBE), nhưng giải pháp này cũng áp dụng cho tính năng mã hoá siêu dữ liệu too.

Một cách để tránh cần các khoá mã hoá thô trong bộ nhớ hệ thống là chỉ giữ các khoá này trong các khe khoá của công cụ mã hoá nội dòng. Tuy nhiên, phương pháp này gặp phải một số vấn đề:

  • Số lượng khoá mã hoá có thể vượt quá số lượng khe khoá.
  • Công cụ mã hoá nội dòng thường mất nội dung của các khe khoá nếu bộ điều khiển máy chủ bộ nhớ được đặt lại. Việc đặt lại bộ điều khiển máy chủ bộ nhớ là một quy trình khôi phục lỗi tiêu chuẩn được thực thi nếu xảy ra một số loại lỗi bộ nhớ nhất định và các lỗi như vậy có thể xảy ra bất cứ lúc nào. Do đó, khi sử dụng tính năng mã hoá nội dòng, hệ điều hành phải luôn sẵn sàng lập trình lại các khe khoá mà không cần người dùng can thiệp.
  • Bạn chỉ có thể sử dụng công cụ mã hoá nội tuyến để mã hoá/giải mã toàn bộ khối dữ liệu trên ổ đĩa. Tuy nhiên, trong trường hợp FBE, phần mềm vẫn cần có khả năng thực hiện các công việc mã hoá khác, chẳng hạn như mã hoá tên tệp và lấy giá trị nhận dạng khoá. Phần mềm vẫn cần quyền truy cập vào các khoá FBE thô để thực hiện công việc khác này.

Để tránh những vấn đề này, các khoá bộ nhớ sẽ được chuyển thành các khoá được gói bằng phần cứng. Bạn chỉ có thể giải gói và sử dụng các khoá này bằng phần cứng chuyên dụng. Điều này cho phép hỗ trợ số lượng khoá không giới hạn. Ngoài ra, hệ phân cấp khoá được sửa đổi và chuyển một phần sang phần cứng này, cho phép trả về một khoá con cho phần mềm đối với các tác vụ không thể sử dụng công cụ mã hoá nội dòng.

Hệ phân cấp khoá

Bạn có thể lấy các khoá từ các khoá khác bằng cách sử dụng hàm lấy khoá (KDF), chẳng hạn như HKDF, dẫn đến hệ phân cấp khoá.

Sơ đồ sau đây mô tả hệ phân cấp khoá điển hình cho FBE khi không sử dụng các khoá được gói bằng phần cứng:

Hệ phân cấp khoá FBE (tiêu chuẩn)
Hình 1. Hệ phân cấp khoá FBE (tiêu chuẩn)

Khoá lớp FBE là khoá mã hoá thô mà Android chuyển đến nhân Linux để mở khoá một tập hợp thư mục được mã hoá cụ thể, chẳng hạn như bộ nhớ được mã hoá bằng thông tin đăng nhập cho một người dùng Android cụ thể. (Trong nhân, khoá này được gọi là khoá chính fscrypt.) Từ khoá này, nhân sẽ lấy các khoá con sau:

  • Giá trị nhận dạng khoá. Giá trị này không được dùng để mã hoá mà là một giá trị dùng để xác định khoá mà một tệp hoặc thư mục cụ thể được bảo vệ.
  • Khoá mã hoá nội dung tệp
  • Khoá mã hoá tên tệp

Ngược lại, sơ đồ sau đây mô tả hệ phân cấp khoá cho FBE khi sử dụng các khoá được gói bằng phần cứng:

Hệ thống phân cấp khoá FBE (có khoá được bao bọc bằng phần cứng)
Hình 2. Hệ phân cấp khoá FBE (có khoá được gói bằng phần cứng)

So với trường hợp trước, một cấp bổ sung đã được thêm vào hệ phân cấp khoá và khoá mã hoá nội dung tệp đã được chuyển đi. Nút gốc vẫn đại diện cho khoá mà Android chuyển đến Linux để mở khoá một tập hợp thư mục được mã hoá. Tuy nhiên, hiện tại, khoá đó ở dạng được gói tạm thời và để sử dụng, bạn phải chuyển khoá đó đến phần cứng chuyên dụng. Phần cứng này phải triển khai 2 giao diện nhận một khoá được gói tạm thời:

  • Một giao diện để lấy inline_encryption_key và trực tiếp lập trình khoá đó vào một khe khoá của công cụ mã hoá nội dòng. Điều này cho phép mã hoá/giải mã nội dung tệp mà phần mềm không có quyền truy cập vào khoá thô. Trong các nhân chung của Android, giao diện này tương ứng với thao tác blk_crypto_ll_ops::keyslot_program. Trình điều khiển bộ nhớ phải triển khai thao tác này.
  • Một giao diện để lấy và trả về sw_secret ("bí mật phần mềm" – trước đây được gọi là "bí mật thô"), là khoá mà Linux sử dụng để lấy các khoá con cho mọi thứ khác ngoài tính năng mã hoá nội dung tệp. Trong các nhân chung của Android, giao diện này tương ứng với thao tác blk_crypto_ll_ops::derive_sw_secret. Trình điều khiển bộ nhớ phải triển khai thao tác này.

Để lấy inline_encryption_keysw_secret từ khoá bộ nhớ thô, phần cứng phải sử dụng KDF mạnh về mặt mật mã. KDF này phải tuân theo các phương pháp hay nhất về mật mã; KDF này phải có độ mạnh về bảo mật ít nhất là 256 bit, tức là đủ cho mọi thuật toán được sử dụng sau này. KDF này cũng phải sử dụng nhãn và ngữ cảnh riêng biệt khi lấy từng loại khoá con để đảm bảo rằng các khoá con kết quả được cách ly về mặt mật mã, tức là kiến thức về một trong số đó không tiết lộ bất kỳ khoá con nào khác. Bạn không cần kéo dài khoá vì khoá bộ nhớ thô đã là một khoá ngẫu nhiên đồng nhất.

Về mặt kỹ thuật, bạn có thể sử dụng bất kỳ KDF nào đáp ứng các yêu cầu về bảo mật. Tuy nhiên, cho mục đích kiểm thử, vts_kernel_encryption_test triển khai cùng một KDF trong phần mềm để tái tạo văn bản mã hoá trên đĩa và xác minh rằng văn bản đó chính xác. Để dễ dàng kiểm thử và đảm bảo rằng bạn sử dụng KDF an toàn và đã được xem xét, bạn nên triển khai KDF mặc định mà quy trình kiểm thử kiểm tra. Đối với phần cứng sử dụng KDF khác, hãy xem Kiểm thử các khoá được gói để biết cách định cấu hình quy trình kiểm thử cho phù hợp.

Gói khoá

Để đáp ứng các mục tiêu bảo mật của các khoá được gói bằng phần cứng, 2 loại gói khoá được xác định:

  • Gói tạm thời: phần cứng mã hoá khoá thô bằng một khoá được tạo ngẫu nhiên ở mỗi lần khởi động và không được hiển thị trực tiếp bên ngoài phần cứng.
  • Gói dài hạn: phần cứng mã hoá khoá thô bằng một khoá duy nhất, liên tục được tích hợp vào phần cứng và không được hiển thị trực tiếp bên ngoài phần cứng.

Tất cả các khoá được chuyển đến nhân Linux để mở khoá bộ nhớ đều được gói tạm thời. Điều này đảm bảo rằng nếu kẻ tấn công có thể trích xuất một khoá đang sử dụng từ bộ nhớ hệ thống, thì khoá đó không chỉ không sử dụng được trên thiết bị mà còn không sử dụng được trên thiết bị sau khi khởi động lại.

Đồng thời, Android vẫn cần có khả năng lưu trữ phiên bản được mã hoá của các khoá trên đĩa để có thể mở khoá các khoá đó. Các khoá thô sẽ phù hợp với mục đích này. Tuy nhiên, bạn nên không bao giờ để các khoá thô có mặt trong bộ nhớ hệ thống để không bao giờ có thể trích xuất các khoá đó để sử dụng trên thiết bị, ngay cả khi được trích xuất vào thời gian khởi động. Vì lý do này, khái niệm về gói dài hạn được xác định.

Để hỗ trợ quản lý các khoá được gói theo 2 cách khác nhau này, phần cứng phải triển khai các giao diện sau:

  • Các giao diện để tạo và nhập các khoá bộ nhớ, trả về các khoá đó ở dạng được gói dài hạn. Giao diện tạo được vold sử dụng để tạo các khoá bộ nhớ mới cho Android sử dụng. Giao diện nhập được vts_kernel_encryption_test sử dụng để nhập các khoá kiểm thử.
  • Một giao diện để chuyển đổi khoá bộ nhớ được gói dài hạn thành một khoá bộ nhớ được gói tạm thời. Giao diện này được cả voldvts_kernel_encryption_test sử dụng để mở khoá bộ nhớ.

Thuật toán gói khoá là một chi tiết triển khai, nhưng thuật toán này phải sử dụng AEAD mạnh, chẳng hạn như AES-256-GCM với IV ngẫu nhiên.

Các thay đổi cần thiết về phần mềm

AOSP đã có một khung cơ bản để hỗ trợ các khoá được gói bằng phần cứng. Điều này bao gồm cả tính năng hỗ trợ trong các thành phần không gian người dùng, chẳng hạn như vold, cũng như tính năng hỗ trợ nhân Linux trong blk-crypto, fscryptdm-default-key.

Các thay đổi về nhân Linux

Bạn phải sửa đổi trình điều khiển nhân Linux cho bộ điều khiển bộ nhớ của thiết bị có hỗ trợ mã hoá nội dòng để hỗ trợ các khoá được gói bằng phần cứng.

Đối với nhân android17 trở lên:

  • Đặt BLK_CRYPTO_KEY_TYPE_HW_WRAPPED trong blk_crypto_profile::key_types_supported.
  • Làm cho blk_crypto_ll_ops::keyslot_program hỗ trợ lập trình các khoá được gói bằng phần cứng.
  • Làm cho blk_crypto_ll_ops::keyslot_evict hỗ trợ loại bỏ các khoá được gói bằng phần cứng.
  • Triển khai blk_crypto_ll_ops::derive_sw_secret, blk_crypto_ll_ops::import_key, blk_crypto_ll_ops::generate_key, và blk_crypto_ll_ops::prepare_key.

Đối với nhân android14, android15android16:

  • Đặt BLK_CRYPTO_KEY_TYPE_HW_WRAPPED trong blk_crypto_profile::key_types_supported.
  • Làm cho blk_crypto_ll_ops::keyslot_program hỗ trợ lập trình các khoá được gói bằng phần cứng.
  • Làm cho blk_crypto_ll_ops::keyslot_evict hỗ trợ loại bỏ các khoá được gói bằng phần cứng.
  • Triển khai blk_crypto_ll_ops::derive_sw_secret.

Đối với nhân android12android13:

  • Đặt BLK_CRYPTO_FEATURE_WRAPPED_KEYS trong blk_keyslot_manager::features.
  • Làm cho blk_ksm_ll_ops::keyslot_program hỗ trợ lập trình các khoá được gói bằng phần cứng.
  • Làm cho blk_ksm_ll_ops::keyslot_evict hỗ trợ loại bỏ các khoá được gói bằng phần cứng.
  • Triển khai blk_ksm_ll_ops::derive_raw_secret.

Đối với nhân android11:

  • Đặt BLK_CRYPTO_FEATURE_WRAPPED_KEYS trong keyslot_manager::features.
  • Làm cho keyslot_mgmt_ll_ops::keyslot_program hỗ trợ lập trình các khoá được gói bằng phần cứng.
  • Làm cho keyslot_mgmt_ll_ops::keyslot_evict hỗ trợ loại bỏ các khoá được gói bằng phần cứng.
  • Triển khai keyslot_mgmt_ll_ops::derive_raw_secret.

Các thay đổi về KeyMint (cũ)

Trong phiên bản hiện tại của các khoá được gói bằng phần cứng (wrappedkey), quá trình tạo, nhập và chuẩn bị các khoá được gói bằng phần cứng sử dụng ioctl nhân Linux BLKCRYPTOGENERATEKEY, BLKCRYPTOIMPORTKEYBLKCRYPTOPREPAREKEY. Các ioctl này tương ứng với các phương thức trong struct blk_crypto_ll_ops. Trình điều khiển bộ nhớ triển khai các phương thức này và giao tiếp với phần cứng gói khoá để thực hiện thao tác được yêu cầu. Để biết thêm thông tin về các ioctl này, hãy xem tài liệu về nhân Linux..

Các ioctl này đã được thêm vào Linux 6.16. Trên các thiết bị không khởi chạy bằng giải pháp dựa trên ioctl, một giải pháp khác sử dụng KeyMint Android (hoặc trước đây là KeyMaster) sẽ được sử dụng. Giải pháp cũ (wrappedkey_v0) không tương thích với nhân Linux chính hoặc với giải pháp hiện tại. Giải pháp cũ sử dụng chức năng KeyMint sau:

  • Hỗ trợ TAG_STORAGE_KEY, cả cho việc tạo và nhập khoá.
  • Hỗ trợ phương thức convertStorageKeyToEphemeral.

Bạn chỉ cần chức năng KeyMint này trên các thiết bị sử dụng giải pháp cũ, tương ứng với wrappedkey_v0 trong tệp fstab.

Các thiết bị sử dụng giải pháp hiện tại, tương ứng với wrappedkey trong tệp fstab, không cần triển khai chức năng KeyMint này.

Kiểm thử các khoá được gói

Mặc dù khó kiểm thử tính năng mã hoá bằng các khoá được gói bằng phần cứng hơn là mã hoá bằng các khoá thô, nhưng bạn vẫn có thể kiểm thử bằng cách nhập một khoá kiểm thử và triển khai lại quá trình lấy khoá mà phần cứng thực hiện. Điều này được triển khai trong vts_kernel_encryption_test. Để chạy quy trình kiểm thử này, hãy chạy:

atest -v vts_kernel_encryption_test

Đọc nhật ký kiểm thử và xác minh rằng các trường hợp kiểm thử khoá được gói bằng phần cứng (ví dụ: FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicyDmDefaultKeyTest.TestHwWrappedKey) không bị bỏ qua do không phát hiện thấy tính năng hỗ trợ các khoá được gói bằng phần cứng, vì kết quả kiểm thử vẫn là "đạt" trong trường hợp đó.

Theo mặc định, vts_kernel_encryption_test giả định phần cứng triển khai một KDF mà phần cứng này gọi là kdf1. KDF này thuộc họ KDF ở chế độ bộ đếm từ NIST SP 800-108 và sử dụng AES-256-CMAC làm hàm giả ngẫu nhiên. Để biết thêm thông tin về CMAC, hãy xem thông số kỹ thuật CMAC. KDF sử dụng các ngữ cảnh và nhãn cụ thể khi lấy từng khoá con. Phần cứng phải triển khai KDF này, bao gồm cả việc lựa chọn chính xác ngữ cảnh, nhãn và định dạng của chuỗi đầu vào cố định khi lấy từng khoá con.

Tuy nhiên, vts_kernel_encryption_test cũng triển khai các KDF bổ sung kdf2 đến kdf4. Các KDF này cũng an toàn như kdf1 và chỉ khác nhau ở việc lựa chọn ngữ cảnh, nhãn và định dạng của chuỗi đầu vào cố định. Các KDF này chỉ tồn tại để phù hợp với các phần cứng khác nhau.

Đối với các thiết bị sử dụng KDF khác, hãy đặt thuộc tính hệ thống ro.crypto.hw_wrapped_keys.kdf trong PRODUCT_VENDOR_PROPERTIES thành tên của KDF như được xác định trong mã nguồn kiểm thử. Điều này khiến vts_kernel_encryption_test kiểm tra KDF đó thay vì kdf1. Ví dụ: để chọn kdf2, hãy sử dụng:

PRODUCT_VENDOR_PROPERTIES += ro.crypto.hw_wrapped_keys.kdf=kdf2

Đối với các thiết bị sử dụng KDF mà quy trình kiểm thử không hỗ trợ, hãy thêm một cách triển khai KDF đó vào quy trình kiểm thử và đặt tên duy nhất cho KDF đó.

Bật các khoá được gói

Khi tính năng hỗ trợ khoá được gói bằng phần cứng của thiết bị hoạt động chính xác, hãy thực hiện các thay đổi sau đối với tệp fstab của thiết bị để Android sử dụng tính năng này cho FBE và mã hoá siêu dữ liệu:

  • FBE: thêm cờ wrappedkey (hoặc wrappedkey_v0 cho phiên bản cũ) vào tham số fileencryption. Ví dụ: sử dụng fileencryption=::inlinecrypt_optimized+wrappedkey. Để biết thêm chi tiết, hãy xem tài liệu về FBE.
  • Mã hoá siêu dữ liệu: thêm cờ wrappedkey (hoặc wrappedkey_v0 cho phiên bản cũ) vào tham số metadata_encryption. Ví dụ: sử dụng metadata_encryption=:wrappedkey. Để biết thêm chi tiết, hãy xem tài liệu về mã hoá siêu dữ liệu.

Trong mỗi trường hợp, có 2 phiên bản của cờ:

  • wrappedkey, được Android 17 và trở lên hỗ trợ, cho phép phiên bản hiện tại của các khoá được gói bằng phần cứng. Phiên bản này tương thích với nhân Linux chính.
  • wrappedkey_v0, được Android 11 trở lên hỗ trợ, cho phép phiên bản cũ của các khoá được gói bằng phần cứng. Phiên bản này không tương thích với nhân Linux chính. Phiên bản này uỷ quyền một số thao tác thông qua KeyMint và sử dụng định dạng không tiêu chuẩn trên đĩa. Để biết thêm thông tin, hãy xem Các thay đổi về KeyMint (cũ).

Trên các thiết bị khởi chạy bằng Android 17 trở lên, hãy ưu tiên wrappedkey.

Trên các thiết bị đã khởi chạy bằng wrappedkey_v0, hãy tiếp tục sử dụng wrappedkey_v0 để tương thích ngược.