每次往 TestFlight 上传完都会看到一条黄条:“Missing Compliance”,提示 “Provide export compliance information.”。在你回答完那个问卷之前,build 会一直带着这个警告,外部测试者无法安装。原因就是你的 Info.plist 没有 ITSAppUsesNonExemptEncryption 这一项,App Store Connect 无法替你预先回答美国出口管理局的问题,于是每次上传都重新问一遍。
最快的修法(95% 的 App 适用):在 App target 的 Info.plist 里把 ITSAppUsesNonExemptEncryption 设为 <false/>,bump 一下 build 号,归档,重新上传,黄条就再也不会回来。只有当你的二进制里真的打包了非标准加密(自带 VPN、libsodium、OpenSSL、自写 AES、DRM)时才需要下面更麻烦的路径 —— 那种情况下值要写 <true/> 加一个豁免码,可能还要做美国年度自分类 (ERN) 和法国声明。截至 2026 年 6 月,Info.plist key、App Store Connect 流程、以及 BIS 2 月 1 日的截止日期都仍然适用。
常见原因
按触发频率排序。
1. ITSAppUsesNonExemptEncryption 根本不在 Info.plist 里
key 不存在时 App Store Connect 视为你还没回答,所以每次上传都弹问卷。老一些的 Xcode 模板创建的新工程不会自带这一项;新版 SwiftUI App 模板也不会加 —— 得你自己加。
如何识别:grep ITSAppUsesNonExemptEncryption Info.plist 没输出。处理完成后 TestFlight build 上立即出现黄色的 Missing Compliance 警告。
2. key 设成 true,但你只用了 HTTPS / 标准 iOS 加密
绝大多数 App 只用 URLSession(HTTPS)、Keychain、CryptoKit 的标准算法 —— 这些在美国 BIS 740.17(b)(1) 和苹果的”仅使用豁免加密”路径下都算豁免。明明没自带加密代码却把 key 写成 true(非豁免)是最差的回答:会强制你上传一份其实并不需要的 ERN 和首年 BIS 通知。
如何识别:你的代码从不 import OpenSSL、libsodium、BoringSSL、自写的 AES 表或 DRM 模块。所有加密逻辑都来自苹果的 framework 或 HTTPS。这种情况下你是豁免的 —— 但 key 现在却写着 true。
3. key 设成 false,但 App 实际上有非豁免加密
反过来的微妙情况:你打包了一个第三方 VPN SDK、一个自研文件加密库或一个 DRM 模块。这种情况下标 false 属于虚假陈述,审计时会出问题。
如何识别:Podfile.lock 中能搜到 OpenSSL-Universal、WireGuardKit、libsodium、BoringSSL-GRPC,或者你自己手写了 AES-GCM。诚实答案是 true 并走豁免声明流程。
4. key 设在了错的 target 里
包含 App + Widget + Watch + Share Extension 的 workspace,每个 target 都有自己的 Info.plist。只在 App target 设了、Extension target 没设,按 App Store Connect 当前版本逻辑还是可能在主 App 上弹警告。
如何识别:先 find . -name Info.plist,再对每个匹配 grep -l ITSAppUsesNonExemptEncryption,得到的条目比你的 target 数少。
5. 修完之后 CFBundleShortVersionString 反而降低了
你加了 key,但新 build 的 CFBundleShortVersionString 比 App Store Connect 上已有的某个 build 还旧。App Store Connect 按版本号保留合规状态,老版本号会重新触发问卷。
如何识别:明明最新 build 归档里 Info.plist 已经有 key,合规黄条还是反复出现。Build 号是新的,但版本字符串没变或更老。
6. 用了非豁免加密但没提交年度自分类
如果你的 App 用了非豁免加密,又不属于苹果列出的有限用途(认证、版权保护等),美国要求你在每年 2 月 1 日前向 BIS 提交一份年度自分类报告 (ERN)。不提交并不会卡你的 build —— 苹果不会执法 —— 但严格来说你违反了美国出口法。
如何识别:你把 ITSAppUsesNonExemptEncryption 标了 true、没交 ERN、App 不属于任何豁免类别,而且日历已经过了 2 月 1 日。
开始之前
- 从归档好的
.ipa中提取真正打包进去的Info.plist,不要只看 source 里的版本 —— build settings 有时会覆盖或删掉某些 key。 - 列出
Podfile.lock/Package.resolved里所有跟密码学相关的依赖。 - 列出最终交付包里包含哪些 target(App、Widgets、Watch、Intents、Share、NotificationService)。
需要收集的信息
grep -RE "ITSAppUsesNonExempt|ITSEncryption" .从项目根目录跑出的结果。- 加密库清单(
grep -RE "OpenSSL|BoringSSL|libsodium|WireGuard|crypto" Podfile.lock Package.resolved 2>/dev/null)。 - 你的 App 实际怎么用密码学(只用 HTTPS?CryptoKit?自写 AES?VPN?)。
- App Store Connect → TestFlight → 对应 build 上合规警告的原文。
- 上一个 build 的版本字符串以及当时是否已带 key。
修复步骤
绝大多数 App 走完 Step 1 + Step 2 就够了。
步骤 1:盘点实际的加密使用
三种情况。先确定自己属于哪一类,再去对应的步骤。
| 类别 | 你的二进制用了什么 | ITSAppUsesNonExemptEncryption | 额外文件 |
|---|---|---|---|
| A(多数 App) | 只用 iOS 自带加密:URLSession 走 HTTPS、Keychain、CryptoKit 标准算法 | <false/> | 无 |
| B | 非标准 / 自研加密,但仍符合美国豁免条件(仅用于认证、版权保护,或用途有限) | <true/> + ITSEncryptionExportComplianceCode | 在法国上架则需法国声明 |
| C | 真正的非豁免加密(自写 AES、自带 VPN、把端到端加密当核心功能) | <true/> + ITSEncryptionExportComplianceCode | 向 BIS 提交美国年度自分类 (ERN);在法国上架还需法国声明 |
两点多数指南都搞错了,要特别说明:
- 美国的豁免码(
ITSEncryptionExportComplianceCode)和法国加密声明是两份不同的文件。豁免码是你答完问卷后苹果发给你的;法国表格只在你于法国 App Store 上架时才需要 —— 而且按苹果的加密文件参考,只要用到任何不是苹果系统提供的行业标准算法就需要,不只是自研加密才要。 - 完全来自苹果操作系统的加密,在 App Store Connect 里根本不需要上传任何文件 —— 这正是
<false/>这条路。
步骤 2:把 key 加到每个 target 的 Info.plist
App target:
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
如果你用的是新版 Xcode 把 Info.plist 写在 build settings 里的方式,到 target → Build Settings → Info.plist Values 加:
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO
每个 Extension target 都重复一次。当主 App 已声明时,extension 不需要单独发布加密声明,但每个 Info.plist 都带上 key 能避免边界情况。
步骤 3:如果必须回答 true,加上豁免码
在 App Store Connect 里打开 Apps → 你的 App → TestFlight,在侧栏选好平台,点开带警告的那个 build。点 build 旁边的 Manage,再点 Provide Export Compliance Information,回答问题。如果需要上传文件,App Store Connect 会显示 Go to App Encryption Page 让你上传(或用 Choose File 附上之前已通过的文件)。通过之后会拿到一个 ITSEncryptionExportComplianceCode。把它写进 Info.plist,后续 build 就会跳过问卷:
<key>ITSAppUsesNonExemptEncryption</key>
<true/>
<key>ITSEncryptionExportComplianceCode</key>
<string>YOUR-CODE-FROM-APP-STORE-CONNECT</string>
这个码是按 App(不是按 build)发放的,在加密使用没实质变化前可以一直沿用。
步骤 4:重新构建、归档、上传
Info.plist 改动必须重新归档 —— App Store Connect 里不能编辑。先 bump build 号:
agvtool next-version -all
在 Xcode 里归档(Product → Archive)、校验、上传。处理完之后那条黄色合规栏就不应该再出现了。
步骤 5:确认 key 真的进了二进制
从 App Store Connect → TestFlight → Build → 下载 dSYM 处下载处理后的 .ipa(或从你的归档里提取),然后:
unzip -p YourApp.ipa "Payload/YourApp.app/Info.plist" \
| plutil -convert xml1 -o - - \
| grep -A1 ITSAppUsesNonExemptEncryption
应能看到你设置的值。如果 key 不见了,是 build settings 给剥掉了 —— 检查 Skip Install、Strip PNG Text 以及任何处理 Info.plist 的自定义 build phase。
步骤 6:如适用,向 BIS 提交年度自分类
真正的非豁免加密(类别 C),每年 2 月 1 日前把上一自然年产品的年度自分类报告同时邮件发到 crypt-supp8@bis.doc.gov 和 enc@nsa.gov。按 BIS 年度自分类页面,报告必须是 CSV 文件(只接受 CSV),首行是表头,正好这 12 个字段,任何字段都不能留空:
PRODUCT NAME, MODEL NUMBER, MANUFACTURER, ECCN, AUTHORIZATION TYPE, ITEM TYPE, SUBMITTER NAME, TELEPHONE NUMBER, E-MAIL ADDRESS, MAILING ADDRESS, NON-U.S. COMPONENTS, NON-U.S. MANUFACTURING LOCATIONS
对一款典型的大众市场手机 App,ECCN 一般是 5D002,授权类型是 ENC 大众市场例外,但分类的责任在你自己。免费。还有两个省事的点:这一年里如果没有任何相关产品的出口,就根本不用交报告;如果跟上一份报告相比什么都没变,可以直接发邮件说明无变化(或把上一份报告再发一遍),不用重做。
如何确认已修复
- 处理完成后,新 TestFlight build 不再出现黄色的 Missing Compliance 警告。
- 处理完成后外部测试者立即能安装 build,你无需做任何手动操作。
- 在提取出的二进制(步骤 5)的
Info.plist里grep ITSAppUsesNonExemptEncryption能看到你设的值,而不只是源码里的那份。 - App Store Connect → TestFlight → 对应 build 里,合规状态显示为已提供,而不是再让你去 Manage。
长期预防
- 把这个 key 加进你的 Xcode 项目模板,新建工程时默认就在。
- 把值锁定在版本管理里 —— 不要直接在 Xcode 里改而不 commit。
- 引入新 SDK 时,先问”它是否自带加密?“再 merge;加进项目级的依赖审查清单。
- 如果 App 从豁免升级到非豁免(比如加了 VPN 功能),更新 Info.plist key、重新拿合规码、提交年度报告。Build 处理依旧慢的话,参考TestFlight Build 卡在处理中。
- 在
README.md旁边维护一份一页的”加密清单”文档,记下二进制用了哪些加密以及法律分类 —— 这是 M&A 或安全审计时最有用的单一文档。
常见坑
- Xcode 还开着项目时,在 Finder 里手动改 Info.plist —— Xcode 下次 build 会重写,把你的改动覆盖掉。
- 把 key 设成字符串
"YES"/"NO"而不是布尔<true/>/<false/>。字符串形式 App Store Connect 会忽略。 - 以为 CryptoKit 算非标准加密。CryptoKit 用的是苹果提供的算法 —— 稳稳算”豁免”。
- 忽略了通过传递依赖(gRPC、Firebase Auth 套件)引入的
OpenSSL或libsodium会把你从豁免变成可能非豁免。 - 为了”过掉黄条”标个 true 就不交年度报告 —— 苹果不执法,但 BIS 会,罚款不轻。
- 把别的 App 或别的 team 的合规码复用过来 —— 这个码是按 App、按 team 发的。
FAQ
Q:我的 App 只用 HTTPS,该怎么填?
把 ITSAppUsesNonExemptEncryption 设为 <false/>。HTTPS / URLSession / Keychain / CryptoKit 都是操作系统提供的加密,按苹果的 ITSAppUsesNonExemptEncryption 文档属于豁免,不需要上传任何文件。
Q:我需要法国加密声明吗?
只有在法国 App Store 上架才需要。它和美国豁免码是两回事。按苹果说法,只要用到不是苹果系统提供的行业标准算法就需要它(自研算法则需美国 CCATS 加法国声明)。如果你属于类别 A —— 只用系统自带加密 —— 这些都不需要。
Q:我每次发版都点一遍 App Store Connect 的问卷不行吗?
可以,但每次都会让外部测试者邀请暂停。把 Info.plist key 加好之后,该版本字符串永久跳过问卷。
Q:我打包了一个聊天 SDK,它内部封装了 libsodium。这算非豁免吗?
可能算。如果 libsodium 是用来给用户消息做端到端加密、并且这是 App 的核心功能,那是非豁免。如果 SDK 只在内部用 libsodium 做签名或密钥派生,是 API 的附带行为,通常仍可走豁免 —— 但最稳妥的做法是答 true + 通过问卷拿豁免码。
Q:我之前那版已经以 false 通过审核了,现在改成 true 会留下”案底”吗?
App Store Connect 按版本字符串记录合规状态。提交一个新版本号、新 key 的 build 就行,老 build 不用删。
Q:年度自分类要花钱吗?
不要。BIS 备案免费;只要每年 2 月 1 日前递交上一年度的产品清单即可。漏交是监管层面的处罚,跟商业无关 —— 苹果不会下架你的 App。
相关阅读
标签: #排查 #App Store #TestFlight #encryption #export-compliance