Web身份验证(WebAuthn)
目錄
1、WebAuthn簡介
2、FIDO2:客戶端到驗證器協議(CTAP)
3、瀏覽器和平臺
4、Web身份驗證API 基本流程
5、使用 WebAuthn API
5.1 注冊WebAuthn憑據
5.2使用WebAuthn憑據進行身份驗證
6、 WebAuthn演示(注冊和認證)
6.1 注冊演示:
6.2 認證演示:
6.3 瀏覽器中存儲
1、WebAuthn簡介
WebAuthn?是FIDO聯盟FIDO2規范集的核心組件,是一種基于Web的API,允許網站更新其登錄頁面,以便在支持的瀏覽器和平臺上添加基于FIDO的身份驗證。FIDO2使用戶能夠利用常見設備輕松地在移動的和桌面環境中對在線服務進行身份驗證。
Web服務和應用程序可以-并且應該-打開此功能,通過生物識別技術,移動的設備和/或FIDO安全密鑰為用戶提供更輕松的登錄體驗-并且比單獨的密碼具有更高的安全性。
FIDO聯盟決定與萬維網國際標準組織萬維網聯盟(W3C)合作,為整個Web平臺標準化FIDO身份驗證。這種標準化將通過支持該標準的Web瀏覽器和Web應用服務器的整個社區來發展FIDO生態系統。
它允許服務器與現在內置于設備中的強身份驗證器集成,如Windows Hello或Apple的Touch ID。為網站創建私鑰-公鑰對(稱為?憑據?)而不是密碼。私鑰安全地存儲在用戶的設備上;公鑰和隨機生成的憑據 ID 將發送到服務器進行存儲。然后,服務器可以使用該公鑰來證明用戶的身份。
公鑰不是秘密的,因為如果沒有相應的私鑰,它實際上是無用的。服務器未收到任何機密這一事實對用戶和組織的安全性具有深遠的影響。數據庫對黑客不再那么有吸引力,因為公鑰對他們沒有用。
FIDO聯盟成員公司于2015年將FIDO規范提交給W3C進行正式標準化。然后,他們在W3C內部工作,最終確定了API,這被稱為Web身份驗證或WebAuthn。WebAuthn于2019年3月被正式認可為W3C Web標準。如今WebAuthn是FIDO聯盟FIDO2規范的一部分,FIDO聯盟運行認證程序以確保合規性。
什么是公鑰密碼?
公鑰密碼學發明于20世紀70年代,是解決共享秘密問題的一種方法。它是現代互聯網安全的支柱;例如,每次我們連接到HTTPS網站時,都會發生公鑰交易。
公鑰加密使用密鑰對的概念;一個私鑰由用戶安全存儲,一個公鑰可以與服務器共享。這些“密鑰”是彼此具有數學關系的長隨機數。
2、FIDO2:客戶端到驗證器協議(CTAP)
FIDO2的另一個組件,客戶端到認證者協議(CTAP)
,是WebAuthn的補充。它使外部身份驗證器(如安全密鑰或移動的電話)能夠與支持WebAuthn的瀏覽器一起使用,并且還可以用作桌面應用程序和Web服務的身份驗證器。
3、瀏覽器和平臺
WebAuthn目前在Google?Chrome
、Mozilla? Firefox、Microsoft?Edge和Apple?Safari
?Web瀏覽器以及Windows?10和Android
平臺中得到支持。
瀏覽器兼容性如下:
4、Web身份驗證API 基本流程
本節規范性地指定了用于創建和使用公鑰憑據的API?;镜?這個想法是憑證屬于用戶并由WebAuthn Authenticator管理,WebAuthn依賴方通過客戶端平臺與WebAuthn Authenticator交互。?依賴方腳本可以(在用戶同意的情況下)請求 瀏覽器以創建新憑證供信賴方將來使用。參見下圖:
注冊流程:
1、依賴方服務端開始準備用戶信息,以及依賴方信息,這些屬于公鑰憑證創建相關內容選項。?
2、把對應信息在依賴房應用程序通過WebAuthnAPI,生成依賴方id,用戶信息,依賴方信息,客戶數據哈希,把這些信息發送身份驗證器進行驗證。
3、身份驗證器開始驗證用戶信息,會生成新的密鑰對,和對應認證。
4、身份驗證器,把新的公鑰和認證id等相關認證信息返回。
5、經瀏覽器和依賴方js應用返回客戶JOSN數據,認證對象信息,這一步是驗證器認證響應。
6、服務端進行驗證。
腳本還可以請求用戶的權限,以便使用現有憑據執行身份驗證操作。參見下圖1、依賴方服務端發起詢問,需要公鑰認證請求需要的參數。
?
?身份驗證流程:
1、依賴方服務端發起詢問,需要公鑰認證請求需要的參數。
2、經依賴方JS應用,瀏覽器得到依賴方id,客戶對應的哈希數據發送給身份驗證器。
3、身份驗證器開始驗證用戶信息,開始創建認證信息。
4、驗證數據簽名返回給瀏覽器。
5、經依賴方返回客戶端的json數據,驗證數據,簽名等信息。
6、依賴方服務端
5、使用 WebAuthn API
5.1 注冊WebAuthn憑據
在基于密碼的用戶注冊流程中,服務器通常會向用戶呈現一個表單,詢問用戶名和密碼。密碼將被發送到服務器進行存儲。
在WebAuthn中,服務器必須提供將用戶綁定到憑證(私鑰-公鑰對)的數據;該數據包括用戶和組織(也稱為“依賴方”)的標識符。然后,網站將使用Web身份驗證API來提示用戶創建新的密鑰對。需要注意的是,我們需要從服務器隨機生成的字符串作為挑戰,以防止重放攻擊。
?
navigator.credentials.create()
服務器將通過調用客戶機上的navigator.credentials.create()開始創建新的憑據。
navigator.credentials.create({ publicKeyCredentialCreationOptions }).then(function (newCredentialInfo) {// 發送新的憑證信息到服務器進行驗證和注冊.}).catch(function (err) {// 沒有可接受的身份驗證者或用戶拒絕同意。再做對應處理。}); publicKeyCredentialCreationOptions 對象包含許多必需和可選字段,服務器指定這些字段為用戶創建新憑證。 const publicKeyCredentialCreationOptions = {// 在服務器上生成的加密隨機字節的緩沖區,并且需要防止“重放攻擊”。challenge: bufferToBase64URLString(utf8StringToBuffer('sd')),// 服務器希望從認證器接收證明數據(direct)attestation: 'direct',// 這是一個對象數組,描述服務器可以接受哪些公鑰類型// -7表示服務器接受使用SHA-256簽名算法的橢圓曲線公鑰pubKeyCredParams: [{alg: -7,type: 'public-key',},],// 依賴方的id,是瀏覽器當前域名的子集rp: {id: 'duosecurity.dev',name: 'duosecurity',},// 用戶信息user: {id: '5678',displayName: 'username',name: 'username',},timeout: 1000, // 希望限制在單個身份驗證器上為同一帳戶創建多個憑據的依賴方使用。excludeCredentials: [{id: 'C0VGlvYFratUdAV1iCw-ULpUW8E-exHPXQChBfyVeJZCMfjMFcwDmOFgoMUz39LoMtCJUBW8WPlLkGT6q8qTCg',type: 'public-key',transports: ['internal'],},], };返回的create()對象是一個包含公鑰和用于驗證注冊事件的其他屬性的對象。
PublicKeyCredential {// 新生成的憑證的ID;它將用于在認證用戶時標識憑證。ID是一個base64編碼的字符串id: 'ADSUllKQmbqdGtpu4sjseh4cg2TxSvrbcHDTBsv4NSSX9...',// 屬性返回包含內部槽中的標識符rawId: ArrayBuffer(59),response: AuthenticatorAttestationResponse {// 這表示從瀏覽器傳遞到身份驗證器的數據,以便將新憑據與服務器和瀏覽器相關聯。// 驗證器將其作為UTF-8字節數組提供。clientDataJSON: ArrayBuffer(121),// 此對象包含憑證公鑰、可選的證明證書和其他用于驗證注冊事件的元數據。// 它是以CBOR編碼的二進制數據。attestationObject: ArrayBuffer(306),},type: 'public-key' }解析clientDataJSON數據:
// decode the clientDataJSON into a utf-8 string const utf8Decoder = new TextDecoder('utf-8'); const decodedClientData = utf8Decoder.decode(credential.response.clientDataJSON)// parse the string as an object const clientDataObj = JSON.parse(decodedClientData);console.log(clientDataObj){// 這是傳遞到create()調用中的challenge是否相同challenge: "p5aV2uHXr0AOqUk7HQitvi-Ny1....",// 服務器必須驗證這個“origin”字符串與應用程序的源代碼匹配。origin: "https://webauthn.guide",// 服務器驗證該字符串是不是"webauthn.create",否則可能執行不正確的操作。type: "webauthn.create",// 客戶端和callerOrigin之間的令牌綁定狀態tokenBinding: { // 如果缺失,表示客戶端不支持令牌的綁定// status存在,則該成員必須存在,// 并且必須是與依賴方通信時使用的令牌綁定ID的base64url編碼id:'ASAFDQWE12312aasDASD......';// supported 支持令牌綁定,但與依賴方通信時未協商// present 與依賴方通信時使用了令牌綁定,id必須存在status: 'present' | 'supported'';}}解析attestationObject:
// note: a CBOR decoder library is needed here. const decodedAttestationObj = CBOR.decode(credential.response.attestationObject);console.log(decodedAttestationObject); {// 這里的authenticator數據是一個字節數組,包含有關注冊事件的元數據,// 以及我們將用于未來身份驗證的公鑰。authData: Uint8Array(196),fmt: "fido-u2f", // 表示證明格式attStmt: {sig: Uint8Array(70), // 簽名x5c: Array(1), // X.509 格式編碼的證明證書。alg: -7 // -7 使用SHA-256對應的哈希算法的摘要}, }解析驗證器數據:
const {authData} = decodedAttestationObject;// get the length of the credential ID const dataView = new DataView(new ArrayBuffer(2)); const idLenBytes = authData.slice(53, 55); idLenBytes.forEach((value, index) => dataView.setUint8(index, value)); const credentialIdLength = dataView.getUint16();// get the credential ID const credentialId = authData.slice(55, 55 + credentialIdLength);// get the public key object const publicKeyBytes = authData.slice(55 + credentialIdLength);// the publicKeyBytes are encoded again as CBOR const publicKeyObject = CBOR.decode(publicKeyBytes.buffer); console.log(publicKeyObject){1: 2,3: -7,-1: 1,-2: Uint8Array(32) ...-3: Uint8Array(32) ... }authData是規范中描述的字節數組。解析它將涉及從數組中切片字節并將它們轉換為可用的對象。
最后檢索到的publicKeyObject是一個以COSE標準編碼的對象,這是一種描述憑證公鑰和使用它所需的元數據的簡潔方式。
1:?1字段描述密鑰類型。值2表示關鍵點類型為橢圓曲線格式。
3:?3字段描述用于生成身份驗證簽名的算法。-7值指示此身份驗證器將使用ES256。
-1:?-1字段描述此鍵的“曲線類型”。值1指示該鍵使用“P-256”曲線。
-2:?-2字段描述此公鑰的x坐標。
-3:?-3字段描述此公鑰的y坐標。
5.2使用WebAuthn憑據進行身份驗證
注冊完成后,現在可以對用戶進行身份驗證。在認證期間,創建斷言,這是用戶擁有私鑰的證明。此斷言包含使用私鑰創建的簽名。服務器使用在注冊期間檢索到的公鑰來驗證此簽名。
navigator.credentials.get()?
在身份驗證過程中,用戶證明他們擁有他們注冊的私鑰。 它們通過提供一個assertion來實現,是在客戶端上調用navigator.credentials.get()生成的。這將檢索在注冊期間生成的包含簽名的憑證。
const credential = await navigator.credentials.get({publicKey: publicKeyCredentialRequestOptions });publicKeyCredentialCreationOptions對象包含許多必需和可選字段,服務器指定這些字段為用戶創建新憑證。
const publicKeyCredentialRequestOptions = {challenge: Uint8Array.from(randomStringFromServer, c => c.charCodeAt(0)),// 這個數組告訴瀏覽器服務器希望用戶使用哪些憑據進行身份驗證。allowCredentials: [{id: Uint8Array.from(credentialId, c => c.charCodeAt(0)),type: 'public-key',transports: ['usb', 'ble', 'nfc'], // 傳輸方式 USB、藍牙,NFC}],timeout: 60000, }const assertion = await navigator.credentials.get({publicKey: publicKeyCredentialRequestOptions });從assertion調用返回的get()對象再次是PublicKeyCredential對象。它與我們在注冊時收到的對象略有不同;特別是它包括signature成員,并且不包括公鑰。
console.log(assertion);PublicKeyCredential {id: 'ADSUllKQmbqdGtpu4sjseh4cg2TxSvrbcHDTBsv4NSSX9...',rawId: ArrayBuffer(59),response: AuthenticatorAssertionResponse {// 認證器數據類似于注冊期間接收的authData,但值得注意的是,這里不包括公鑰。authenticatorData: ArrayBuffer(191), clientDataJSON: ArrayBuffer(118), // 用作簽名的一些數據signature: ArrayBuffer(70), // 私鑰簽名userHandle: ArrayBuffer(10),},type: 'public-key' }解析和驗證身份驗證數據:
獲得認證后,將其發送到服務器進行驗證。在驗證數據完全有效之后,使用在注冊期間存儲在數據庫中的公鑰來驗證簽名。
const storedCredential = await getCredentialFromDatabase(userHandle, credentialId);const signedData = (authenticatorDataBytes +hashedClientDataJSON);const signatureIsValid = storedCredential.publicKey.verify(signature, signedData);if (signatureIsValid) {return "簽名校驗成功! 🎉"; } else {return "簽名校驗失敗. 😭" }6、 WebAuthn演示(注冊和認證)
?有關WebAuthn的演示,請訪問https//webauthn.io/https://webauthn.io/
6.1 注冊演示:
用Chrome 瀏覽器進行測試,第一步:輸入用戶名(zj),單擊“Register” 按鈕。用電腦測試,需打開藍牙。
?
然后輸入電腦密碼,則提示驗證成功!?
6.2 認證演示:
1、輸入用戶名,點擊“”按鈕,進行認證,登錄成功,如下所示:
6.3 瀏覽器中存儲
在Chrome瀏覽器中,點擊“設置”->“自動填充”->“密碼管理”可以找到對應的通用密鑰,如下圖所示:
總結
以上是生活随笔為你收集整理的Web身份验证(WebAuthn)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 内存管理Memoryamp;nbsp;O
- 下一篇: 示波器基本原理之六:示波器的基本控制