aboutsummaryrefslogtreecommitdiff
path: root/zh-cn/security/apksigning
diff options
context:
space:
mode:
Diffstat (limited to 'zh-cn/security/apksigning')
-rw-r--r--zh-cn/security/apksigning/index.html60
-rw-r--r--zh-cn/security/apksigning/v2.html223
2 files changed, 283 insertions, 0 deletions
diff --git a/zh-cn/security/apksigning/index.html b/zh-cn/security/apksigning/index.html
new file mode 100644
index 00000000..c0af7d0f
--- /dev/null
+++ b/zh-cn/security/apksigning/index.html
@@ -0,0 +1,60 @@
+<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 平台上运行的每个应用都必须要有<a href="https://developer.android.com/studio/publish/app-signing.html">开发者的签名</a>。Google Play 或 Android 设备上的软件包安装程序会拒绝没有获得签名就尝试安装的应用。
+</p>
+<p>在 Google Play 上,应用签名可以将 Google 对开发者的信任和开发者对自己的应用的信任联系在一起。这样一来,开发者就知道自己的应用是以未经修改的形式提供给 Android 设备,并且可以对其应用的行为负责。
+</p>
+<p>在 Android 上,应用签名是将应用放入其应用沙盒的第一步。已签名的应用证书定义了哪个用户 ID 与哪个应用相关联;不同的应用要以不同的用户 ID 运行。应用签名可确保一个应用无法访问任何其他应用的数据,通过明确定义的 IPC 进行访问时除外。</p>
+<p>当应用(APK 文件)安装到 Android 设备上时,软件包管理器会验证 APK 是否已经过适当签名(已使用 APK 中包含的证书签名)。如果该证书(或更准确地说,证书中的公钥)与设备上的任何其他 APK 使用的签名密钥一致,那么这个新 APK 就可以选择在清单中指定它将与其他以类似方式签名的 APK 共用一个 UID。
+</p>
+<p>应用可以由第三方(OEM、运营商、其他应用市场)签名,也可以自行签名。Android 提供了使用自签名证书进行代码签名的功能,而开发者无需外部协助或许可即可生成自签名证书。应用并非必须由核心机构签名。Android 目前不对应用证书进行 CA 认证。
+</p>
+<p>应用还可以在“签名”保护级别声明安全权限,以便只有使用同一个密钥签名的应用可以获得此仅限,同时让这些应用可以各自维持单独的 UID 和应用沙盒。通过<a href="https://developer.android.com/guide/topics/manifest/manifest-element.html#uid">共用 UID 功能</a>,多个应用可以共用一个应用沙盒,从而建立起更紧密的联系。在该功能中,使用同一个开发者密钥签名的两个或更多应用可以在其清单中声明共用的 UID。</p>
+<h2>APK 签名方案</h2>
+<p>Android 支持两种应用签名方案,一种是基于 JAR 签名的方案(v1 方案),另一种是 Android Nougat (Android 7.0) 中引入的 <a href="v2.html">APK 签名方案 v2(v2 方案)</a>。
+</p>
+<p>为了最大限度地提高兼容性,应同时采用 v1 和 v2 这两种方案对应用进行签名。与只通过 v1 方案签名的应用相比,通过 v2 方案签名的应用能够更快速地安装到 Android Nougat 及更高版本的设备上。更低版本的 Android 平台会忽略 v2 签名,这就需要应用包含 v1 签名。
+</p>
+<h3 id="v1">JAR 签名(v1 方案)</h3>
+<p>从一开始,APK 签名就是 Android 的一个有机部分。该方案基于<a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File">签名的 JAR</a>。如要详细了解如何使用该方案,请参阅介绍如何<a href="https://developer.android.com/studio/publish/app-signing.html">为您的应用签名</a>的 Android Studio 文档。
+</p>
+<p>v1 签名不保护 APK 的某些部分,例如 ZIP 元数据。APK 验证程序需要处理大量不可信(尚未经过验证)的数据结构,然后会舍弃不受签名保护的数据。这会导致相当大的受攻击面。此外,APK 验证程序必须解压所有已压缩的条目,而这需要花费更多时间和内存。为了解决这些问题,Android 7.0 中引入了 APK 签名方案 v2。
+</p>
+<h3 id="v2">APK 签名方案 v2(v2 方案)</h3>
+<p>Android 7.0 中引入了 APK 签名方案 v2(v2 方案)。该方案会对 APK 的内容进行哈希处理和签名,然后将生成的“APK 签名分块”插入到 APK 中。如要详细了解如何在应用中使用 v2 方案,请参阅 Android N 开发者预览版中的 <a href="https://developer.android.com/about/versions/nougat/android-7.0.html#apk_signature_v2">APK 签名方案 v2</a>。
+</p>
+<p>在验证期间,v2 方案会将 APK 文件视为 Blob,并对整个文件进行签名检查。对 APK 进行的任何修改(包括对 ZIP 元数据进行的修改)都会使 APK 签名作废。这种形式的 APK 验证不仅速度要快得多,而且能够发现更多种未经授权的修改。
+</p>
+<p>新的签名格式向后兼容,因此,使用这种新格式签名的 APK 可在更低版本的 Android 设备上进行安装(会直接忽略添加到 APK 的额外数据),但前提是这些 APK 还带有 v1 签名。
+</p>
+<p>
+ <img src="../images/apk-validation-process.png" alt="APK 签名验证过程" id="figure1"/>
+</p>
+<p class="img-caption"><strong>图 1.</strong> APK 签名验证过程(新步骤以红色显示)</p>
+
+<p>验证程序会对照存储在“APK 签名分块”中的 v2 签名对 APK 的全文件哈希进行验证。该哈希涵盖除“APK 签名分块”(其中包含 v2 签名)之外的所有内容。在“APK 签名分块”以外对 APK 进行的任何修改都会使 APK 的 v2 签名作废。v2 签名被删除的 APK 也会被拒绝,因为 v1 签名指明相应 APK 带有 v2 签名,所以 Android Nougat 及更高版本会拒绝使用 v1 签名验证 APK。
+</p>
+
+<p>如需关于 APK 签名验证过程的详细信息,请参阅 APK 签名方案 v2 的<a href="v2.html#verification">“验证”部分</a>。</p>
+
+</body></html> \ No newline at end of file
diff --git a/zh-cn/security/apksigning/v2.html b/zh-cn/security/apksigning/v2.html
new file mode 100644
index 00000000..ac12e74a
--- /dev/null
+++ b/zh-cn/security/apksigning/v2.html
@@ -0,0 +1,223 @@
+<html devsite><head>
+ <title>APK 签名方案 v2</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>APK 签名方案 v2 是一种全文件签名方案,该方案能够发现对 APK 的受保护部分进行的所有更改,从而有助于加快验证速度并<a href="#integrity-protected-contents">增强完整性保证</a>。</p>
+
+<p>使用 APK 签名方案 v2 进行签名时,会在 APK 文件中插入一个 <a href="#apk-signing-block">APK 签名分块</a>,该分块位于“ZIP 中央目录”部分之前并紧邻该部分。在“APK 签名分块”内,v2 签名和签名者身份信息会存储在 <a href="#apk-signature-scheme-v2-block">APK 签名方案 v2 分块</a>中。
+</p>
+
+<p>
+ <img src="../images/apk-before-after-signing.png" alt="签名前和签名后的 APK" id="figure1"/>
+</p>
+<p class="img-caption"><strong>图 1.</strong> 签名前和签名后的 APK</p>
+
+<p>APK 签名方案 v2 是在 Android 7.0 (Nougat) 中引入的。为了使 APK 可在 Android 6.0 (Marshmallow) 及更低版本的设备上安装,应先使用 <a href="index.html#v1">JAR 签名</a>功能对 APK 进行签名,然后再使用 v2 方案对其进行签名。
+</p>
+
+<h2 id="apk-signing-block">APK 签名分块</h2>
+<p>为了保持与当前 APK 格式向后兼容,v2 及更高版本的 APK 签名会存储在“APK 签名分块”内,该分块是为了支持 APK 签名方案 v2 而引入的一个新容器。在 APK 文件中,“APK 签名分块”位于“ZIP 中央目录”(位于文件末尾)之前并紧邻该部分。
+</p>
+
+<p>该分块包含多个“ID-值”对,所采用的封装方式有助于更轻松地在 APK 中找到该分块。APK 的 v2 签名会存储为一个“ID-值”对,其中 ID 为 0x7109871a。
+</p>
+
+<h3 id="apk-signing-block-format">格式</h3>
+<p>“APK 签名分块”的格式如下(所有数字字段均采用小端字节序):</p>
+
+<ul>
+ <li><code>size of block</code>,以字节数(不含此字段)计 (uint64)</li>
+ <li>带 uint64 长度前缀的“ID-值”对序列:<ul>
+ <li><code>ID</code> (uint32)</li>
+ <li><code>value</code>(可变长度:“ID-值”对的长度 - 4 个字节)</li>
+ </ul>
+ </li>
+ <li><code>size of block</code>,以字节数计 - 与第一个字段相同 (uint64)</li>
+ <li><code>magic</code> APK 签名分块 42(16 个字节)</li>
+</ul>
+
+<p>在解析 APK 时,首先要通过以下方法找到“ZIP 中央目录”的起始位置:在文件末尾找到“ZIP 中央目录结尾”记录,然后从该记录中读取“中央目录”的起始偏移量。通过 <code>magic</code> 值,可以快速确定“中央目录”前方可能是“APK 签名分块”。然后,通过 <code>size of
+block</code> 值,可以高效地找到该分块在文件中的起始位置。
+</p>
+
+<p>在解译该分块时,应忽略 ID 未知的“ID-值”对。
+</p>
+
+<h2 id="apk-signature-scheme-v2-block">APK 签名方案 v2 分块</h2>
+<p>APK 由一个或多个签名者/身份签名,每个签名者/身份均由一个签名密钥来表示。该信息会以“APK 签名方案 v2 分块”的形式存储。对于每个签名者,都会存储以下信息:</p>
+
+<ul>
+ <li>(签名算法、摘要、签名)元组。摘要会存储起来,以便将签名验证和 APK 内容完整性检查拆开进行。</li>
+ <li>表示签名者身份的 X.509 证书链。</li>
+ <li>采用键值对形式的其他属性。</li>
+</ul>
+
+<p>对于每位签名者,都会使用收到的列表中支持的签名来验证 APK。签名算法未知的签名会被忽略。如果遇到多个支持的签名,则由每个实现来选择使用哪个签名。这样一来,以后便能够以向后兼容的方式引入安全系数更高的签名方法。建议的方法是验证安全系数最高的签名。
+</p>
+
+<h3 id="apk-signature-scheme-v2-block-format">格式</h3>
+<p>“APK 签名方案 v2 分块”存储在“APK 签名分块”内,ID 为 <code>0x7109871a</code>。
+</p>
+
+<p>“APK 签名方案 v2 分块”的格式如下(所有数字值均采用小端字节序,所有带长度前缀的字段均使用 uint32 值表示长度):</p>
+<ul>
+ <li>带长度前缀的 <code>signer</code>(带长度前缀)序列:<ul>
+ <li>带长度前缀的 <code>signed data</code>:<ul>
+ <li>带长度前缀的 <code>digests</code>(带长度前缀)序列:<ul>
+ <li><code>signature algorithm ID</code> (uint32)</li>
+ <li>(带长度前缀)<code>digest</code> - 请参阅<a href="#integrity-protected-contents">受完整性保护的内容</a></li>
+ </ul>
+ </li>
+ <li>带长度前缀的 X.509 <code>certificates</code> 序列:<ul>
+ <li>带长度前缀的 X.509 <code>certificate</code>(ASN.1 DER 形式)</li>
+ </ul>
+ </li>
+ <li>带长度前缀的 <code>additional attributes</code>(带长度前缀)序列:<ul>
+ <li><code>ID</code> (uint32)</li>
+ <li><code>value</code>(可变长度:附加属性的长度 - 4 个字节)</li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ <li>带长度前缀的 <code>signatures</code>(带长度前缀)序列:<ul>
+ <li><code>signature algorithm ID</code> (uint32)</li>
+ <li><code>signed data</code> 上带长度前缀的 <code>signature</code></li>
+ </ul>
+ </li>
+ <li>带长度前缀的 <code>public key</code>(SubjectPublicKeyInfo,ASN.1 DER 形式)</li>
+ </ul>
+ </li>
+</ul>
+
+<h4 id="signature-algorithm-ids">签名算法 ID</h4>
+<ul>
+ <li>0x0101 - 采用 SHA2-256 摘要、SHA2-256 MGF1、32 个字节的盐且尾部为 0xbc 的 RSASSA-PSS 算法</li>
+ <li>0x0102 - 采用 SHA2-512 摘要、SHA2-512 MGF1、64 个字节的盐且尾部为 0xbc 的 RSASSA-PSS 算法</li>
+ <li>0x0103 - 采用 SHA2-256 摘要的 RSASSA-PKCS1-v1_5 算法。此算法适用于需要确定性签名的编译系统。</li>
+ <li>0x0104 - 采用 SHA2-512 摘要的 RSASSA-PKCS1-v1_5 算法。此算法适用于需要确定性签名的编译系统。</li>
+ <li>0x0201 - 采用 SHA2-256 摘要的 ECDSA 算法</li>
+ <li>0x0202 - 采用 SHA2-512 摘要的 ECDSA 算法</li>
+ <li>0x0301 - 采用 SHA2-256 摘要的 DSA 算法</li>
+</ul>
+
+<p>Android 平台支持上述所有签名算法。签名工具可能只支持其中一部分算法。
+</p>
+
+<p>
+<strong>支持的密钥大小和 EC 曲线:</strong>
+</p>
+
+<ul>
+ <li>RSA:1024、2048、4096、8192、16384</li>
+ <li>EC:NIST P-256、P-384、P-521</li>
+ <li>DSA:1024、2048、3072</li>
+</ul>
+
+<h2 id="integrity-protected-contents">受完整性保护的内容</h2>
+
+<p>为了保护 APK 内容,APK 包含以下 4 个部分:</p>
+
+<ol>
+ <li>ZIP 条目的内容(从偏移量 0 处开始一直到“APK 签名分块”的起始位置)</li>
+ <li>APK 签名分块</li>
+ <li>ZIP 中央目录</li>
+ <li>ZIP 中央目录结尾</li>
+</ol>
+
+<p>
+ <img src="../images/apk-sections.png" alt="签名后的各个 APK 部分" id="figure2"/>
+</p>
+<p class="img-caption"><strong>图 2.</strong> 签名后的各个 APK 部分</p>
+
+<p>APK 签名方案 v2 负责保护第 1、3、4 部分的完整性,以及第 2 部分包含的“APK 签名方案 v2 分块”中的 <code>signed data</code> 分块的完整性。
+</p>
+
+<p>第 1、3 和 4 部分的完整性通过其内容的一个或多个摘要来保护,这些摘要存储在 <code>signed data</code> 分块中,而这些分块则通过一个或多个签名来保护。
+</p>
+
+<p>第 1、3 和 4 部分的摘要采用以下计算方式,类似于两级 <a href="https://en.wikipedia.org/wiki/Merkle_tree">Merkle 树</a>。每个部分都会被拆分成多个大小为 1 MB(2<sup>20</sup> 个字节)的连续块。每个部分的最后一个块可能会短一些。每个块的摘要均通过字节 <code>0xa5</code> 的连接、块的长度(采用小端字节序的 uint32 值,以字节数计)和块的内容进行计算。顶级摘要通过字节 <code>0x5a</code> 的连接、块数(采用小端字节序的 uint32 值)以及块的摘要的连接(按照块在 APK 中显示的顺序)进行计算。摘要以分块方式计算,以便通过并行处理来加快计算速度。
+</p>
+
+<p>
+ <img src="../images/apk-integrity-protection.png" alt="APK 摘要" id="figure3"/>
+</p>
+<p class="img-caption"><strong>图 3.</strong> APK 摘要</p>
+
+<p>由于第 4 部分(ZIP 中央目录结尾)包含“ZIP 中央目录”的偏移量,因此该部分的保护比较复杂。当“APK 签名分块”的大小发生变化(例如,添加了新签名)时,偏移量也会随之改变。因此,在通过“ZIP 中央目录结尾”计算摘要时,必须将包含“ZIP 中央目录”偏移量的字段视为包含“APK 签名分块”的偏移量。
+</p>
+
+<h2 id="rollback-protections">防回滚保护</h2>
+<p>攻击者可能会试图在支持对带 v2 签名的 APK 进行验证的 Android 平台上将带 v2 签名的 APK 作为带 v1 签名的 APK 进行验证。为了防范此类攻击,带 v2 签名的 APK 如果还带 v1 签名,其 META-INF/*.SF 文件的主要部分中必须包含 X-Android-APK-Signed 属性。该属性的值是一组以英文逗号分隔的 APK 签名方案 ID(v2 方案的 ID 为 2)。在验证 v1 签名时,对于此组中验证程序首选的 APK 签名方案(例如,v2 方案),如果 APK 没有相应的签名,APK 验证程序必须要拒绝这些 APK。此项保护依赖于内容 META-INF/*.SF 文件受 v1 签名保护这一事实。请参阅 <a href="#v1-verification">JAR 已签名的 APK 的验证</a>部分。
+</p>
+
+<p>攻击者可能会试图从“APK 签名方案 v2 分块”中删除安全系数较高的签名。为了防范此类攻击,对 APK 进行签名时使用的签名算法 ID 的列表会存储在通过各个签名保护的 <code>signed data</code> 分块中。
+</p>
+
+<h2 id="verification">验证</h2>
+
+<p>在 Android 7.0 中,可以根据 APK 签名方案 v2(v2 方案)或 JAR 签名(v1 方案)验证 APK。更低版本的平台会忽略 v2 签名,仅验证 v1 签名。
+</p>
+
+<p>
+ <img src="../images/apk-validation-process.png" alt="APK 签名验证过程" id="figure4"/>
+</p>
+<p class="img-caption"><strong>图 4.</strong> APK 签名验证过程(新步骤以红色显示)</p>
+
+<h3 id="v2-verification">APK 签名方案 v2 验证</h3>
+<ol>
+ <li>找到“APK 签名分块”并验证以下内容:<ol>
+ <li>“APK 签名分块”的两个大小字段包含相同的值。</li>
+ <li>“ZIP 中央目录”紧跟在“ZIP 中央目录结尾”记录后面。</li>
+ <li>“ZIP 中央目录结尾”之后没有任何数据。</li>
+ </ol>
+ </li>
+ <li>找到“APK 签名分块”中的第一个“APK 签名方案 v2 分块”。如果 v2 分块存在,则继续执行第 3 步。否则,回退至<a href="#v1-verification">使用 v1 方案</a>验证 APK。</li>
+ <li>对“APK 签名方案 v2 分块”中的每个 <code>signer</code> 执行以下操作:<ol>
+ <li>从 <code>signatures</code> 中选择安全系数最高的受支持 <code>signature algorithm ID</code>。安全系数排序取决于各个实现/平台版本。</li>
+ <li>使用 <code>public
+ key</code> 并对照 <code>signed data</code> 验证 <code>signatures</code> 中对应的 <code>signature</code>。(现在可以安全地解析 <code>signed data</code> 了。)</li>
+ <li>验证 <code>digests</code> 和 <code>signatures</code> 中的签名算法 ID 列表(有序列表)是否相同。(这是为了防止删除/添加签名。)</li>
+ <li>使用签名算法所用的同一种摘要算法<a href="#integrity-protected-contents">计算 APK 内容的摘要</a>。</li>
+ <li>验证计算出的摘要是否与 <code>digests</code> 中对应的 <code>digest</code> 相同。</li>
+ <li>验证 <code>certificates</code> 中第一个 <code>certificate</code> 的 SubjectPublicKeyInfo 是否与 <code>public key</code> 相同。</li>
+ </ol>
+ </li>
+ <li>如果找到了至少一个 <code>signer</code>,并且对于每个找到的 <code>signer</code>,第 3 步都取得了成功,APK 验证将会成功。</li>
+</ol>
+
+<p class="note"><strong>注意</strong>:如果第 3 步或第 4 步失败了,则不得使用 v1 方案验证 APK。
+</p>
+
+<h3 id="v1-verification">JAR 已签名的 APK 的验证(v1 方案)</h3>
+<p>JAR 已签名的 APK 是一种<a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File">标准的已签名 JAR</a>,其中包含的条目必须与 META-INF/MANIFEST.MF 中列出的条目完全相同,并且所有条目都必须已由同一组签名者签名。其完整性按照以下方式进行验证:</p>
+
+<ol>
+ <li>每个签名者均由一个包含 META-INF/&lt;signer&gt;.SF 和 META-INF/&lt;signer&gt;.(RSA|DSA|EC) 的 JAR 条目来表示。</li>
+ <li>&lt;signer&gt;.(RSA|DSA|EC) 是<a href="https://tools.ietf.org/html/rfc5652">具有 SignedData 结构的 PKCS #7 CMS ContentInfo</a>,其签名通过 &lt;signer&gt;.SF 文件进行验证。</li>
+ <li>&lt;signer&gt;.SF 文件包含 META-INF/MANIFEST.MF 的全文件摘要和 META-INF/MANIFEST.MF 各个部分的摘要。需要验证 MANIFEST.MF 的全文件摘要。如果该验证失败,则改为验证 MANIFEST.MF 各个部分的摘要。</li>
+ <li>对于每个受完整性保护的 JAR 条目,META-INF/MANIFEST.MF 都包含一个具有相应名称的部分,其中包含相应条目未压缩内容的摘要。所有这些摘要都需要验证。</li>
+ <li>如果 APK 包含未在 MANIFEST.MF 中列出且不属于 JAR 签名一部分的 JAR 条目,APK 验证将会失败。</li>
+</ol>
+
+<p>因此,保护链是每个受完整性保护的 JAR 条目的 &lt;signer&gt;.(RSA|DSA|EC) -&gt; &lt;signer&gt;.SF -&gt; MANIFEST.MF -&gt; 内容。
+</p>
+
+</body></html> \ No newline at end of file