这些名为 passkeys 的东西最近很火。它们是 W3C TPAC 2022 的主要亮点,获得了 Safari 16 的支持,正在进入 macOS 和 iOS,并将成为 像 1Password 这样的密码管理器的未来。它们 已得到 Android 的支持,并将在未来版本中进入 Chrome OS 和 Windows。
极客式的操作系统安全增强功能并没有在前端社区引起轰动,但 passkeys 将成为“事物”是合乎逻辑的。考虑到密码和密码应用程序如何影响身份验证和表单处理等方面的用户体验,我们至少应该了解它们,这样我们就知道将要发生什么。
这就是本文的目的。我一直在研究和尝试 passkeys——以及它们基于的 WebAuthn API——已经有一段时间了。让我分享一下我所学到的知识。
目录
- 术语
- 什么是 passkeys?
- passkeys 如何取代密码?
- 更多关于密码学的知识
- 我们如何访问 passkeys?
- passkeys 和 WebAuthn 之间的区别
- 简而言之的过程
- 重点内容
- 一些缺点
- 未来发展方向?
- 资源
术语
这是我们深入了解时需要了解的术语的必备部分。像大多数科技一样,passkeys 充斥着深奥的词汇和缩略词,这些词汇和缩略词往往是理解的障碍。我将在这里尝试为您解释一些内容。
- 依赖方: 您将要验证的服务器。在本文中,我们将使用“服务器”来隐含依赖方。
- 客户端: 在我们的例子中,就是 web 浏览器或操作系统。
- 验证器: 允许生成和存储公钥对的软件和/或硬件设备。
- FIDO:一个开放标准机构,也创建关于 FIDO 凭据的规范。
- WebAuthn:passkeys 的底层协议,也称为 FIDO2 凭据或单设备 FIDO 凭据。
- Passkeys:WebAuthn,但带有云同步(也称为多设备 FIDO 凭据、可发现凭据或驻留凭据)。
- 公钥密码学: 一个生成的密钥对,包括一个私钥和一个公钥。根据算法,它应该用于签名和验证,或者用于加密和解密。这也被称为 非对称密码学。
- RSA: 三位创作者 Rivest、Shamir 和 Adel 的首字母缩写。RSA 是一个较老但仍然有用的公钥密码学家族,它基于素数分解。
- 椭圆曲线密码学 (ECC): 一个较新的密码学家族, 基于椭圆曲线.
- ES256: 一个椭圆曲线公钥,使用 ECDSA 签名算法(PDF)和 SHA256 进行哈希运算。
- RS256: 与 ES256 相似,但它使用 RSA,使用 RSASSA-PKCS1-v1.5 和 SHA256。
什么是 passkeys?
在具体谈论 passkeys 之前,我们需要谈论另一个名为 WebAuthn (也称为 FIDO2)的协议。Passkeys 是建立在 WebAuthn 之上的一个规范。WebAuthn 允许使用公钥密码学来取代密码。我们使用某种安全设备,例如硬件密钥或 可信平台模块 (TPM),来创建私钥和公钥。
公钥可以被任何人使用。但是,私钥不能从生成它的设备中删除。这是 WebAuthn 的问题之一;如果您丢失了设备,您将失去访问权限。
Passkeys 通过提供您的凭据的云同步解决了这个问题。换句话说,您在计算机上生成的内容现在也可以在您的手机上使用(尽管令人困惑的是,也存在单设备凭据)。
目前,在撰写本文时,只有 iOS、macOS 和 Android 提供对云同步 passkeys 的完全支持,即使那样,它们也受到所用浏览器的限制。Google 和 Apple 分别通过其 Google 密码管理器 和 Apple iCloud 钥匙串 服务提供同步界面。
passkeys 如何取代密码?
在公钥密码学中,您可以执行称为 签名 的操作。签名会获取一段数据,然后使用私钥将其运行到签名算法中,然后可以使用公钥对其进行验证。
任何人都可以生成公钥对,它不属于任何个人,因为任何人都可能首先生成了它。使其有用的原因是,只有使用私钥签名的数据才能使用公钥进行验证。这部分取代了密码——服务器存储公钥,我们通过验证我们是否拥有另一半(例如私钥)来登录,方法是签署一个随机挑战。
作为一项额外的好处,由于我们将用户的公钥存储在数据库中,因此不再担心密码泄露会影响数百万用户。这减少了网络钓鱼、泄露以及我们当前依赖密码的世界所面临的一系列其他安全问题。如果数据库被泄露,所有存储在用户公钥中的内容都将变得对攻击者毫无用处。
也不再需要忘记电子邮件及其关联的密码!浏览器将记住您为哪个网站使用了哪些凭据——您只需要点击几下,就可以登录。您可以提供一种辅助验证方式来使用 passkey,例如生物识别或 PIN,但这些方式仍然比过去几年的密码快得多。
更多关于密码学的知识
公钥密码学涉及拥有私钥和公钥(称为密钥对)。密钥一起生成并具有不同的用途。例如,私钥应保密,而公钥应提供给您希望与之交换消息的任何人。
在加密和解密消息时,使用接收者的公钥加密消息,以便只有接收者的私钥才能解密消息。在安全术语中,这被称为“提供机密性”。但是,这并不能证明发送者就是他们自称的人,因为任何人都可以使用公钥向某人发送加密消息。
在某些情况下,我们需要验证消息确实来自发送者。在这些情况下,我们使用签名和签名验证来确保发送者就是他们自称的人(也称为 真实性)。在公钥(也称为 非对称)密码学中,这通常通过对消息的哈希值进行签名来完成,以便只有公钥才能正确验证它。哈希值和发送者的私钥在通过算法运行后会生成签名,然后任何人都可以使用发送者的公钥验证消息来自发送者。
我们如何访问 passkeys?
要访问 passkeys,我们首先需要生成它们并将其存储在某处。一些功能可以通过验证器提供。 验证器 是任何提供加密密钥生成能力的硬件或软件支持的设备。想想您从 Google Authenticator、 1Password 或 LastPass 等获得的一次性密码。
例如,软件身份验证器可以使用设备的受信任平台模块 (TPM) 或安全区域来创建凭据。然后可以将凭据存储在远程位置并在设备之间同步,例如密码键。硬件身份验证器类似于 YubiKey,它可以在设备本身生成和存储密钥。
要访问身份验证器,浏览器需要访问硬件,为此,我们需要一个接口。我们在这里使用的接口是客户端到身份验证器协议 (CTAP)。它允许通过不同的机制访问不同的身份验证器。例如,我们可以通过利用 CTAP 通过 NFC、USB 和蓝牙访问身份验证器。
使用密码键的一个更有趣的方法是通过蓝牙将您的手机连接到可能不支持密码键的另一台设备。当设备通过蓝牙配对时,我可以使用我的手机作为中介登录到我计算机上的浏览器!
密码键与 WebAuthn 之间的区别
密码键和 WebAuthn 密钥在几个方面有所不同。首先,密码键被认为是多设备凭据,可以在设备之间同步。相比之下,WebAuthn 密钥是单设备凭据——一种花哨的说法,即您绑定到一个设备进行验证。
其次,要对服务器进行身份验证,WebAuthn 密钥需要提供用户的登录句柄,之后服务器会将一个 allowCredentials
列表返回给客户端,告知可以使用哪些凭据登录。密码键跳过此步骤,并使用服务器的域名来显示哪些密钥已绑定到该站点。 您能够选择与该服务器关联的密码键,因为它已经被您的系统识别。
否则,密钥在密码学上是相同的;它们只是在存储方式以及用于启动登录过程的信息方面有所不同。
过程… 简而言之
生成 WebAuthn 或密码键的过程非常相似:从服务器获取一个挑战,然后使用 navigator.credentials.create
Web API 生成一个公钥对。然后,将挑战和公钥发送回服务器以进行存储。
服务器在收到公钥和挑战后,会验证挑战和创建它的会话。如果验证通过,则会将公钥存储起来,以及数据库中的任何其他相关信息,例如用户标识符或证明数据。
用户还有一个步骤——从服务器检索另一个挑战,并使用 navigator.credentials.get
API 对挑战进行签名。我们将签名的挑战发送回服务器,服务器会验证挑战,然后如果签名通过,则登录我们。
当然,每个步骤还有更多内容。但这通常是我们使用 WebAuthn 或密码键登录网站的方式。
重点
密码键用于两个不同的阶段:证明和断言阶段。
证明阶段也可以看作是注册阶段。您将在新网站上使用电子邮件和密码进行注册,但是,在这种情况下,我们将使用我们的密码键。
断言阶段类似于您在注册后登录网站的方式。
证明

navigator.credentials.create
API 是我们证明阶段的重点。我们已在系统中注册为新用户,需要生成一个新的公钥对。但是,我们需要指定要生成哪种密钥对。这意味着我们需要向 navigator.credentials.create
提供选项。
// The `challenge` is random and has to come from the server
const publicKey: PublicKeyCredentialCreationOptions = {
challenge: safeEncode(challenge),
rp: {
id: window.location.host,
name: document.title,
},
user: {
id: new TextEncoder().encode(crypto.randomUUID()), // Why not make it random?
name: 'Your username',
displayName: 'Display name in browser',
},
pubKeyCredParams: [
{
type: 'public-key',
alg: -7, // ES256
},
{
type: 'public-key',
alg: -256, // RS256
},
],
authenticatorSelection: {
userVerification: 'preferred', // Do you want to use biometrics or a pin?
residentKey: 'required', // Create a resident key e.g. passkey
},
attestation: 'indirect', // indirect, direct, or none
timeout: 60_000,
};
const pubKeyCredential: PublicKeyCredential = await navigator.credentials.create({
publicKey
});
const {
id // the key id a.k.a. kid
} = pubKeyCredential;
const pubKey = pubKeyCredential.response.getPublicKey();
const { clientDataJSON, attestationObject } = pubKeyCredential.response;
const { type, challenge, origin } = JSON.parse(new TextDecoder().decode(clientDataJSON));
// Send data off to the server for registration
我们将获得 PublicKeyCredential
,其中包含在创建后返回的 AuthenticatorAttestationResponse
。凭据包含生成的密钥对的 ID。
响应提供了几个有用的信息。首先,我们在该响应中拥有我们的公钥,需要将其发送到服务器以进行存储。其次,我们还获得 clientDataJSON
属性,我们可以对其进行解码,并从那里获得密码键的 type
、challenge
和 origin
。
对于证明,我们想要在服务器上验证 type
、challenge
和 origin
,以及存储公钥及其标识符,例如 kid。我们也可以选择存储 attestationObject
,如果我们愿意。另一个有用的存储属性是 COSE 算法,它在我们的 PublicKeyCredentialCreationOptions
中定义,alg: -7
或 alg: -256
,以便在断言阶段轻松验证任何签名的挑战。
断言

navigator.credentials.get
API 将是我们断言阶段的重点。从概念上讲,这将是用户在注册后登录 Web 应用程序的地方。
// The `challenge` is random and has to come from the server
const publicKey: PublicKeyCredentialRequestOptions = {
challenge: new TextEncoder().encode(challenge),
rpId: window.location.host,
timeout: 60_000,
};
const publicKeyCredential: PublicKeyCredential = await navigator.credentials.get({
publicKey,
mediation: 'optional',
});
const {
id // the key id, aka kid
} = pubKeyCredential;
const { clientDataJSON, attestationObject, signature, userHandle } = pubKeyCredential.response;
const { type, challenge, origin } = JSON.parse(new TextDecoder().decode(clientDataJSON));
// Send data off to the server for verification
我们又将获得一个带有 AuthenticatorAssertionResponse
的 PublicKeyCredential
。凭据再次包含密钥标识符。
我们还从 clientDataJSON
中再次获得 type
、challenge
和 origin
。signature
现在包含在响应中,以及 authenticatorData
。我们需要它们以及 clientDataJSON
来验证这是使用私钥签名的。
authenticatorData
包含一些值得跟踪的属性。第一个是您正在使用的源的 SHA256 哈希,位于前 32 个字节内,这对于验证请求是否来自相同的源服务器很有用。第二个是 signCount
,它来自第 33 到 37 个字节。这是从身份验证器生成的,应该与其以前的值进行比较,以确保密钥没有发生任何问题。当它是多设备密码键时,该值应始终为 0,并且当它是单设备密码键时,该值应始终大于以前的值。
一旦您断言了您的登录,您就应该登录了——恭喜!密码键是一个非常棒的协议,但它确实有一些注意事项。
一些缺点
密码键有很多优点,但是,在撰写本文时,它也存在一些问题。首先,密码键在某种程度上仍然处于早期支持阶段,Windows 上只允许使用单设备凭据,而 Linux 系统几乎没有支持。 Passkeys.dev 提供了一个 类似于该协议的 Caniuse 的好表格。
此外,Google 和 Apple 的密码键平台无法相互通信。如果您想将您的凭据从 Android 手机转移到 iPhone 上……好吧,目前您只能祝你好运。这并不是说没有互操作性!您可以通过使用手机作为身份验证器来登录到计算机。但是,将其直接内置到操作系统中并同步会更简洁,而不会被锁定在供应商级别。
未来将会怎样?
未来的密码键协议会是什么样子?看起来很棒!一旦它获得更多操作系统的支持,使用量应该会增加,您会开始看到它在野外被越来越多地使用。一些 密码管理器 甚至会直接支持它们。
密码键绝不仅限于 Web 支持。Android 和 iOS 都将支持原生密码键作为一等公民。我们仍处于这一切的早期阶段,但预计会看到它被越来越多地提及。
毕竟,我们消除了对密码的需要,从而使世界变得更加安全!
资源
如果您想了解更多关于 Passkeys 的信息,这里还有一些额外的资源。 这篇文章还附带了一个我整理的代码库和演示。
- 实时演示 (该表单不会收集任何实际信息)
- 演示 GitHub 代码库
- YubiKey 文档
- Passkeys.dev
- Passkeys.io
- Webauthn.io
https://passkeys.directory 是一个社区驱动的索引,收录了提供 Passkeys 登录的网站、应用程序和服务。
我的主要问题是,这过于依赖所有东西都按预期工作。。
如果您的设备忘记保存 Passkey(有时当我使用 iCloud Keychain 与另一个浏览器一起使用时会发生这种情况),那么就没有办法手动复制 Passkey 并将其添加到您的密码管理器或其他设备中。 这也将使在另一个密码管理器中备份您的密码变得困难。 我个人使用两个,以防其中一个出现问题或我被锁定而无法登录。
我一点也不喜欢这个,因为过于依赖所有东西都完美运行。
如果我必须使用蓝牙在台式机上登录,那么如果设备上没有蓝牙或蓝牙无法正常工作,我该怎么办?
希望这将成为一个选项,而不是取代常规密码。
目前,Passkeys 演示在所有我拥有的浏览器中都与 WebAuthn 的行为完全一样:它要求使用安全密钥,但我没有。
使用移动设备作为 WebAuthn 中的漫游身份验证器这个想法怎么了?
要求凭据与云同步似乎很奇怪,原因有很多:您使用什么在新的设备上进行第一次同步? 旧设备? 如果之前的设备丢失了,这并非总是一个选项。 密码? 然后它会让人想起密码管理器及其常见的批评,即把所有鸡蛋放在一个篮子里。 此外,Google 和 Apple 提供了同步解决方案,但也有开源自托管选项? 我希望他们能找到一些方法让同步提供商之间互操作,考虑到开源解决方案,也许可以使用某种标准化的开源密码加密凭据交换格式,以便在切换提供商时使用,否则在我看来这仅仅是让 Apple 的封闭花园更加糟糕的一种方式——难怪 Apple 是我听说 Passkeys 的第一家公司。
我使用过 sftp 公钥/私钥来避免传统的服务器登录。 我假设 Passkeys 本质上是“相同的”。 对吧?
那么,好处是什么? 从安全角度来看,基本上可以归结为一种花哨的说法“您的密码管理器会为您生成一个随机密码,并且仅在您登录时发送该密码的哈希值”(它比这更安全,因为“哈希值”仅供一次性使用)。 这意味着您拥有密码管理器的所有经典缺点,以及新的缺点是您不再有任何其他选择。 密码管理器创造了一个非常有吸引力的攻击目标,因为它们将您的整个数字生活集中在一个地方。 这比在所有地方重复使用同一个密码更安全,但可能不如将您的密码放在计算机旁边的一个物理笔记本中安全。 设备特定的 Passkeys 的优点是私钥永远不会离开诸如“安全芯片”之类的设备,但是一旦您开始同步它们,您就会回到传统的密码管理器领域。 这看起来并不像是一个了不起的进步~
听起来很酷。 我一直在尝试让它在我的工作中的 AD 登录(我是一个管理员)中工作。 我已经在 Intune 上完成了配置,但我遗漏了一些东西。 我想知道我是否需要启用密码回写(我们是混合的)。
在 USB 密钥上丢失 Passkey 可能很麻烦,因为 Passkey 通常与用于身份验证的公钥和私钥对相关联。
在这种情况下发生了什么?
好吧,我喜欢这个背后的想法,但我不想依赖我不控制的云同步数据库。 然而,我知道大多数人对密码的谨慎程度不如他们应该的那样,而 Passkeys 是一种很好的解决方案。
我希望能够找到一种介于依赖云和保持机密安全之间的折衷方案。
感谢您撰写这篇文章。