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á do phần cứng chuyên dụng thực hiện 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, mong muốn cung cấp 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ư cold boot (khởi động nguội) tấn công mạng và cuộc tấn công trực tuyến mà trong đó kẻ tấn công có thể rò rỉ hệ thống mà không gây ảnh hưởng hoàn toàn đến thiết bị.

Để giải quyết vấn đề này, Android 11 đã ra mắt tính năng hỗ trợ dành cho khoá gói phần cứng (nếu có hỗ trợ phần cứng). Khoá được gói bằng phần cứng là khoá bộ nhớ chỉ được biết ở dạng thô đối với phần cứng chuyên dụng; phần mềm chỉ thấy và hoạt động với các khoá này ở dạng gói (mã hoá). Phần cứng này phải có khả năng tạo và nhập khoá bộ nhớ, gói khoá bộ nhớ ở 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 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á được gói bằng phần cứng, bao gồm cả phần cứng cần hỗ trợ 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 đến khoá mã hoá thô trong bộ nhớ hệ thống là chỉ đặt chúng trong các ô khoá của một công cụ mã hoá cùng dòng. Tuy nhiên, việc này phương pháp tiếp cận 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á.
  • 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 của FBE, phần mềm vẫn cần phải có khả năng thực hiện công việc mật mã khác như mã hoá tên tệp và khoá dẫn xuất giá trị nhận dạng. 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, khoá lưu trữ sẽ được chuyển thành khoá bọc phần cứng, là loại khoá mà chỉ có đối tượng mới có thể mở gói và sử dụ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. Ngang bằng Ngoài ra, hệ phân cấp khoá được sửa đổi và di 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 cho các tác vụ không thể sử dụng công cụ mã hoá cùng dòng.

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ơ đồ sau đây mô tả hệ phân cấp khoá thông thường cho FBE khi không sử dụng khoá được gói bằng phần cứng:

Hệ phân cấp khoá FBE (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 truyền sang Linux để mở khoá một tập hợp thư mục đã mã hoá cụ thể, chẳng hạn như bộ nhớ được mã hoá thông tin xác thực cho một người dùng Android cụ thể. (Trong kernel, đây là được gọi là khoá chính fscrypt.) Từ khoá này, hạt nhân suy ra 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ị được dùng để xác định khoá cho 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, biểu đồ dưới đây mô tả hệ phân cấp khoá của FBE khi khoá bọc phần cứng được dùng:

Hệ phân cấp khoá FBE (với khoá được bao bọc 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. Gốc nút vẫn đại diện cho khoá mà Android truyền sang Linux để mở khoá một tập hợp các thư mục đã mã hoá. Tuy nhiên, giờ đây khoá đó nằm ở dạng được gói tạm thời và để sử dụng, URL đó phải được truyề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à trực tiếp lập trình giao diện đó vào một khe khoá của công cụ mã hoá nội tuyến. Thao tác này cho phép tệp nội dung được mã hoá/giải mã mà không cần phần mềm có quyền truy cập vào dữ liệu 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 Toán tử blk_crypto_ll_ops::derive_sw_secret, phải là do trình điều khiển bộ nhớ triển khai.

Để lấy inline_encryption_keysw_secret từ khoá lưu trữ thô, phần cứng phải sử dụng KDF được 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. Phương thức này cũng phải sử dụng một nhãn, ngữ cảnh và chuỗi thông tin riêng biệt cho ứng dụng khi lấy mỗi loại khoá con để đảm bảo rằng các khoá con thu được được tách biệt bằng phương thức mã hoá, tức là việc biết một khoá con sẽ không tiết lộ bất kỳ khoá con nào khác. Không bắt buộc kéo giãn khoá vì khoá lưu trữ thô đã là phím ngẫu nhiên thố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, đối với mục đích thử nghiệm, cần phải 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. Phần cứng nên sử dụng KDF này. 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 hệt nhau, bao gồm cả việc lựa chọn ngữ cảnh KDF và nhãn cho từng khoá con.

Bao bọc phím

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

  • Đóng 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.
  • Đó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 tới nhân hệ điều hành Linux để mở khoá bộ nhớ đều 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 trong bộ nhớ hệ thống thì không chỉ sử dụng được khoá đó ra khỏi thiết bị mà còn 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, khái niệm là gói dài hạn được xác định.

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

  • Giao diện để tạo và nhập khoá bộ nhớ, trả về khoá ở dạng gói lâu dài. Các giao diện này được truy cập gián tiếp thông qua KeyMint và các thẻ này tương ứng với thẻ KeyMint TAG_STORAGE_KEY. vold sử dụng khả năng "tạo" để tạo khoá bộ nhớ mới cho Android sử dụng, còn vts_kernel_encryption_test sử dụng khả năng "nhập" để nhập khoá kiểm thử.
  • Giao diện để chuyển đổi khoá bộ nhớ được gói dài hạn thành khoá bộ nhớ đượ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 cần thực hiện một số thay đổi theo phương thức triển khai cụ thể.

Thay đổi về KeyMint

Phải sửa đổi quá trình triển khai KeyMint của thiết bị để hỗ trợ TAG_STORAGE_KEY và triển khai convertStorageKeyToEphemeral.

Trong Keymaster, exportKey đã được dùng thay cho convertStorageKeyToEphemeral.

Thay đổi nhân hệ điều hành 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 các hạt nhân android12android13, đặ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ỏ khoá được gói phần cứng, và triển khai blk_ksm_ll_ops::derive_raw_secret.

Đối với hạt nhân android11, thiết lập 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ỏ khoá được gói phần cứng, và triển khai keyslot_mgmt_ll_ops::derive_raw_secret.

Thử nghiệm

Mặc dù phương thức mã hoá bằng khoá bọc phần cứng khó kiểm tra hơn so với phương thức mã hoá bằng các khoá tiêu chuẩn, bạn vẫn có thể kiểm tra bằng cách nhập khoá kiểm tra và triển khai lại quy trình dẫn xuất khoá mà phần cứng thực hiện. Cách này được triển khai trong vts_kernel_encryption_test. Để chạy kiểm thử nà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 phần cứng (ví dụ: FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicyDmDefaultKeyTest.TestHwWrappedKey) không được bỏ qua do còn hỗ trợ đối với trường hợp không phát hiện được khoá bọc phần cứng, vì kết quả kiểm tra vẫn đang "đã vượt qua" trong trường hợp đó.

Bật phím

Khi tính năng hỗ trợ khoá gói phần cứng của thiết bị hoạt động đúng cách, 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 sử dụng công cụ này để mã hoá siêu dữ liệu và FBE:

  • FBE: thêm cờ wrappedkey_v0 vào tham 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ụ: hãy 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.