密碼金鑰可讓使用者帳戶更安全、更簡單易用。
發布日期:2022 年 10 月 12 日,上次更新時間:2026 年 4 月 9 日
使用密碼金鑰可提升安全性、簡化登入程序,並取代密碼。密碼金鑰與一般密碼不同,使用者不必記住密碼並手動輸入,而是透過裝置的螢幕鎖定機制 (例如生物辨識或 PIN 碼) 登入,有助於降低網路釣魚和憑證遭竊的風險。
密碼金鑰會透過 Google 密碼管理工具和 iCloud 鑰匙圈等密碼金鑰提供者,在裝置間同步。
您必須建立密碼金鑰,將私密金鑰安全地儲存在密碼金鑰供應商,並將必要的中繼資料和公開金鑰儲存在伺服器上,以進行驗證。私密金鑰會在有效網域上驗證使用者後發出簽章,因此密碼金鑰可防範網路釣魚。公開金鑰會驗證簽章,且不會儲存機密憑證,因此密碼金鑰可防範憑證遭竊。
建立密碼金鑰的運作方式
如要透過密碼金鑰登入,使用者必須建立密碼金鑰並連結至使用者帳戶,再將其公開金鑰儲存在伺服器上。
在下列情況中,您可以要求使用者建立密碼金鑰:
- 註冊期間或註冊後。
- 登入後。
- 使用其他裝置的密碼金鑰登入後 (即
authenticatorAttachmentcross-platform)。 - 在專屬頁面,使用者可管理密碼金鑰。
如要建立密碼金鑰,請使用 WebAuthn API。
密碼金鑰註冊流程的四個元件如下:
- 後端:儲存使用者帳戶詳細資料,包括公開金鑰。
- 前端:與瀏覽器通訊,並從後端擷取必要資料。
- 瀏覽器:執行 JavaScript 並與 WebAuthn API 互動。
- 密碼金鑰供應商:建立及儲存密碼金鑰。這通常是密碼管理工具 (例如 Google 密碼管理工具) 或安全金鑰。
建立密碼金鑰前,請確認系統符合下列先決條件:
在有意義的短時間內,透過安全方法 (例如電子郵件、電話驗證或身分同盟) 驗證使用者帳戶。
前端和後端可以安全地通訊,交換憑證資料。
瀏覽器支援 WebAuthn 和密碼金鑰建立作業。
我們會在下文說明如何檢查大部分的項目。
系統滿足這些條件後,就會執行下列程序來建立密碼金鑰:
- 當使用者啟動動作時 (例如在密碼金鑰管理頁面中點選「建立密碼金鑰」按鈕,或完成註冊程序後),系統就會觸發密碼金鑰建立程序。
- 前端會向後端要求必要的憑證資料,包括使用者資訊、驗證問題和憑證 ID,以防重複。
- 前端會呼叫
navigator.credentials.create(),提示裝置的密碼金鑰供應商使用後端資訊產生密碼金鑰。請注意,這項呼叫會傳回 Promise。 - 使用者的裝置會使用生物特徵辨識方法、PIN 碼或解鎖圖案驗證使用者,藉此建立密碼金鑰。
- 密碼金鑰供應商會建立密碼金鑰,並將公開金鑰憑證傳回前端,解決 Promise。
- 前端會將產生的公開金鑰憑證傳送至後端。
- 後端會儲存公開金鑰和其他重要資料,以供日後進行驗證,
- 後端會通知使用者 (例如透過電子郵件),確認密碼金鑰建立作業,並偵測潛在的未授權存取行為。
這項程序可確保使用者能安全順暢地註冊密碼金鑰。
適用情形
大多數瀏覽器都支援 WebAuthn,但仍有少許缺漏。 如需瀏覽器和作業系統的相容性詳細資料,請參閱 passkeys.dev。
建立新的密碼金鑰
如要建立新密碼金鑰,前端應按照下列程序操作:
以下各節說明如何執行這項操作。
確認相容性
顯示「建立新密碼金鑰」按鈕前,前端應檢查下列事項:
- 瀏覽器支援 WebAuthn,且支援
PublicKeyCredential。
- 瀏覽器支援使用
PublicKeyCredential.getClientCapabilities()偵測功能。
瀏覽器支援 WebAuthn 條件式 UI,並使用
conditionalGet。裝置支援平台驗證器 (可在裝置上建立密碼金鑰並進行驗證),且支援
passkeyPlatformAuthenticator。
下列程式碼片段說明如何先檢查相容性,再顯示密碼金鑰相關選項。
if (window.PublicKeyCredential && PublicKeyCredential.getClientCapabilities) {
const capabilities = await PublicKeyCredential.getClientCapabilities();
if (capabilities.conditionalGet === true &&
capabilities.passkeyPlatformAuthenticator === true) {
// The browser supports passkeys and the conditional UI.
}
}
在本例中,只有在符合所有條件時,才應顯示「建立新密碼金鑰」按鈕。
從後端擷取資訊
使用者點選按鈕時,請從後端擷取必要資訊,然後呼叫 navigator.credentials.create()。
下列程式碼片段顯示 JSON 物件,其中包含呼叫 navigator.credentials.create() 時所需的資訊:
// Example `PublicKeyCredentialCreationOptions` contents
{
challenge: *****,
rp: {
name: "Example",
id: "example.com",
},
user: {
id: *****,
name: "john78",
displayName: "John",
},
pubKeyCredParams: [{
alg: -7, type: "public-key"
},{
alg: -257, type: "public-key"
}],
excludeCredentials: [{
id: *****,
type: 'public-key',
transports: ['internal'],
}],
authenticatorSelection: {
authenticatorAttachment: "platform",
requireResidentKey: true,
}
}
物件中的鍵/值組合包含下列資訊:
challenge: 這個註冊作業的伺服器產生挑戰,格式為 ArrayBuffer。rp.id:RP ID (信賴方 ID),網域和網站可以指定網域或可註冊的後置字串。舉例來說,如果 RP 的來源是https://login.example.com:1337,則 RP ID 可以是login.example.com或example.com。如果 RP ID 指定為example.com,使用者可以在login.example.com或example.com的任何子網域上進行驗證。詳情請參閱「允許在網站間重複使用密碼金鑰,並提出相關來源要求」。rp.name: RP (信賴方) 的名稱。這項功能已在 WebAuthn L3 中淘汰,但為了相容性而納入。user.id: 帳戶建立時產生的 ArrayBuffer 專屬使用者 ID。與可編輯的使用者名稱不同,這項資訊應為永久性質。使用者 ID 可識別帳戶,但不得包含任何個人識別資訊 (PII)。您的系統可能已有使用者 ID,但如有需要,請專為密碼金鑰建立 ID,確保其中不含任何 PII。user.name: 使用者能夠認得的帳戶專屬 ID,例如電子郵件地址或使用者名稱。這會顯示在帳戶選取器中。user.displayName:帳戶的必填名稱,方便使用者辨識。這個名稱可以重複,且可以是使用者選擇的名稱。如果網站沒有適合在此處加入的值,請傳遞空字串。視瀏覽器而定,這可能會顯示在帳戶選取器中。pubKeyCredParams: 指定 RP (信賴方) 支援的公開金鑰演算法。建議設為[{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}]。這會指定支援使用 P-256 的 ECDSA 和 RSA PKCS#1,支援這些項目可提供完整涵蓋範圍。excludeCredentials: 已註冊的憑證 ID 清單。提供已註冊憑證 ID 清單,避免重複註冊同一部裝置。transports成員 (如有提供) 應包含註冊各個憑證時呼叫getTransports()的結果。authenticatorSelection.authenticatorAttachment:如果密碼金鑰建立作業是從密碼升級而來 (例如在登入後進行促銷活動),請將此值設為"platform",並搭配hint: ['client-device']使用。"platform"表示 RP 需要平台驗證器 (嵌入平台裝置的驗證器),且不會提示使用者插入 USB 安全金鑰等。使用者可以更輕鬆地建立密碼金鑰。authenticatorSelection.requireResidentKey:設為布林值true。可探索的憑證 (常駐金鑰) 會將使用者資訊儲存至密碼金鑰,並讓使用者在驗證時選取帳戶。authenticatorSelection.userVerification:指出使用裝置螢幕鎖定進行使用者驗證的結果,可為"required"、"preferred"或"discouraged"。預設值為"preferred",表示驗證器可能會略過使用者驗證。將這個屬性設為"preferred"或省略屬性。
建議您在伺服器上建構物件,使用 Base64URL 編碼 ArrayBuffer,然後從前端擷取物件。這樣一來,您就可以使用 PublicKeyCredential.parseCreationOptionsFromJSON() 解碼酬載,並直接傳遞至 navigator.credentials.create()。
下列程式碼片段說明如何擷取及解碼建立密碼金鑰所需的資訊。
// Fetch an encoded `PubicKeyCredentialCreationOptions` from the server.
const _options = await fetch('/webauthn/registerRequest');
// Deserialize and decode the `PublicKeyCredentialCreationOptions`.
const decoded_options = JSON.parse(_options);
const options = PublicKeyCredential.parseCreationOptionsFromJSON(decoded_options);
...
呼叫 WebAuthn API 建立密碼金鑰
呼叫 navigator.credentials.create() 建立新密碼金鑰。API 會傳回 Promise,等待使用者互動並顯示模式對話方塊。
// Invoke WebAuthn to create a passkey.
const credential = await navigator.credentials.create({
publicKey: options
});
將傳回的公開金鑰憑證傳送至後端
使用裝置的螢幕鎖定驗證使用者後,系統會建立密碼金鑰並解析 Promise,將 PublicKeyCredential 物件傳回前端。
承諾可能會因各種原因遭到拒絕。您可以檢查 Error 物件的 name 屬性,處理這些錯誤:
InvalidStateError:裝置上已有密碼金鑰。系統不會向使用者顯示任何錯誤對話方塊。網站不應將此視為錯誤。使用者希望註冊本機裝置,且裝置已註冊。NotAllowedError:使用者已取消作業。AbortError:作業已取消。- 其他例外狀況:發生未預期的錯誤。瀏覽器會向使用者顯示錯誤對話方塊。
公開金鑰憑證物件包含下列屬性:
id: 所建立密碼金鑰的 Base64URL 編碼 ID。這個 ID 可在驗證時,協助瀏覽器判斷裝置上是否有相符的密碼金鑰。這個值必須儲存在後端資料庫中。rawId:憑證 ID 的 ArrayBuffer 版本。response.clientDataJSON:編碼的用戶端資料 ArrayBuffer。response.attestationObject: 以 ArrayBuffer 編碼的認證物件。當中包含重要資訊,例如 RP ID、旗標和公開金鑰。authenticatorAttachment:如果是在支援密碼金鑰的裝置上建立這項憑證,則會傳回"platform"。type:這個欄位一律設為"public-key"。
使用 .toJSON() 方法編碼物件,然後使用 JSON.stringify() 序列化物件,並傳送至伺服器。
...
// Encode and serialize the `PublicKeyCredential`.
const _result = credential.toJSON();
const result = JSON.stringify(_result);
// Encode and send the credential to the server for verification.
const response = await fetch('/webauthn/registerResponse', {
method: 'post',
credentials: 'same-origin',
body: result
});
...
儲存憑證
在後端收到公開金鑰憑證後,建議您使用伺服器端程式庫或解決方案,而非自行編寫程式碼來處理公開金鑰憑證。
然後將從憑證擷取的資訊儲存至資料庫,以供日後使用。
以下列出建議儲存的屬性:
- 憑證 ID: 與公開金鑰憑證一併傳回的憑證 ID。
- 認證名稱:認證的名稱。根據 AAGUID 識別密碼金鑰提供者,並以該提供者命名。
- 使用者 ID:用於建立密碼金鑰的使用者 ID。
- 公開金鑰:公開金鑰憑證傳回的公開金鑰。這是驗證密碼金鑰聲明的必要條件。
- 建立日期和時間:記錄建立密碼金鑰的日期和時間。這有助於識別密碼金鑰。
- 上次使用日期和時間:記錄使用者上次使用密碼金鑰登入的日期和時間。這項資訊有助於判斷使用者是否已使用密碼金鑰。
- AAGUID: 密碼金鑰供應商的專屬 ID。
- 備份資格旗標:如果裝置符合密碼金鑰同步資格,則為 true。這項資訊可協助使用者在密碼金鑰管理頁面上,識別可同步處理的密碼金鑰,以及與裝置綁定 (無法同步處理) 的密碼金鑰。
如需更詳細的指示,請參閱「伺服器端密碼金鑰註冊」一文。
註冊失敗時發出信號
如果密碼金鑰註冊失敗,可能會讓使用者感到困惑。如果密碼金鑰提供者有密碼金鑰,且使用者可以存取,但相關聯的公開金鑰未儲存在伺服器端,使用密碼金鑰登入的嘗試永遠不會成功,且難以進行疑難排解。請務必告知使用者。
為避免發生這種情況,您可以使用 Signal API 向密碼金鑰提供者發出不明密碼金鑰信號。RP 可以使用 RP ID 和憑證 ID 呼叫 PublicKeyCredential.signalUnknownCredential(),通知密碼金鑰提供者指定的憑證已移除或不存在。密碼金鑰供應商可自行決定如何處理這項信號,但如果支援這項功能,系統應會移除相關聯的密碼金鑰。
// Detect authentication failure due to lack of the credential
if (response.status === 404) {
// Feature detection
if (PublicKeyCredential.signalUnknownCredential) {
await PublicKeyCredential.signalUnknownCredential({
rpId: "example.com",
credentialId: "vI0qOggiE3OT01ZRWBYz5l4MEgU0c7PmAA" // base64url encoded credential ID
});
} else {
// Encourage the user to delete the passkey from the password manager nevertheless.
...
}
}
如要進一步瞭解 Signal API,請參閱「使用 Signal API 讓密碼金鑰與伺服器上的憑證保持一致」。
傳送通知給使用者
在密碼金鑰註冊時傳送通知 (例如電子郵件),有助於使用者偵測未經授權的帳戶存取行為。如果攻擊者在使用者不知情的情況下建立密碼金鑰,即使密碼變更,密碼金鑰仍可供日後濫用。這類通知會提醒使用者,並協助避免發生這種情況。
檢查清單
- 先驗證使用者身分 (建議使用電子郵件或安全方法),再允許對方建立密碼金鑰。
- 使用
excludeCredentials防止為同一密碼金鑰供應商建立重複的密碼金鑰。 - 儲存 AAGUID,以識別密碼金鑰供應商,並為使用者命名憑證。
- 如果嘗試使用
PublicKeyCredential.signalUnknownCredential()註冊密碼金鑰失敗,請發出信號。 - 為使用者的帳戶建立及註冊密碼金鑰後,傳送通知給使用者。
資源
後續步驟: 透過表單自動填入功能使用密碼金鑰登入。