iOS 推送用 p8 还是 p12?APNs 推送密钥与证书的区别和迁移
iOS 推送新项目用 .p8 认证密钥:它不会过期,一个密钥能给团队下所有 App 发推送;.p12 推送证书每个 App 一张、一年到期。本文讲两者的创建步骤、服务端配置、从 p12 迁到 p8 和 APNs 返回码排查表。
iOS 推送新项目用 .p8 认证密钥,不用 .p12 推送证书:.p8 不会过期,一个密钥能给团队下所有 App 发推送;.p12 每个 App 一张、一年到期,只在对接只支持证书的老系统时才需要。从 p12 换到 p8 只改服务端,App 不用发新版本。下文讲两者的创建步骤、服务端和推送平台配置、迁移步骤和 APNs 返回码排查表,Apple 规则截至 2026 年 10 月,都附了官方链接。
第一次接推送、还没跑通过链路的,先按给 iOS App 加推送通知从开通能力做到真机收到第一条,这篇专门讲凭据怎么选、怎么换。
iOS 推送的 p8 和 p12 有什么区别?
| 对比项 | .p8 认证密钥(Token) | .p12 推送证书(Certificate) |
|---|---|---|
| 在哪创建 | Certificates, Identifiers & Profiles 的 Keys 页 | Identifiers 页 App ID 的 Push Notifications → Configure,或 Certificates 页 |
| 拿到的文件 | AuthKey_<Key ID>.p8,只能下载一次 |
.cer,要和生成 CSR 时的私钥合成 .p12 |
| 有效期 | 不过期,可以吊销 | 一年,到期前要换新证书 |
| 覆盖范围 | 团队密钥(Team Scoped)覆盖团队所有 App,包括以后新建的;主题密钥(Topic Specific)只覆盖选中的 App | 一张证书只绑定一个 App ID |
| 环境 | 创建时选环境,Apple 建议开发和生产各用一个密钥 | 当前类型「Sandbox & Production」一张同时用于两个环境 |
| 服务端要做的事 | 每次请求带一个用 .p8 签名的 JWT(ES256),20 到 60 分钟换一次 | 建立 TLS 连接时出示证书,请求里不用带令牌 |
| 多个 App | 一个密钥、一个连接池就够 | 每个 App 一张证书,各自管理连接 |
| 泄露后 | 先建新密钥,切换后再吊销旧的 | 吊销证书,APNs 拒绝用它建立的连接 |
| 需要的角色 | 账户持有人或管理员 | 账户持有人或管理员 |
表里的规则来自 Apple 的用令牌连接 APNs、用证书连接 APNs和帮助页用认证令牌与 APNs 通信。证书文档原话是 Provider certificates are valid for a year,过期后不能再发推送,见证书过期或被吊销会怎样。
关于密钥的环境
Apple 现在的文档写明,新建的团队密钥限定在 Sandbox 或 Production 其中一个环境,每个环境最多两个团队密钥。以前建的、两个环境都能用的密钥继续有效,但 Apple 建议改成按环境分开。主题密钥每个环境最多 200 个,每个最多关联 400 个 topic。
按环境分开后,开发环境(api.sandbox.push.apple.com)用开发密钥签 JWT,生产环境(api.push.apple.com)用生产密钥签。用错了,APNs 返回 403 BadEnvironmentKeyIdInToken。
怎么选
| 你的情况 | 选 |
|---|---|
| 新项目,自建推送服务 | .p8 团队密钥,开发、生产各一个 |
| 一个团队有多个 App | .p8 团队密钥,一个密钥覆盖全部 |
| 想让某个服务只能给指定 App 发 | .p8 主题密钥,只勾选这几个 App |
| 第三方推送平台两种都支持 | .p8,省掉每年换证书 |
| 老系统或平台只支持证书 | .p12,并在日历上记好到期日 |
怎么创建 .p8 认证密钥?
- 在 Certificates, Identifiers & Profiles 的 Keys 页点「+」,填一个名字,勾选 Apple Push Notification service (APNs)。
- 点旁边的 Configure,按页面上的选项选环境和密钥类型(Team Scoped 或 Topic Specific)。选 Topic Specific 的,再勾选要关联的 App。
- 确认后点 Download。
.p8文件只能下载一次,Download 按钮变灰说明已经下过,丢了只能吊销重建。 - 记下 Key ID(密钥详情页上,名字下方的 10 位字符)和 Team ID(开发者网站 Membership details 里的 10 位字符)。
步骤以 Apple 的创建私钥和获取密钥标识符为准。App ID 上还要勾选 Push Notifications 能力,做法见推送教程的开通能力一节。用 .p8 时 Push Notifications 旁边的 Configure 不用管。
.p8 等同于整个团队的推送权限。把它放在服务器的密钥管理里,不要提交到代码仓库,也不要打进 App。
创建 .p12 推送证书
证书要先有一份 CSR(证书签名请求),Apple 签发 .cer 后,再和 CSR 对应的私钥合成 .p12。在 Windows、Linux 上可以用 OpenSSL 完成,和申请开发证书的做法一样。
先生成私钥和 CSR。
1openssl req -new -newkey rsa:2048 -nodes -keyout push.key -out push.csr -subj "/emailAddress=you@example.com/CN=MyApp Push/C=CN"
然后到开发者后台申请证书。
- 在 Identifiers 页点开 App 的 App ID,勾选 Push Notifications,点 Configure。
- 在 Production SSL Certificate 下点 Create Certificate,上传
push.csr,点 Continue。 - 点 Download 下载
.cer文件,下面假设叫aps.cer。
最后合成 .p12,导出时设置的密码要告诉服务端或推送平台。
1openssl x509 -in aps.cer -inform DER -out aps.pem
2openssl pkcs12 -export -inkey push.key -in aps.pem -out push.p12
查看证书绑定的 App 和到期时间。
1openssl x509 -in aps.pem -noout -subject -enddate
subject 里能看到 Bundle ID,notAfter 是到期时间。步骤以 Apple 的用 TLS 证书与 APNs 通信为准,那一页写的是在 Mac 上用钥匙串导出,原理相同。
推送证书只给服务器用。在 Windows、Linux 上用 KXBuild 构建时,不要把它放进 keychain 证书目录,那里只放给 App 签名的开发证书和发布证书,见证书与签名。
服务端和第三方推送平台怎么配?
不管自建服务还是用极光、个推、Firebase 这类第三方平台,要准备的材料是同一套。第三方平台在它的后台上传即可,界面以平台文档为准。
| 用 .p8 时提供 | 用 .p12 时提供 |
|---|---|
.p8 文件 |
.p12 文件 |
| Key ID | 导出 .p12 时设的密码 |
| Team ID | 证书对应的环境(有的平台要求开发、生产分开上传) |
| Bundle ID(推送的 topic) | Bundle ID(多数平台从证书里自动读出) |
| 环境:开发或生产 |
配置时最容易错的是这三处。
- Bundle ID 一字不差。请求头
apns-topic就是 App 的 Bundle ID,和构建的不一致会返回DeviceTokenNotForTopic。Ad Hoc 测试包改过 Bundle ID 的(例如com.example.myapp.test),后台也要按测试包的填,见 iOS 多环境打包。 - 环境和包对应。用 Development 描述文件签名的 Debug 包走开发环境;TestFlight、App Store、Ad Hoc 包走生产环境。两个环境的设备令牌不通用。
- 设备令牌是最新的。App 每次启动都把令牌上报给后台,删除重装、换机后令牌可能变。
自建服务时再注意几点,均出自 Apple 的向 APNs 发送通知请求和令牌文档。
- 用 HTTP/2 和 TLS 1.2 及以上连接,端口 443,防火墙只放行推送时可以改用 2197。
- JWT 的
iat超过一小时就会被拒,返回ExpiredProviderToken。同一个连接上 20 分钟内换新 JWT 太频繁,返回TooManyProviderTokenUpdates。做一个定时任务每 20 到 60 分钟重新签一次。 - 一个连接只能对应一个团队。管理多个开发者账号的推送,要分开建连接池。
怎么从 p12 迁移到 p8?
迁移只改服务端,App 不用发新版本,设备令牌也不用重新收集。令牌由手机按 App 和环境生成,和服务端用密钥还是证书认证无关。
- 建密钥。按上文创建 .p8。建密钥不影响正在用的证书,两者可以同时有效。
- 先在开发环境验证。服务端或推送平台新增一套 .p8 配置,用 Debug 包的设备令牌发到开发环境,确认返回 200、手机收到。
- 切生产。生产环境改用 .p8,用 TestFlight 包的令牌验证一次,再观察一两天正式用户的发送成功率。
- 换用新连接。自建服务的,切换后关掉用证书建立的旧连接,用新连接发送。Apple 文档写明,连接在第一次推送时就和所用的团队、密钥绑定,之后换用无关的密钥会报错。
- 处理旧证书。确认没有任何服务还在用后,可以吊销证书,或者让它自然到期。吊销后 APNs 会拒绝所有用它建立的连接,先确认再动手。
用第三方平台的,在平台后台上传 .p8 并填好 Key ID、Team ID、Bundle ID,按平台文档的说明切换,再用测试设备各发一条确认。
iOS 推送失败按返回码怎么排查?
APNs 每个请求都会返回状态码,失败时响应体里有 {"reason":"..."}。下表的 reason 字符串和含义以 Apple 的处理 APNs 的响应为准,「常见原因」是对应到实际配置的排查方向。
| 状态码 | reason | 含义 | 常见原因与处理 |
|---|---|---|---|
| 400 | BadDeviceToken |
设备令牌无效 | 令牌和环境不匹配,Debug 包的令牌发到了生产地址或者反过来;令牌复制不完整 |
| 400 | DeviceTokenNotForTopic |
令牌和 topic 不对应 | apns-topic 和 App 的 Bundle ID 不一致,常见于测试包改了 Bundle ID |
| 400 | TopicDisallowed |
不允许向这个 topic 推送 | 核对 topic 是否属于这个团队、App ID 是否开了 Push Notifications、主题密钥是否勾选了这个 App |
| 400 | BadTopic、MissingTopic |
topic 无效或没填 | 请求头补上 apns-topic,值为 Bundle ID |
| 400 | InvalidPushType |
apns-push-type 无效 |
普通通知填 alert,静默推送填 background |
| 403 | InvalidProviderToken |
JWT 无效或签名验证不过 | Key ID、Team ID 填错,.p8 不是这个团队的,签名不是 64 字节的原始格式 |
| 403 | ExpiredProviderToken |
JWT 过期 | JWT 超过一小时,或服务器时间不准,校准时间并定时重签 |
| 403 | BadEnvironmentKeyIdInToken |
密钥和环境不符 | 开发密钥发到了生产环境或者反过来 |
| 403 | UnrelatedKeyIdInToken |
和这个连接第一次推送用的密钥无关 | 换密钥后要新建连接 |
| 403 | MissingProviderToken |
没带证书也没带 JWT | 请求头补上 authorization: bearer <JWT> |
| 403 | BadCertificate |
证书无效 | .p12 损坏、密码错、证书已过期或被吊销 |
| 403 | BadCertificateEnvironment |
证书和环境不符 | 证书和所连的地址(开发或生产)对不上,换成对应环境的证书或改连接地址 |
| 410 | Unregistered |
令牌对这个 topic 已失效 | 常见于 App 已从手机删除,停止向这个令牌发送,除非 App 再次上报同一个令牌 |
| 410 | ExpiredToken |
设备令牌已过期 | 等 App 下次启动上报新令牌 |
| 413 | PayloadTooLarge |
payload 太大 | 普通推送不超过 4KB,大图片改成通知服务扩展下载 |
| 429 | TooManyProviderTokenUpdates |
JWT 更新太频繁 | 一个 JWT 复用 20 到 60 分钟,不要每次请求都重签 |
| 429 | TooManyRequests |
对同一令牌发送太密 | 降低频率,延迟后再试 |
Apple 要求 BadDeviceToken、DeviceTokenNotForTopic、Forbidden、ExpiredToken、Unregistered、PayloadTooLarge 这几种不要原样重试,改好配置或换新令牌再发。5xx 错误可以在 15 分钟后带退避重试。太多 4xx 错误会让 APNs 降低你的发送速度甚至断开连接。
返回 200 却收不到、或拿不到令牌怎么办?
返回 200 只说明 APNs 收下了。手机端收不到、或者 App 根本拿不到设备令牌,多半是 App 签名里的 aps-environment 有问题。
| 现象 | 原因 | 处理 |
|---|---|---|
注册推送失败,no valid "aps-environment" entitlement string found for application |
签名里没有 aps-environment |
App ID 勾选 Push Notifications,重新生成描述文件,工程的 entitlements 加上这个键 |
| 构建报描述文件不含 Push Notifications | 描述文件是开通能力前生成的 | 编辑描述文件后直接保存,重新下载替换 |
| 上传 App Store Connect 报 ITMS-90078 | 代码注册了推送,签名里没有推送权限 | 见 ITMS-90078 |
| Debug 收得到,TestFlight 收不到 | 后台还在用开发环境地址或开发令牌 | 生产包用生产环境地址和它上报的新令牌 |
| 推送证书时代能收,换 .p8 后收不到 | 密钥环境选错,或发送端还连着旧连接 | 看返回的 reason,对照上表 |
aps-environment 的值由描述文件决定。Apple 的 aps-environment 文档写明,开发描述文件对应 development(也叫 sandbox),发布描述文件和 TestFlight 对应 production。在 Windows、Linux 上用 KXBuild 构建时,entitlements 文件里写 development 即可,构建时按所用描述文件取值,配置方法见推送教程的 entitlements 一节。描述文件相关的构建报错见 iOS 签名报错与 ipa 安装失败。凭据、环境和签名三者都对上,iOS 推送的链路才算完整。
常见问题
APNs 的 .p8 密钥会过期吗?
不会。Apple 写明 APNs 签名密钥不会过期,但可以被吊销。要换密钥时先建新的,服务端切换完成后再吊销旧的。
一个 .p8 密钥能给多个 App 发推送吗?
能。团队密钥覆盖团队下所有 App,包括以后新建的,发送时用 apns-topic 指定发给哪个 App。只想覆盖部分 App 时,建主题密钥并勾选这几个 App。
推送证书 .p12 过期了会怎样?
服务器不能再用它发推送,App 本身照常运行,只是收不到通知。推送证书有效期一年,到期前换新证书,或者趁这次改用 .p8 密钥。
从 p12 换成 p8,需要重新发版或重新收集设备令牌吗?
都不需要。设备令牌由手机按 App 和环境生成,和服务端用哪种凭据无关,只要在服务端或推送平台把凭据换掉。
第三方推送平台应该上传 p8 还是 p12?
平台支持 .p8 就上传 .p8,同时填 Key ID、Team ID 和 Bundle ID,以后不用每年换证书。平台只支持证书时上传 .p12 和导出密码,并注意按开发、生产环境分别配置。