Chìa khoá bọc phần cứng

Giống như hầu hết phần mềm mã hoá ổ đĩa và tệp, 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ì phần mềm, phần mềm thường vẫn cần quản lý các khoá mã hoá thô.

Thông thường, đây không phải là vấn đề vì các khoá sẽ không xuất hiện trong một 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ớ nhằm mục đích bảo vệ. Tuy nhiên, chúng tôi muốn tăng cường khả năng bảo vệ trước các loại hình tấn công khác, chẳng hạn như tấn công khởi động nguội và các cuộc tấn công trên mạng, trong đó kẻ tấn công có thể rò rỉ bộ nhớ hệ thống mà không làm hỏng hoàn toàn thiết bị.

Để giải quyết vấn đề này, Android 11 đã ra mắt tính năng hỗ trợ khoá được gói bằng phần cứng, trong đó có hỗ trợ phần cứng. Khoá được bọc phần cứng là khoá lưu trữ chỉ được biết ở dạng thô dành cho phần cứng chuyên dụng; phần mềm chỉ nhìn 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 khoá lưu trữ, gói các khoá lưu trữ ở dạng tạm thời và dài hạn, lấy khoá con, lập trình trực tiếp một khoá con vào một công cụ mã hoá cùng 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 tuyến (hoặc phần cứng mã hoá nội tuyến) là 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ị lưu trữ. 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 khoá bọc phần cứng, bao gồm cả những tính năng hỗ trợ phần cứng cần thiết cho tính năng này. Cuộc thảo luận này tập trung vào mã hoá dựa trên tệp (FBE), nhưng giải pháp này cũng áp dụng cho mã hoá siêu dữ liệu.

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á đó trong các khe khoá của công cụ mã hoá nội tuyến. 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 khoá.
  • Các công cụ mã hoá nội tuyến thường mất nội dung của các khe khoá nếu bộ điều khiển bộ nhớ (thường là UFS hoặc eMMC) được đặt lại. Việc đặt lại bộ điều khiển bộ nhớ là một quy trình khôi phục lỗi tiêu chuẩn được thực thi nếu một số loại lỗi bộ nhớ nhất định xảy ra và các lỗi như vậy có thể xảy ra bất cứ lúc nào. Do đó, khi đang sử dụng phương thức mã hoá nội tuyến, 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ể 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 tác vụ mã hoá khác 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 sự cố này, các khoá lưu trữ sẽ được chuyển thành khoá bọc phần cứng. Chỉ phần cứng chuyên dụng mới có thể mở gói và sử dụng các khoá này. Đ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à một phần được chuyển sang phần cứng này, cho phép trả về 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 tuyến.

Hệ phân cấp khoá

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

Sơ đồ dưới đây mô tả 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 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 hạt nhân Linux để mở khoá một nhóm thư mục mã hoá cụ thể, chẳng hạn như bộ nhớ được mã hoá bằng thông tin xác thực cho một người dùng Android cụ thể. (Trong hạt nhân, khoá này được gọi là khoá chính fscrypt.) Từ khoá này, nhân lấy các khoá con sau:

  • Giá trị nhận dạng khoá. Giá trị này không dùng để mã hoá mà là một giá trị dùng để xác định khoá bảo vệ một tệp hoặc thư mục cụ thể.
  • 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 phần cứng:

Hệ phân cấp khoá FBE (với khoá được gói bằng phần cứng)
Hình 2. Hệ phân cấp khoá FBE (với 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 di chuyển. 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 các thư mục đã mã hoá. Tuy nhiên, hiện tại, khoá đó ở dạng được gói tạm thời và để sử dụng, khoá đó phải được chuyển đến phần cứng chuyên dụng. Phần cứng này phải triển khai 2 giao diện lấy một khoá được gói tạm thời:

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

Để lấy inline_encryption_keysw_secret từ khoá bộ nhớ thô, phần cứng phải sử dụng KDF có khả năng mã hoá mạnh. KDF này phải tuân theo các phương pháp hay nhất về mật mã; phải có độ mạnh bảo mật tối thiểu là 256 bit, tức là đủ cho mọi thuật toán được sử dụng sau này. Mã này cũng phải sử dụng nhãn, ngữ cảnh và chuỗi thông tin dành riêng cho ứng dụng riêng biệt khi lấy từng loại khoá con để đảm bảo rằng các khoá con thu được sẽ được phân tách bằng mật mã, tức là việc biết về một trong các khoá đó sẽ không cho thấy bất kỳ khoá con nào khác. Bạn không cần phải kéo giãn 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 bảo mật. Tuy nhiên, để kiểm thử, bạn cần triển khai lại cùng một KDF trong mã kiểm thử. Hiện tại, một KDF đã được xem xét và triển khai; bạn có thể tìm thấy KDF này trong mã nguồn cho vts_kernel_encryption_test. Bạn nên sử dụng KDF này cho phần cứng. KDF này sử dụng NIST SP 800-108 "KDF ở chế độ bộ đếm" với AES-256-CMAC làm PRF. Xin lưu ý rằng để tương thích, tất cả các phần của thuật toán phải giống nhau, bao gồm cả lựa chọn ngữ cảnh KDF và nhãn cho mỗi khoá con.

Đóng gói khoá

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

  • 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 khi 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, ổn định đượ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 truyền đến nhân hệ điều hành 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 khoá đang sử dụng từ bộ nhớ hệ thống, thì khoá đó không chỉ không sử dụng được khi thiết bị tắt 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 mã hoá của các khoá trên ổ đĩa để có thể mở khoá các khoá đó ngay từ đầu. Các khoá thô sẽ hoạt động cho mục đích này. Tuy nhiên, bạn không nên để khoá thô xuất hiện trong bộ nhớ hệ thống để không bao giờ có thể trích xuất khoá thô để sử dụng ngoài thiết bị, ngay cả khi trích xuất tại thời điểm khởi động. Vì lý do này, chúng tôi đã xác định khái niệm gói dài hạn.

Để hỗ trợ quản lý các khoá được gói theo 2 cách 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 khoá lưu trữ, trả về các khoá đó ở dạng được gói trong thời gian dài. Các giao diện này được truy cập gián tiếp thông qua KeyMint và tương ứng với thẻ KeyMint TAG_STORAGE_KEY. vold sử dụng khả năng "tạo" để tạo các khoá bộ nhớ mới cho Android sử dụng, trong khi vts_kernel_encryption_test sử dụng khả năng "nhập" để nhập các khoá kiểm thử.
  • Một giao diện để chuyển đổi một khoá lưu trữ được gói trong thời gian dài thành một khoá lưu trữ được gói tạm thời. Điều này tương ứng với phương thức KeyMint convertStorageKeyToEphemeral. Cả voldvts_kernel_encryption_test đều sử dụng phương thức này để mở khoá bộ nhớ.

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

Cần thay đổi 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 như vold, cũng như tính năng hỗ trợ hạt nhân Linux trong blk-crypto, fscryptdm-default-key.

Tuy nhiên, bạn bắt buộc phải thực hiện một số thay đổi cụ thể về việc triển khai.

Các thay đổi của KeyMint

Bạn phải sửa đổi cách triển khai KeyMint của thiết bị để hỗ trợ TAG_STORAGE_KEY và triển khai phương thức convertStorageKeyToEphemeral.

Trong Keymaster, exportKey được sử dụng thay vì convertStorageKeyToEphemeral.

Thay đổi về nhân Linux

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

Đối với các hạt nhân android14 trở lên, hãy đặt BLK_CRYPTO_KEY_TYPE_HW_WRAPPED trong blk_crypto_profile::key_types_supported, tạo blk_crypto_ll_ops::keyslot_programblk_crypto_ll_ops::keyslot_evict hỗ trợ lập trình/loại bỏ các khoá được gói bằng phần cứng và triển khai blk_crypto_ll_ops::derive_sw_secret.

Đối với nhân android12android13, hãy đặt BLK_CRYPTO_FEATURE_WRAPPED_KEYS trong blk_keyslot_manager::features, tạo blk_ksm_ll_ops::keyslot_programblk_ksm_ll_ops::keyslot_evict hỗ trợ lập trình/loại bỏ các khoá được gói bằng phần cứng và triển khai blk_ksm_ll_ops::derive_raw_secret.

Đối với hạt nhân android11, hãy đặt BLK_CRYPTO_FEATURE_WRAPPED_KEYS trong keyslot_manager::features, tạo keyslot_mgmt_ll_ops::keyslot_programkeyslot_mgmt_ll_ops::keyslot_evict hỗ trợ lập trình/loại bỏ các khoá được gói bằng phần cứng và triển khai keyslot_mgmt_ll_ops::derive_raw_secret.

Thử nghiệm

Mặc dù việc mã hoá bằng khoá được gói bằng phần cứng khó kiểm thử hơn so với việc mã hoá bằng khoá tiêu chuẩn, nhưng bạn vẫn có thể kiểm thử bằng cách nhập khoá kiểm thử và triển khai lại quá trình tạo khoá mà phần cứng thực hiện. Việc này được triển khai trong vts_kernel_encryption_test. Để chạy 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 được tính năng hỗ trợ khoá được gói bằng phần cứng, vì kết quả kiểm thử vẫn là "đã vượt qua" trong trường hợp đó.

Bật phím

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

  • FBE: thêm cờ wrappedkey_v0 vào thông số fileencryption. Ví dụ: sử dụng fileencryption=::inlinecrypt_optimized+wrappedkey_v0. Để biết thêm thông tin chi tiết, hãy xem tài liệu về FBE.
  • Mã hoá siêu dữ liệu: thêm cờ wrappedkey_v0 vào tham số metadata_encryption. Ví dụ: sử dụng metadata_encryption=:wrappedkey_v0. Để biết thêm thông tin, hãy xem tài liệu về việc mã hoá siêu dữ liệu.