修复 ITSAppUsesNonExemptEncryption 出口合规缺失

TestFlight 每个新版本都让你填出口合规,是因为 Info.plist 没声明 ITSAppUsesNonExemptEncryption。设对值之后就不再弹了。

每次往 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)、KeychainCryptoKit 的标准算法 —— 这些在美国 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-UniversalWireGuardKitlibsodiumBoringSSL-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、KeychainCryptoKit 标准算法<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 InstallStrip PNG Text 以及任何处理 Info.plist 的自定义 build phase。

步骤 6:如适用,向 BIS 提交年度自分类

真正的非豁免加密(类别 C),每年 2 月 1 日前把上一自然年产品的年度自分类报告同时邮件发到 crypt-supp8@bis.doc.govenc@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.plistgrep 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 套件)引入的 OpenSSLlibsodium 会把你从豁免变成可能非豁免。
  • 为了”过掉黄条”标个 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