iOS 自动填充原理:密码、Passkeys 与验证码是如何工作的?

type
status
date
slug
summary
tags
category
icon
password
wechat_gate
在开发密码管理器 Kypra 的过程中,我遇到过一个看似简单的问题:
为什么 iOS「自动填充与密码」设置中,有的 App 显示「密码(仅填充)」,有的显示「通行密钥」,还有的显示「验证码」?
起初以为只是功能开关不同,深入研究 AuthenticationServices 后才发现,这三种标签对应的是完全不同的凭据能力。
密码负责交付已有秘密,Passkeys 通过私钥签名证明身份,验证码则提供短时间有效的验证结果。
它们的协议、数据结构和安全边界都不一样。
本文结合 Kypra 的实际开发经历,聊聊 iOS 自动填充的工作原理、关键实现,以及第三方密码管理器需要注意的问题。

一、iOS 自动填充究竟是什么?

打开 iPhone:
设置 → 通用 → 自动填充与密码
这里可以开启自动填充,并选择使用哪些密码管理器。
需要明确的是:
自动填充不是另一套密码数据库,而是 iOS 提供的系统级凭据调度机制。
它主要涉及三个角色:
  • 登录 App:告诉系统当前输入框需要什么凭据。
  • iOS 系统:负责匹配建议、协调用户操作和调用提供者。
  • 密码管理器:保存凭据,在系统请求时提供用户选中的数据。
例如,用户登录某个 App:
  1. 点击用户名或密码输入框。
  1. iOS 根据输入框和服务信息查找匹配凭据。
  1. 键盘上方出现账号建议。
  1. 用户选择 Kypra 中保存的账号。
  1. Kypra 解锁保险箱,读取对应凭据。
  1. 系统将账号和密码填入登录页面。
整个过程不需要反复打开密码管理器复制、粘贴。
但有一个重要的安全边界:
启用自动填充,不代表其他 App 可以读取密码管理器中的全部密码。
系统只会根据用户操作和相关请求协调凭据交付。
另外,安装密码管理器不等于自动启用。用户仍然需要在系统设置中允许它参与自动填充。

二、密码、Passkeys 和验证码有什么区别?

我习惯从两个角度理解:
保存了什么?登录时交出了什么?
凭据类型
管理器保存
登录时提供
密码
用户名、密码
账号和密码
Passkeys
私钥及凭据元数据
挑战签名和协议数据
TOTP
共享 Secret
当前有效验证码

1. 密码:交付已有的登录秘密

这是传统密码管理器最常见的功能。
用户保存账号密码,登录时由系统调用密码管理器,将对应数据填入表单。
在 iOS 中,可以通过 ASPasswordCredential 返回:
它解决了重复输入密码的问题,但密码最终仍会交给目标网站或 App。
因此,自动填充提高了便利性,却不代表传统密码天然具备抗钓鱼能力。
那么,为什么有些管理器显示「密码(仅填充)」?
因为能填充已有密码,不代表能够响应系统发起的新密码保存、更新或生成请求。
这几项能力需要分别实现。

2. Passkeys:不提交密码,而是证明身份

Passkeys(通行密钥)采用公钥密码学和挑战响应机制。
基本过程是:
注册阶段
客户端创建公私钥对。
网站保存公钥,私钥由用户的设备或凭据管理器安全保存。
登录阶段
网站发送随机挑战,凭据提供者使用私钥构造签名响应,服务器再用公钥验证。
整个过程中,私钥不会发送给网站。
很多人会把 Passkeys 理解为 Face ID 登录,但两者并不是一回事。
Face ID 是本地用户验证方式,Passkey 才是真正参与远程身份认证的凭据。
Passkeys 还有一个重要优势:认证与网站身份绑定,能有效抵抗仿冒域名的密码钓鱼。
不过,对于开发者而言,实现 Passkeys 并不简单。
除了生成密钥,还要处理 WebAuthn 协议、私钥存储、凭据同步、恢复和异常情况。

3. 验证码:SMS OTP 与 TOTP 不一样

虽然用户看到的都是几位数字,但验证码至少有两种常见来源。
SMS / Email OTP
验证码由服务器生成,通过短信或邮件发送。iOS 在支持的场景下识别验证码,并显示自动填充建议。
TOTP
TOTP(基于时间的一次性密码)通常由密码管理器生成。
它保存共享 Secret,再结合当前时间计算验证码。常见配置是每 30 秒更新一次。
例如,用户登录 GitHub:
先填写密码,再由 Kypra 提供当前 TOTP 验证码。
Apple 从 iOS 18 开始支持第三方凭据提供者集成一次性验证码填充能力。
需要注意,系统中的「验证码使用后删除」针对的是短信或邮件中的验证码消息。
它不会删除密码管理器里保存的 TOTP Secret。

三、普通 iOS App 如何支持自动填充?

如果开发的是普通 App,例如社交、购物或效率工具,接入自动填充通常并不复杂。
核心是让系统准确识别输入框的用途。

1. UIKit 输入框配置

这里有两个容易混淆的属性:
isSecureTextEntry:决定密码是否隐藏显示。
textContentType:告诉系统输入框属于哪种凭据类型。
仅仅隐藏密码字符,不代表完成了自动填充适配。
对于网页表单,也应该使用标准的 autocomplete 属性,例如 username、current-password 和 one-time-code。

2. 配置 Associated Domains

如果 iOS App 和网站使用同一套账号,还需要建立可信关联。
在 Xcode 的 Associated Domains 中添加:
同时,在网站部署符合 Apple 要求的 apple-app-site-association 文件。
这样系统才能更准确地匹配网站与 App 对应的登录凭据。
实际开发中,很多「Safari 能自动填充,App 却不行」的问题,都与输入框语义、域名关联或 AASA 配置有关。

四、密码管理器如何接入系统?

对于 Kypra 这样的密码管理器,情况就复杂得多。
核心框架是:
AuthenticationServices + Credential Provider Extension
整体架构可以拆成四部分。
主 App
负责管理加密保险箱,支持新增、编辑、删除密码,以及同步和安全设置。
Credential Provider Extension
响应系统发起的凭据请求,完成解锁、查询、解密与返回。
ASCredentialIdentityStore
登记可用于自动填充建议的凭据身份。
App Group / Keychain
在安全边界内,为主 App 和扩展提供必要的数据共享能力。

1. 为什么需要凭据索引?

假设 Kypra 保存了几百条密码。
用户打开某个网站时,系统不可能先读取全部明文密码,再判断哪一条匹配。
因此,Apple 提供了 ASCredentialIdentityStore。
开发者可以登记域名、用户名和记录标识:
这里登记的不是密码,而是用于匹配的身份信息。
当用户真正选择某条凭据时,系统再向扩展请求对应的秘密数据。
系统建议索引与加密保险箱必须分离。
同时,域名和用户名也属于需要保护的数据,不能因为没有保存密码就忽略隐私风险。

2. 保险箱解锁如何处理?

这是 Kypra 开发过程中需要特别注意的地方。
系统可能先调用无界面接口:
如果保险箱处于锁定状态,扩展不能直接在这里随意弹出解锁界面。
正确的做法是向系统报告需要用户交互,再进入允许展示 UI 的处理流程。
我在 Kypra 中采用的思路是:
  • 默认保持保险箱锁定。
  • 用户选择凭据后才进行授权。
  • 只解密当前选中的记录。
  • 请求结束后及时清理敏感状态。
对于密码管理器,安全性不只是加密算法的问题,更是数据在什么时刻、以什么范围被访问的问题。

五、Kypra 当前实现到了哪一步?

结合目前的 Kypra 实现,可以把系统集成能力分成几个阶段。
功能
当前状态
已有密码自动填充
已接入
TOTP 验证码填充
已接入
Passkeys 创建与登录
尚未接入
系统发起的密码保存
尚未接入
系统强密码生成集成
尚未接入
所以,iOS 设置中显示「密码(仅填充)」是符合当前实现的。
虽然 Kypra 内部可以手动新增密码,也有密码生成能力,但这不代表它已经接入系统级保存或生成流程。
同样,只在扩展中声明 ProvidesPasskeys = true,并不会自动获得通行密钥能力。
如果未来接入 Passkeys,至少需要完成创建、认证、私钥保护、跨设备同步与恢复,并在真机上验证整个流程。
我认为 Passkeys 应当作为独立的认证功能进行设计,而不是自动填充模块里的一个小补丁。

六、自动填充失效时,应该如何排查?

实际开发中,我更倾向于按系统调用链逐层排查,而不是一开始就修改代码。
第一层:系统配置
检查自动填充总开关,以及对应密码管理器是否启用。
第二层:输入框和域名
确认 textContentType、Associated Domains 和 AASA 配置是否正确。
第三层:凭据索引
检查 ASCredentialIdentityStore 是否正常同步,以及域名与记录标识是否匹配。
第四层:扩展执行
确认扩展是否收到请求、保险箱能否解锁、对应凭据能否正确返回。
这里尤其要区分两种问题:
没有出现凭据建议,通常优先检查配置、索引和域名。
选择凭据后填充失败,则应重点检查扩展调用、解密和响应过程。
还有一点:系统设置中显示了 Kypra,只能说明系统识别到了提供者及其声明的能力。
是否真正完成填充,仍然需要真机测试。

七、写在最后

过去开发普通业务 App 时,我更多关注的是如何让用户更快完成登录。
真正开始开发 Kypra 之后,才发现自动填充背后是一套设计相当清晰的系统机制。
登录 App 负责声明输入意图,iOS 负责协调与调度,密码管理器负责安全存储并按需交付凭据。
密码、Passkeys 和验证码虽然都服务于登录,但底层解决的是不同问题。
密码交付秘密,Passkeys 证明私钥所有权,TOTP 提供短期有效的验证结果。
对独立开发者而言,我认为最重要的不是让设置页多显示几个能力标签,而是让每一项声明的能力都能在真实场景中安全、稳定地运行。
功能可以逐步完善,但底层安全边界不能含糊。
如果你也在开发 iOS 登录系统或密码管理器,欢迎交流 AuthenticationServices、Credential Provider Extension 和 Passkeys 的实践经验。

参考资料
  • Apple Developer:About the Password AutoFill Workflow
  • Apple Developer:ASCredentialIdentityStore
  • Apple Developer:ASCredentialProviderViewController
  • Apple WWDC24:Streamline sign-in with passkey upgrades and credential managers

写 AI,写成长,偶尔写投资。
关注沐风,不定期更新,全是干货。
2026.11.10
沪 · 赵巷