blob: 95f77634edc8026e1f31832c9529539365d3e014 [file] [log] [blame]
<html devsite><head>
<title>对要发布的编译版本进行签名</title>
<meta name="project_path" value="/_project.yaml"/>
<meta name="book_path" value="/_book.yaml"/>
</head>
<body>
<!--
Copyright 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.
-->
<p>Android OS 映像在两个地方使用加密签名:</p>
<ol>
<li>映像中的所有 .apk 文件都必须经过签名。Android 软件包管理器通过下列两种方式使用 .apk 签名:<ul>
<li>更换应用时,必须使用与旧应用相同的密钥对其签名,才能存取旧应用的数据。无论是通过覆盖 .apk 来更新用户应用,还是使用安装在 <code>/data</code> 下的新版本应用来覆盖系统应用,这一点都适用。</li>
<li>如果两个或多个应用想要共享同一个用户 ID(方便共享数据等),则必须使用相同的密钥对它们进行签名。</li></ul>
</li><li>必须使用符合系统预期的密钥对 OTA 更新包进行签名,否则在安装过程中 OTA 更新包将被拒绝。</li>
</ol>
<h2 id="release-keys">发布密钥</h2>
<p><i></i>Android 树的 <code>build/target/product/security</code> 目录中提供了测试密钥。使用 <code>make</code> 编译 Android OS 映像便可使用这些测试密钥对所有 .apk 文件进行签名。由于这些测试密钥是公开的,任何人都可以使用相同的密钥对他们自己的 .apk 文件签名,这样他们就能够替换或盗用您的操作系统映像中编译的系统应用。因此,您必须使用只有您自己才能访问的特殊“发布密钥”<i></i>集对公开发布或部署的 Android OS 映像进行签名。</p>
<p>要生成您自己的唯一发布密钥集,请在 Android 树的根目录下运行以下命令:</p>
<pre class="devsite-click-to-copy">
<code class="devsite-terminal">subject='/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'</code>
<code class="devsite-terminal">mkdir ~/.android-certs</code>
<code class="devsite-terminal">for x in releasekey platform shared media; do \
./development/tools/make_key ~/.android-certs/$x "$subject"; \
done</code>
</pre>
<p>您需要对 <code>$subject</code> 进行更改以反映贵组织的信息。您可以使用任何目录,但要注意选择一个已备份且安全的位置。部分供应商会使用强密码加密私钥,并将其存储在源代码控制系统中;其他供应商则将他们的发布密钥完全存储在其他地方,如气隙阻隔的计算机上。</p>
<p>要生成发布映像,请使用以下命令:</p>
<pre class="devsite-click-to-copy">
<code class="devsite-terminal">make dist</code>
<code class="devsite-terminal">./build/tools/releasetools/sign_target_files_apks \
-o \ # explained in the next section
-d ~/.android-certs out/dist/*-target_files-*.zip \
signed-target_files.zip</code>
</pre>
<p><code>sign_target_files_apks</code> 脚本将目标文件 .zip 作为输入文件,并生成一个新的目标文件 .zip,其中所有的 .apk 都已使用新密钥签名。您可以在 <code>signed-target_files.zip</code> 中的 <code>IMAGES/</code> 目录下找到新签名的映像。</p>
<h2 id="sign-ota-packages">对 OTA 更新包进行签名</h2>
您可以按照以下步骤将已签名的目标文件 zip 转换为已签名的 OTA 更新 zip:
<pre class="devsite-click-to-copy">
<code class="devsite-terminal">./build/tools/releasetools/ota_from_target_files \
-k ~/.android-certs/releasekey \
signed-target_files.zip \
signed-ota_update.zip</code>
</pre>
<h3 id="signatures-sideloading">签名和旁加载</h3>
<p>旁加载不会绕过 Recovery 流程中的正常软件包签名验证机制。在安装一个软件包之前,Recovery 会验证该软件包是否由与 Recovery 分区中存储的公钥相匹配的私钥进行签名,这与利用无线方式传输的软件包的处理方式一样。
</p>
<p>从主系统收到的更新包通常要经过两次验证:一次是由主系统使用 Android API 中的 <code><a href="http://developer.android.com/reference/android/os/RecoverySystem.html#verifyPackage">RecoverySystem.verifyPackage()</a></code> 方法进行验证,另一次是通过 Recovery 进行验证。RecoverySystem API 对照存储在主系统 <code>/system/etc/security/otacerts.zip
</code> 文件(默认情况下)中的公钥对签名进行检查。Recovery 对照存储在 Recovery 分区 RAM 磁盘中的 <code>/res/keys</code> 文件中的公钥对签名进行检查。</p>
<p>默认情况下,由此版本生成的目标文件 .zip 会将 OTA 证书设置为与测试密钥相匹配。在发布的映像上,必须使用不同的证书,这样设备才能验证更新包的真实性。如前面一部分所示,将 <code>-o</code> 标志传递到 <code>sign_target_files_apks</code> 即可将测试密钥证书替换成您的证书目录中的发布密钥证书。</p>
<p>通常情况下,系统映像和 Recovery 映像存储的是相同的 OTA 公钥集。通过将密钥仅<i></i>添加至 Recovery 密钥集,可对只能通过旁加载安装的 apk 包(假设主系统的更新下载机制正确地对照 otacerts.zip 进行验证)签名。您可以通过在产品定义中设置 PRODUCT_EXTRA_RECOVERY_KEYS 变量来指定其他仅可纳入 Recovery 中的密钥:</p>
<pre class="devsite-click-to-copy">
vendor/yoyodyne/tardis/products/tardis.mk
</pre>
<pre class="devsite-click-to-copy">
[...]
PRODUCT_EXTRA_RECOVERY_KEYS := vendor/yoyodyne/security/tardis/sideload
</pre>
<p>其中包括 Recovery 密钥文件中的公钥 <code>vendor/yoyodyne/security/tardis/sideload.x509.pem</code>,因此它可以安装用此公钥签名的 apk 包。但 otacerts.zip 中不<i></i>包含额外的密钥,因此正确验证下载包的系统不会针对用此密钥签名的 apk 包调用 Recovery。</p>
<h2 id="certificates-keys">证书和私钥</h2>
<p>每个密钥都包含两个文件:一个是扩展名为 .x509.pem 的证书<i></i>,另一个是扩展名为 .pk8 的私钥<i></i>。私钥需要加以保密,并用于对 apk 包进行签名。密钥本身也可能受密码保护。相比之下,证书只包含公开的一半密钥,因此可以大范围地分发。证书被用于验证某个 apk 包是否由相应的私钥进行签名。</p>
<p>标准 Android 版本使用四个密钥,所有这些密钥都位于 <code>
build/target/product/security</code> 中:</p>
<dl>
<dt>testkey</dt>
<dd>适用于未另外指定密钥的 apk 包的通用默认密钥。</dd>
<dt>平台</dt>
<dd>适用于核心平台所包含的 apk 包的测试密钥。</dd>
<dt>共享</dt>
<dd>适用于家庭/联系人进程中的共享内容的测试密钥。</dd>
<dt>媒体</dt>
<dd>适用于媒体/下载系统所包含的 apk 包的测试密钥。</dd></dl>
<p>单个 apk 包通过在其 Android.mk 文件中设置 LOCAL_CERTIFICATE 来指定其中一个密钥。(如果未设置此变量,则使用 testkey。)您还可以通过路径名指定完全不同的密钥,例如:</p>
<pre class="devsite-click-to-copy">
device/yoyodyne/apps/SpecialApp/Android.mk
</pre>
<pre class="devsite-click-to-copy">
[...]
LOCAL_CERTIFICATE := device/yoyodyne/security/special
</pre>
<p>现在,此版本使用 <code>device/yoyodyne/security/special.{x509.pem,pk8}
</code> 密钥来对 SpecialApp.apk 进行签名。此版本仅可使用不受<i></i>密码保护的私钥。</p>
<h2 id="advanced-signing-options">高级签名选项</h2>
<p>运行 <code>sign_target_files_apks</code> 脚本时,必须在命令行中指定在编译版本中使用的每个密钥的替换密钥。
<code>-k <i>src_key</i>=<i>
dest_key</i></code> 标记每次指定一个替换密钥。<code>-d <i>dir</i></code> 标记可让您指定一个包含四个密钥(以替换 <code>build/target/product/security</code> 中的所有密钥)的目录;这相当于使用四次 <code>-k</code> 来指定映射关系:</p>
<pre class="devsite-click-to-copy">
build/target/product/security/testkey = dir/releasekey
build/target/product/security/platform = dir/platform
build/target/product/security/shared = dir/shared
build/target/product/security/media = dir/media
</pre>
<p>对于假定的 tardis 产品,您需要五个受密码保护的密钥:四个用于替换 <code>build/target/product/security</code> 中的四个密钥,其余一个用于替换 SpecialApp 所需的另外一个 <code>keydevice/yoyodyne/security/special</code>(请参见上面的示例)。如果密钥位于以下文件中:</p>
<pre class="devsite-click-to-copy">
vendor/yoyodyne/security/tardis/releasekey.x509.pem
vendor/yoyodyne/security/tardis/releasekey.pk8
vendor/yoyodyne/security/tardis/platform.x509.pem
vendor/yoyodyne/security/tardis/platform.pk8
vendor/yoyodyne/security/tardis/shared.x509.pem
vendor/yoyodyne/security/tardis/shared.pk8
vendor/yoyodyne/security/tardis/media.x509.pem
vendor/yoyodyne/security/tardis/media.pk8
vendor/yoyodyne/security/special.x509.pem
vendor/yoyodyne/security/special.pk8 # NOT password protected
vendor/yoyodyne/security/special-release.x509.pem
vendor/yoyodyne/security/special-release.pk8 # password protected
</pre>
<p>那么您将对所有应用签名,如下所示:</p>
<pre class="devsite-click-to-copy">
<code class="devsite-terminal">./build/tools/releasetools/sign_target_files_apks -d vendor/yoyodyne/security/tardis -k vendor/yoyodyne/special=vendor/yoyodyne/special-release -o tardis-target_files.zip signed-tardis-target_files.zip</code>
</pre>
<p>此时会显示以下内容:</p>
<pre class="devsite-click-to-copy">
Enter password for vendor/yoyodyne/security/special-release key&gt;
Enter password for vendor/yoyodyne/security/tardis/media key&gt;
Enter password for vendor/yoyodyne/security/tardis/platform key&gt;
Enter password for vendor/yoyodyne/security/tardis/releasekey key&gt;
Enter password for vendor/yoyodyne/security/tardis/shared key&gt;
signing: Phone.apk (vendor/yoyodyne/security/tardis/platform)
signing: Camera.apk (vendor/yoyodyne/security/tardis/media)
signing: Special.apk (vendor/yoyodyne/security/special-release)
signing: Email.apk (vendor/yoyodyne/security/tardis/releasekey)
[...]
signing: ContactsProvider.apk (vendor/yoyodyne/security/tardis/shared)
signing: Launcher.apk (vendor/yoyodyne/security/tardis/shared)
rewriting SYSTEM/build.prop:
replace: ro.build.description=tardis-user Eclair ERC91 15449 test-keys
with: ro.build.description=tardis-user Eclair ERC91 15449 release-keys
replace: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/test-keys
with: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/release-keys
signing: framework-res.apk (vendor/yoyodyne/security/tardis/platform)
rewriting RECOVERY/RAMDISK/default.prop:
replace: ro.build.description=tardis-user Eclair ERC91 15449 test-keys
with: ro.build.description=tardis-user Eclair ERC91 15449 release-keys
replace: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/test-keys
with: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/release-keys
using:
vendor/yoyodyne/security/tardis/releasekey.x509.pem
for OTA package verification
done.
</pre>
<p>在提示用户输入所有受密码保护的密钥的密码后,脚本会使用发布密钥对输入目标 .zip 中的所有 .apk 文件重新签名。在运行该命令之前,您还可以将 ANDROID_PW_FILE 环境变量设置为临时文件名;然后脚本会调用您的编辑器,允许您输入所有密钥的密码(采用该方式输入密码可能较为简便)。</p><p>
</p><p><code>sign_target_files_apks</code> 还会在版本属性文件中重写版本描述和指纹,表明这是一个已签名的版本。<code>-t</code> 标志可以控制对指纹所做的编辑。使用 <code>-h</code> 运行脚本来查看所有标志上的文档。</p>
<h2 id="manually-generating-keys">手动生成密钥</h2>
<p>Android 使用公开指数为 3 的 2048 位 RSA 密钥。您可以使用 <a href="https://www.openssl.org/">openssl.org</a> 提供的 openssl 工具来生成证书/私钥对:</p>
<pre class="devsite-click-to-copy">
# generate RSA key
<code class="devsite-terminal">openssl genrsa -3 -out temp.pem 2048</code>
Generating RSA private key, 2048 bit long modulus
....+++
.....................+++
e is 3 (0x3)
# create a certificate with the public part of the key
<code class="devsite-terminal">openssl req -new -x509 -key temp.pem -out releasekey.x509.pem -days 10000 -subj '/C=US/ST=California/L=San Narciso/O=Yoyodyne, Inc./OU=Yoyodyne Mobility/CN=Yoyodyne/emailAddress=yoyodyne@example.com'</code>
# create a PKCS#8-formatted version of the private key
<code class="devsite-terminal">openssl pkcs8 -in temp.pem -topk8 -outform DER -out releasekey.pk8 -nocrypt</code>
# securely delete the temp.pem file
<code class="devsite-terminal">shred --remove temp.pem</code>
</pre>
<p>上述 openssl pkcs8 命令可创建一个适用于该版本系统的 .pk8 文件,该文件未<i></i>设置密码。要创建一个带有密码保护的 .pk8 文件(您应当为所有实际的发布密钥执行此步骤),请将 <code>-nocrypt</code> 参数替换为 <code>-passout stdin</code>;这样 openssl 将使用从标准输入中读取的密码来加密私钥。该过程中不会输出任何提示。因此,当系统确实只是在等待您输入密码时,如果 stdin 是终端,程序将会处于挂起状态。可以对 Passout 参数使用其他值,以便从其他位置读取密码;有关详情,请参阅 <a href="http://www.openssl.org/docs/man1.0.1/apps/openssl.html#PASS-PHRASE-ARGUMENTS">openssl 文档</a></p>
<p>temp.pem 中间文件包含不受任何类型的密码保护的私钥,因此在生成发布密钥时应谨慎处理该文件。需要特别指出的是,GNUshred 实用程序可能对网络或日志文件系统无效。在生成密钥时,您可以使用位于 RAM 磁盘(如 tmpfs 分区)中的工作目录以确保中间文件不会无意间被暴露。</p>
<h2 id="creating-image-files">创建映像文件</h2>
<p>
一旦您签署了目标文件 .zip,您便需要创建映像,以便将其存放到设备上。要从目标文件中创建已签名的映像,请在 Android 树形结构的根目录下运行以下命令:</p>
<pre class="devsite-terminal devsite-click-to-copy">
./build/tools/releasetools/img_from_target_files signed-target-files.zip signed-img.zip
</pre>
生成的文件 <code>signed-img.zip</code> 中包含所有 .img 文件。
要将映像加载到设备上,请使用 fastboot,如下所示:
<pre class="devsite-terminal devsite-click-to-copy">
fastboot update signed-img.zip
</pre>
</body></html>