# Apple 推送证书：Expo 该用 .p8 还是 .p12

> Apple 推送有两种凭证：永不过期、通用于所有 app 的 .p8 密钥，或每年到期的 .p12 证书。用 Expo 的话，EAS 帮你管理密钥。

原始页面：[Apple 推送证书：Expo 该用 .p8 还是 .p12](https://devoria.fr/zh-hans/blog/apple-push-notification-certificate-p8-p12-expo)
作者：[Mehdi Azizi](https://devoria.fr/zh-hans/mehdi-azizi)
发布：2026-11-04
内容最后修订：2026-11-04


Apple 的推送证书授权服务器通过 APNs（Apple 的推送通知服务）向你的 app 发送通知。它有两种形式：.p12 证书，有效期一年，只绑定一个 app；或者 .p8 密钥，不会过期，可用于你账号下的所有 app。**用 Expo 的话，你不需要证书：EAS 会在第一次构建时让你创建一个 .p8 密钥，Expo 的推送服务替你使用它。**

[开发者证书和 provisioning profile](https://devoria.fr/zh-hans/blog/ios-certificate-provisioning-profile-expo) 那篇指南只用几行带过了推送密钥。这里我们讲透：两种格式怎么选、sandbox 和 production 两个环境、续期，以及让 TestFlight 里收不到通知的各种故障。

我用 Expo 和 EAS 发布自己的 app。Solys 和 TradaZen 在配置里都声明了 `expo-notifications` 插件：就是它让 app 做好接收通知的准备。

## 为什么 Apple 要证书或密钥

一条推送通知从服务器（你自己的或 Expo 的）发出，交给 APNs，再由 APNs 送到 iPhone。所以 APNs 必须确认这个服务器有权给你的 app 发消息。Apple 提供两种证明方式。

- **基于令牌的连接，用 .p8 密钥。** [按 Apple 的说法](https://developer.apple.com/documentation/usernotifications/establishing-a-token-based-connection-to-apns)，它更快，可以在多台服务器上使用，一个密钥就能覆盖你团队的全部或部分 app。你的服务器用它签名一个令牌，至少每小时重新生成一次。
- **基于证书的连接，用 .p12 文件。** [Apple 说明](https://developer.apple.com/documentation/usernotifications/establishing-a-certificate-based-connection-to-apns)这种证书只绑定一个 app，有效期一年。几个 app 就要几张证书。

[Apple 关于过期的页面](https://developer.apple.com/support/expiration/)也提到：APNs 令牌签名密钥永远不会过期。

## .p8 / .p12 对比表

| 对比项     | .p8 密钥           | .p12 证书             |
| ---------- | ------------------ | --------------------- |
| 有效期     | 不过期             | 一年                  |
| 适用范围   | 你的所有 app       | 单个 app              |
| 环境       | 创建时选择         | Sandbox 和 production |
| 续期       | 无需续期，除非吊销 | 每年一次              |
| 下载       | 只能下载一次       | 从 Mac 导出           |
| 数量       | 每个环境两个       | 每个 app 一个         |
| 在 Expo 中 | 由 EAS 创建和管理  | Expo 文档中没有提及   |

补充三点。.p8 文件[只能下载一次](https://developer.apple.com/help/account/keys/create-a-private-key)：Apple 不会在你的账号里保留它。而 .p12 则[从创建该证书的 Mac 的钥匙串中导出](https://developer.apple.com/help/account/capabilities/communicate-with-apns-using-a-tls-certificate)。另外，每个环境两个密钥的上限针对的是团队密钥。对于只限定部分 app 的密钥，Apple 允许每个环境最多 200 个。

[Expo 关于凭证的页面](https://docs.expo.dev/app-signing/app-credentials/)只讲推送密钥，并说每个账号最多两个，没有区分环境，也没有区分按 app 的密钥。**对 Expo app 来说，.p8 密钥是正确选择。**

## Sandbox 和 production：两个环境

APNs 有两套服务器：开发（也叫 sandbox）和生产（production）。[Apple](https://developer.apple.com/documentation/usernotifications/sending-notification-requests-to-apns) 规定前者用于测试，后者用于已发布的 app。app 通过 `aps-environment` 这个 entitlement 知道该用哪一个。[按 Apple 的说法](https://developer.apple.com/documentation/bundleresources/entitlements/aps-environment)，它的值取决于签名用的描述文件：开发描述文件对应 `development`，分发描述文件和 TestFlight 对应 `production`。

**所以 TestFlight 构建连接的是 production，和 App Store 完全一样。**

Expo 这边有两个细节。[expo-notifications 的文档](https://docs.expo.dev/versions/latest/sdk/notifications/)说明，项目里的 entitlement 始终写成 `development`，在 release 构建的归档中才改为 `production`。另外，Expo 令牌在创建时会绑定到 app 的环境，这是自动检测的：发送时你什么都不用选。

如果你自己创建密钥，Apple 会让你选择环境和范围：整个团队还是部分 app。Apple 的开发者文档现在描述了限定于 sandbox 或 production 的团队密钥，旧的通用密钥仍然有效。而[账号帮助文档](https://developer.apple.com/help/account/capabilities/communicate-with-apns-using-authentication-tokens)还说密钥两个环境都能用。Apple 自相矛盾：以你创建时选的环境为准。**只限 sandbox 的密钥，TestFlight 和 App Store 都用不了。**

## Expo 如何管理推送密钥

根据 2026 年 7 月 28 日更新的 [Expo 配置指南](https://docs.expo.dev/push-notifications/push-notifications-setup/)，第一次做开发构建时，它会问你是否配置推送通知、是否生成 APNs 密钥。两个都回答 yes。不用 EAS Build 的话，运行 `eas credentials`。你需要一个付费的 Apple Developer 账号。

之后：

- **EAS 保存密钥并关联到你的 app。** 按 Expo 的说法，一个密钥想用于多少个 app 都行，而且上传新密钥不会改变你用户的 Expo Push 令牌。
- **你的服务器不直接调用 APNs。** 它发给 Expo 的推送服务，再由后者转给 APNs。[Expo 要求你读取回执](https://docs.expo.dev/push-notifications/sending-notifications/)，大约在发送后 15 分钟：错误会显示在那里。
- **插件负责添加 entitlement。** 在 `plugins` 里声明 `expo-notifications`：

```json
{
  "expo": {
    "plugins": ["expo-notifications"]
  }
}
```

两个坑。[Expo 的 FAQ](https://docs.expo.dev/push-notifications/faq/) 说从 SDK 53 起 Expo Go 不再支持推送通知，而 expo-notifications 的页面只提到 Android。无论哪种情况，Expo 都建议用开发构建：就用它来测试。另外，用 `eas credentials` 删除密钥只是把它从 Expo 的服务器上移除，并不会从你的 Apple 账号里删除。要腾出名额，得去 Apple 开发者后台吊销它。和所有密钥一样，.p8 文件绝不能提交到你的代码仓库：规则和 [Expo app 的 API 密钥](https://devoria.fr/zh-hans/blog/expo-api-secret-key-security)一样。

## 续期：真正会过期的是什么

**.p8 密钥不需要续期。** 只有你吊销它，它才会失效。如果你怀疑它泄露了，[Apple 建议](https://developer.apple.com/help/account/capabilities/communicate-with-apns-using-authentication-tokens)先创建新密钥，切换过去，然后再吊销旧的。在 Expo 中替换密钥，FAQ 给出了步骤：`eas credentials`，选 iOS，选你的构建 profile，然后移除推送密钥并生成新的。

**.p12 证书每年过期。** Apple 要求你在到期前更新，并在钥匙串里关注到期日期。一旦过期或被吊销，[你就无法再向 app 发送通知](https://developer.apple.com/help/account/certificates/certificates-overview)。Apple 没有单独的续期流程：你从 app 的标识符创建一张新证书，在到期前装到服务器上，再移除旧证书。**最好的续期方式，就是改用密钥。**

> 你用 Intune 或其他设备管理工具管理 iPhone？那不是同一种证书。设备管理用的推送证书在 Apple Push Certificates Portal 上创建，每年续期。[Apple 建议](https://support.apple.com/guide/deployment/dep2de55389a)记下所用的 Apple 账号：续期时要用到。

## 常见故障及解决办法

### 开发环境能收到，TestFlight 收不到

1. **你发到了错误的令牌。** TestFlight 构建在 production 注册，会拿到一个新令牌。开发构建产生的令牌用不了：当[令牌与环境不匹配](https://developer.apple.com/documentation/usernotifications/handling-notification-responses-from-apns)时，Apple 会返回 `BadDeviceToken`。从 TestFlight 版本的 app 本身获取令牌。
2. **生产 app 没有推送凭证。** 这是 Expo FAQ 给出的诊断。检查 expo.dev 上项目的凭证页面，或运行 `eas credentials`。
3. **你的密钥只限 sandbox。** 换成一个在 production 有效的密钥。
4. **iPhone 拿不到令牌。** 如果你把 TestFlight 构建装在了 Xcode 构建之上，Expo 建议重启设备。

### “No valid aps-environment entitlement string found”

按 Expo FAQ 的说法，这个错误表示你的 iOS 项目没有配置推送密钥。检查凭证页面，然后重新构建：EAS 会提示你生成密钥。同时确认 `expo-notifications` 确实在 `plugins` 里。

### ITMS-90078: Missing Push Notification Entitlement

这封邮件是 Apple 在你提交后发的：你的 app 看起来会注册 APNs，但签名里没有 `aps-environment` entitlement。[Expo 关于 SDK 51 的说明](https://github.com/expo/fyi/blob/main/apns-entitlement-sdk-51.md)解释了原因。从这个 SDK 起，只有安装了 `expo-notifications` 才会添加这个 entitlement。这个警告来自 Apple 的两项检查。第一项比较你 App ID 的功能（capabilities）和 app 的 entitlement。第二项在二进制文件里查找注册通知的方法，而所有 app 中的 Expo 代码都包含这个方法。

**这是警告，不是拒绝**，Expo 明确说明。如果你要用通知，添加插件并重新构建：[EAS 会在每次构建时同步](https://docs.expo.dev/build-reference/ios-capabilities/) App ID 的功能。如果不用，就在 Apple 开发者后台把 Push Notifications 功能从你的 App ID 中移除。警告可能还会出现，但没有影响。

### InvalidCredentials，或所有 app 同时收不到通知

回执里出现 `InvalidCredentials` 意味着你的推送凭证已失效，通常是因为密钥被吊销了。所有使用它的 app 会同时收不到通知。Expo 的解决办法：`eas credentials`，然后生成新密钥。如果回执提到 `InvalidProviderToken`，Expo 建议重新构建，同时重新生成推送密钥和描述文件。

## 启用通知之前

想在发布前在 iPhone 上测试通知，看 [用 TestFlight 测试你的 app](https://devoria.fr/zh-hans/blog/testflight-guide-test-iphone-app)。如果你发送静默通知来唤醒 app，重读 [关于后台模式的 guideline 2.5.4](https://devoria.fr/zh-hans/blog/guideline-2-5-4-background-mode-rejection)。不用 Mac 的完整流程见 [没有 Mac 也能发布 iOS app](https://devoria.fr/zh-hans/blog/publish-ios-app-without-mac-expo-eas)，发布步骤见 [在 App Store 上发布应用](https://devoria.fr/zh-hans/expertise/publish-an-app-on-the-app-store)。

第一次发送之前，做一下[免费诊断](https://devoria.fr/zh-hans/diagnostic)：它会告诉你项目里卡在哪里，并发给你我的 Apple 防拒审清单。要开发 app、接入订阅并发布，[iOS & Android Kit](https://devoria.fr/zh-hans/kits/ios) 带你走完四个阶段，附带我自己 app 的起步代码，按你自己的节奏来。

## 常见问题

### Apple 推送证书会过期吗？

.p12 证书一年后过期，如果不更换，你的通知就会停止。.p8 密钥永远不会过期：只有你吊销它才会失效。这正是首选密钥的主要原因。

### 每个 app 都需要一张推送证书吗？

用 .p12 证书的话，是的：每张证书只绑定一个 app。.p8 密钥可用于你账号下的所有 app。所以在 Expo 中，一个密钥就能覆盖你的全部 app。

### 怎么续期 Apple 推送证书？

从 app 的标识符创建一张新证书（附上新的签名请求），装到你的服务器上，再移除旧证书。要在到期前完成。最简单的还是改用 .p8 密钥，它不需要续期。

### 我的 .p8 文件丢了，怎么办？

Apple 只允许下载一次。如果密钥是 EAS 创建的，Expo 会保存它，你的通知照常工作。否则，创建一个新密钥，用 `eas credentials` 添加，然后在 Apple 开发者后台吊销旧密钥。你用户的 Expo Push 令牌不会改变。

### 换了推送密钥后需要重新构建吗？

一般不需要。密钥是给发送通知的服务器用的，不是给已安装的 app 用的，而且 Expo 说明新密钥不会改变令牌。例外：如果回执显示 `InvalidProviderToken`，Expo 建议用新密钥和新描述文件重新构建。


## 你的项目

[Review Kit](https://devoria.fr/zh-hans/kits/app-review)：我的 App Store 审核清单、我被 Apple 拒审后的修正方案、Google Play 审核清单，以及三个 Claude Code skill。¥369。

[iOS & Android Kit](https://devoria.fr/zh-hans/kits/ios)：用 Claude Code 打造你的 iPhone 和 Android 应用：我自己应用的起步代码、上架商店的完整流程，以及全部工具包。¥739。

[诊断：你离自己的 iPhone 应用还差什么](https://devoria.fr/zh-hans/diagnostic)：7 个问题，60 秒：看清真正阻碍你发布 iPhone 应用的原因，拿到你的 4 步路线和适合你的套件。
