| <html devsite><head> |
| <title>元数据加密</title> |
| <meta name="project_path" value="/_project.yaml"/> |
| <meta name="book_path" value="/_book.yaml"/> |
| </head> |
| <body> |
| <!-- |
| Copyright 2018 The Android Open Source Project |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| --> |
| |
| <p> |
| Android 7.0 及更高版本支持<a href="/security/encryption/file-based">文件级加密</a> (FBE)。采用 FBE 时,可以使用不同的密钥对不同的文件进行加密,并且可以对加密文件进行单独解密。这些密钥可用于加密文件内容和文件名。采用 FBE 时,其他信息(例如目录布局、文件大小、权限和创建/修改时间)不会被加密。这些统称为“文件系统元数据”。 |
| </p> |
| <p> |
| Android 9 引入了对存在硬件支持的元数据加密的支持。借助元数据加密,启动时出现的单个密钥会加密未通过 FBE 进行加密的任何内容。该密钥受到 Keymaster 的保护,而 Keymaster 受到验证启动的保护。 |
| </p> |
| <h2 id="implementation">实现</h2> |
| <p> |
| 您可以在搭载 Android 9 的新设备上设置元数据加密,只需设置元数据文件系统,更改 init 序列,然后在设备的 fstab 文件中开启元数据加密即可进行设置。 |
| </p> |
| <h3 id="hardware-requirements">硬件要求</h3> |
| <p> |
| 元数据加密只能在数据分区首次进行格式化时设置。因此,该功能仅适用于新设备;OTA 不应更改此设置。 |
| </p> |
| <p> |
| 为了支持当前的元数据加密,您的硬件必须支持使用<a href="https://blog.google/topics/connected-workspaces/pixel-security-better-faster-stronger/">内嵌加密引擎</a>进行文件级加密。<code>fstab.hardware</code> 中的用户数据分区的 <code>fileencryption=ice</code> 指令指明了这一点。 |
| </p> |
| <p> |
| 此外,内核中必须存在并启用 <code>dm-default-key</code> 模块。 |
| </p> |
| <h3 id="set-up-metadata-filesystem">设置元数据文件系统</h3> |
| <p> |
| 由于在元数据加密密钥出现之前,用户数据分区中的所有内容均无法读取,因此分区表必须留出一个名为“元数据分区”的单独分区,用于存储保护该密钥的 Keymaster Blob。该元数据分区的大小应为 16MB。</p> |
| <p> |
| <code>fstab.hardware</code> 必须针对在该分区上的元数据系统纳入一个条目,并将其装载到 <code>/metadata</code>(包括 <code>formattable</code> 标记),以确保在启动时对其进行格式化。f2fs 文件系统不适用于较小的分区;在较小分区中,我们建议您改为使用 ext4。例如:</p> |
| |
| <pre class="prettyprint">/dev/block/bootdevice/by-name/metadata /metadata ext4 noatime,nosuid,nodev,discard wait,check,formattable</pre> |
| <p>要确保 <code>/metadata</code> 装载点存在,请在 <code>BoardConfig-common.mk</code> 中添加下面这行代码:</p> |
| |
| <pre class="prettyprint">BOARD_USES_METADATA_PARTITION := true</pre> |
| |
| <h3 id="changes-to-the-init-sequence">更改 init 序列</h3> |
| <p> |
| 在使用元数据加密时,必须在装载 <code>/data</code> 之前运行 <code>vold</code>。为了确保其提前足够长的时间开始运行,请将以下节添加到 <code>init.hardware.rc</code> 中:</p> |
| |
| <pre class="prettyprint"># We need vold early for metadata encryption |
| on early-fs |
| start vold</pre> |
| <p> |
| Keymaster 必须在 init 尝试装载 <code>/data</code> 之前运行并准备就绪。 |
| </p> |
| <p> |
| <code>init.hardware.rc</code> 应该已经包含一个 <code>mount_all</code> 指令,用于将 <code>/data</code> 本身装载到 <code>on |
| late-fs</code> 节中。请在这行代码前面添加以下指令,以执行 <code>wait_for_keymaster</code> 服务:</p> |
| |
| <pre class="prettyprint">on late-fs |
| … |
| # Wait for keymaster |
| exec_start wait_for_keymaster |
| |
| # Mount RW partitions which need run fsck |
| mount_all /vendor/etc/fstab.${ro.boot.hardware.platform} --late</pre> |
| |
| <h3 id="switching-on-metadata-encryption">开启元数据加密</h3> |
| <p> |
| 最后,将 <code>keydirectory=/metadata/vold/metadata_encryption</code> 添加到用户数据的 <code>fstab.hardware</code> 条目中:</p> |
| |
| <pre class="prettyprint">/dev/block/bootdevice/by-name/userdata /data f2fs noatime,nosuid,nodev,discard latemount,wait,check,fileencryption=ice,keydirectory=/metadata/vold/metadata_encryption,quota,formattable</pre> |
| |
| <h2 id="validation">验证</h2> |
| <p>在实现元数据加密时,请注意以下常见问题并测试您的实现。 |
| </p> |
| |
| <h3 id="common-issues">常见问题</h3> |
| <p> |
| 在调用 <code>mount_all</code>(用于装载元数据加密的 <code>/data</code> 分区)时,<code>init</code> 会执行 vdc 工具。vdc 工具会通过 <code>vold</code> 连接到 <code>binder</code>,以设置元数据加密的设备并装载分区。在此调用期间,<code>init</code> 会被屏蔽,并且在 <code>mount_all</code> 完成之前,尝试读取或设置 <code>init</code> 属性的操作也会被屏蔽。在此阶段,如果 <code>vold</code> 的任何一部分工作在读取或设置某个属性时被直接或间接屏蔽,则会导致死锁。请务必确保 <code>vold</code> 能够完成读取密钥、与 Keymaster 交互以及装载数据目录的工作,而无需与 <code>init</code> 进一步交互。 |
| </p> |
| <p>如果 Keymaster 在 <code>mount_all</code> 运行时没有完全启动,就不会响应 <code>vold</code>,直到从 <code>init</code> 读取到某些属性为止,从而导致上述死锁。按照相关规定将 <code>exec_start wait_for_keymaster</code> 放置在相关的 <code>mount_all</code> 调用之前,可确保 Keymaster 提前完全运行,从而避免此类死锁。 |
| </p> |
| |
| <h3 id="metadata-encryption-test">元数据加密测试</h3> |
| <p> |
| 我们会将这些测试放到上游,但与此同时,请向 <code>Android.bp</code> 中添加几行代码,并将 <code>check_encryption.cpp</code> 添加到 <code><a href="https://android.googlesource.com/platform/system/vold/+/master">platform/system/vold</a></code> 以测试您的实现。</p> |
| |
| <h4 id="changes-to-android-bp">对 <code>Android.bp</code> 的更改</h4> |
| <p>下面列出了对 <code>Android.bp</code> 的更改。 |
| </p> |
| |
| <pre class="prettyprint">... |
| } |
| |
| cc_binary { |
| name: "vold", |
| defaults: [ |
| "vold_default_flags", |
| "vold_default_libs", |
| ], |
| |
| srcs: ["main.cpp"], |
| static_libs: ["libvold"], |
| product_variables: { |
| arc: { |
| static_libs: [ |
| "arc_services_aidl", |
| "libarcobbvolume", |
| ], |
| }, |
| }, |
| init_rc: [ |
| "vold.rc", |
| "wait_for_keymaster.rc", |
| ], |
| |
| required: [ |
| <strong>"check_encryption",</strong> |
| "mke2fs", |
| "vold_prepare_subdirs", |
| "wait_for_keymaster", |
| ], |
| } |
| ... |
| <strong> |
| } |
| |
| cc_binary { |
| name: "check_encryption", |
| defaults: ["vold_default_flags"], |
| srcs: [ |
| "FileDeviceUtils.cpp", |
| "check_encryption.cpp", |
| ], |
| shared_libs: [ |
| "libbase", |
| ], |
| }</strong></pre> |
| |
| <h4 id="add-check_encryption-cpp">添加 check_encryption.cpp</h4> |
| |
| <p>将 <code>check_encryption.cpp</code> 添加到 <code><a href="https://android.googlesource.com/platform/system/vold/+/master">platform/system/vold</a></code> 中。</p> |
| |
| <pre class="prettyprint">/* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include "FileDeviceUtils.h" |
| |
| #include <cmath> |
| #include <string> |
| |
| #include <assert.h> |
| #include <stdio.h> |
| |
| #include <android-base/file.h> |
| #include <android-base/logging.h> |
| #include <android-base/unique_fd.h> |
| using android::base::unique_fd; |
| using android::base::ReadFileToString; |
| using android::base::WriteStringToFile; |
| |
| namespace android { |
| namespace vold { |
| const size_t sectorsize = 1 << 12; |
| const int sectorcount = 1024; |
| static double randomness_score(const std::string& checkme) { |
| unsigned int freq[256] = {0}; |
| unsigned int sum = 256; |
| double loglaplace = 0; |
| for (auto b : checkme) { |
| loglaplace -= 8 + log2(static_cast<double>(++freq[static_cast<uint8_t>(b)]) / (sum++)); |
| } |
| return loglaplace; |
| LOG(INFO) << "Score: " << loglaplace; // if negative, not random |
| return loglaplace < 0; |
| } |
| static bool run_test(const std::string& device) { |
| unique_fd device_fd(open(device.c_str(), O_RDONLY | O_CLOEXEC)); |
| if (device_fd.get() == -1) { |
| PLOG(ERROR) << "Failed to open " << device; |
| return false; |
| } |
| int randompassed = 0; |
| auto buf = std::string(sectorsize, '\0'); |
| for (int i = 0; i < sectorcount; i++) { |
| auto l = read(device_fd.get(), &buf[0], buf.size()); |
| if (l < 1) { |
| PLOG(ERROR) << "Failed read on sector " << i; |
| return false; |
| } |
| if (((size_t)l) != buf.size()) { |
| LOG(ERROR) << "Short read on sector " << i; |
| return false; |
| } |
| auto score = randomness_score(buf); |
| if (score >= 0) { |
| randompassed++; |
| LOG(INFO) << "Passed randomness check on sector " << i << " with score " << score; |
| } else { |
| LOG(ERROR) << "Failed randomness check on sector " << i << " with score " << score; |
| } |
| } |
| LOG(INFO) << "Passed randomness check on " << randompassed << "/" << sectorcount << " sectors"; |
| return randompassed == sectorcount; |
| } |
| } // namespace vold |
| } // namespace android |
| int main(int argc, const char* const argv[]) { |
| setenv("ANDROID_LOG_TAGS", "*:v", 1); |
| android::base::InitLogging(const_cast<char**>(argv), android::base::StderrLogger); |
| if (argc != 2) { |
| LOG(ERROR) << "Usage: " << argv[0] << " <device>"; |
| LOG(ERROR) << "example: " << argv[0] << " /dev/block/bootdevice/by-name/userdata"; |
| return -1; |
| } |
| android::vold::run_test(std::string(argv[1])); |
| return 0; |
| }</pre> |
| |
| </body></html> |