Web 應用身份驗證的未來?WebAuth 介紹

你的密碼安全嗎

如何判斷自己的密碼已經泄露?

一些安全廠家會定期收集互聯網上被拖庫的相關數據賬號信息,我使用了 Avast 提供的一個工具來查詢自己的密碼是否被泄露(https://www.avast.com/hackcheck/) ,這裏只需要輸入你的郵箱地址,就能查詢到你關聯地址的郵箱的登陸賬號或者密碼是否被泄漏。我在這裏輸入了我之前比較常用的 QQ 郵箱賬號,可以發現我的密碼已經遭到泄漏,泄露的來源分別是 Adobe、CSDN 和京東。其中 Adobe 和京東泄露的是加密後的密碼,因此還算可控,CSDN 泄露的是明文密碼,所以非常危險。

如果你使用了 Chrome 瀏覽器保存密碼,在 Chrome 的安全設置中,也有提醒你密碼已經被泄露的選項。勾選這個選項,在你的密碼被泄露時就會給你發出警告。

除此之外,還有一些黑灰產的途徑可以查詢到自己的密碼和個人隱私信息是否被泄露(如 Telegram 社工庫之類的途徑),因爲涉及到相關法律問題,因此不仔細分享。但是可以得到的結論是:我們的密碼並不安全。

爲什麼你的密碼可能不安全

密碼是如何存儲的

通常情況下,密碼都應該在數據庫中被加密存儲,使用明文存儲密碼是開發信息系統的大忌,但是事實上,能夠做到這一點的信息平臺開發商都不多,對於一些 “小作坊式” 開發的廠家,密碼很多情況下都是明文保存的。

對於密碼的加密,通常會採用一些 Hash 算法,在註冊賬戶時,對賬號的密碼進行 Hash 計算後,再進行存儲,以 MD5 算法爲例:

MD5 ("password") = 5f4dcc3b5aa765d61d8327deb882cf99

若用戶的密碼爲 password,在 Hash 計算後,得到的值爲 5f4dcc3b5aa765d61d8327deb882cf99,在進行存儲時,將得到的 Hash 值存入數據庫的密碼字段。

在用戶登陸時,只要將用戶密碼的輸入再做一次 MD5 計算,將得到的 Hash 值與數據庫存儲的值進行比較,就可以判斷用戶的密碼輸入是否正確了。

這樣做的好處是,即使數據庫中用戶密碼的相關信息字段被泄露,用戶真正的密碼明文是不知道的。這其中利用到了 Hash 算法的不可逆性:在有限算力的情況下,Hash 算法只能從輸入得到輸出,無法從輸出反推得到輸入。因爲這樣的特性,Hash 算法通常用在保存密碼,信息和文件完整性校驗等地方。常用的 Hash 算法有 MD5、SHA、HMAC 等。

但是這樣的算法也會存在一些問題,雖然無法從輸出反推得到輸入,但是可以建立一個密碼到其哈希值的字典(即彩虹表),在破解時只需要查詢彩虹表的值,即可得到對應的密碼明文數據。

爲了解決這樣的問題,我們可以在計算哈希值時採用加鹽的方式。對於每個用戶,鹽值都採用隨機數。在計算哈希時,將用戶輸入的密碼和鹽值進行組合之後再進行計算,最後將加鹽的 Hash 值存入密碼字段。如使用用戶的隨機 UID32142 作爲鹽值,計算方法爲:

MD5 ("password32142") = 970e6155f135079c9c1b9d3302b957b5

通過隨機加鹽的方式,可以有效地避免彩虹表攻擊造成密碼原文泄露。MD5 是一種比較簡單的 Hash 算法,因此在不推薦在實際使用的時候採用 MD5 算法存儲密碼,通常情況下我們會有一些更適合密碼的哈希算法比如 Bcrypt 算法來對密碼計算 Hash,Bcrypt 算法在生成時就會生成一段隨機的鹽值,並且將鹽值保存在輸出的結果中,這樣的好處是即使是同樣的輸入,得到的輸出也是不同的,能夠有效避免密碼原文泄露的風險。

Bcrypt("password")=10$7ioawRWnYEBZwGE7QAGL0.oa5suk8/NjdAS0RSlqy8Kehplft8CBy

密碼過於簡單

談到密碼的安全性,首先需要強調的是幾條密碼保護的基本規則:

但是實際上對於上述幾點,能夠完美堅持做到每一點的同學我相信一定不多。大家通常使用的密碼都是與自己相關的規律組合,因此存在着一定的隱私泄露的風險。

業務系統存在漏洞

事實上,所有的業務系統都是由人來開發的。但是人並不是機器,因此總是會存在着犯錯的可能性,這是無法避免的,我們只能從流程上來規範以儘量降低發生錯誤的概率。但是就如 Meta/Twitter 這樣的大公司都存在着各種各樣的密碼泄露的問題,國內的 BAT 等公司也都曾爆出過密碼被泄漏的安全問題。除此之外,還有着 XCode Ghost/Docker Hub 數據泄露等供應鏈攻擊,就連流程詳盡規範的大公司都防不勝防,就更不用說你手機上那些不知名小公司的小 App 了。

就連我們自己的業務系統也不例外:在某日排查線上 bug 的時候,我也發現在我們業務使用到的某上游服務中,也有把用戶的密碼直接明文打印在日誌中的行爲。根據公司的數據安全管理規定數據分類分級表,用戶操作行爲日誌等屬於 L3 級別數據 ,用戶賬號的密碼等數據屬於 L4 級別數據,因此在日誌中,不可以出現用戶明文密碼相關的敏感字段。這樣任何擁有 L3 級別日誌查詢權限的用戶,都可以獲取到 L4 級別的保密數據,這樣的操作相當於把 L4 級別的數據範圍擴大到了 L3 級別上,因此是違法相關數據保護規定的行爲。出現這樣的問題可能是無意爲之,但是還是暴露了我們在代碼安全上的意識還有需要加強的地方。

基礎設施 / 硬件漏洞

對於業務系統產生的一些安全漏洞,我們可以通過一些流程和規範儘量保障,但是對於一些基礎設施和硬件的漏洞,就很難能夠通過這樣方式來預防了,如以下的幾個例子:

上述這樣的一些漏洞都會造成服務端的風險,從而產生被黑灰產拖庫導致用戶密碼泄漏的風險。

如何解決密碼不安全的問題

總的來說,實現絕對的安全是不可能的,所以我們只能在有限的條件下,尋求相對的安全。爲了能夠實現這樣相對的安全,現有的密碼驗證方式矛盾的地方在於:安全等於麻煩,爲了實現賬戶的安全,你需要記憶各種複雜的密碼和驗證方式,要想省事的話就只能使用一些簡單方便記憶的密碼,並且在不同平臺使用相同的密碼,這樣雖然省事了,但是可能會帶來更多的麻煩:密碼泄露導致賬戶被盜造成個人隱私和財產的損失。

怎麼解決安全和麻煩這樣的問題呢,FIDO(Fast IDentity Online)聯盟想出了比較完美的解決方案:

既然密碼不安全,那麼沒有密碼不就可以解決密碼不安全的問題了嗎?

FIDO 聯盟和 W3C 爲了解決這樣的魚與熊掌的問題,希望可以制定相關的技術規範,定義一套開放的、可擴展的、能互用的機制,減少用戶 在認證時對密碼的依賴,於是 WebAuthn 應運而生。

WebAuthn 是什麼

WebAuthn 全稱爲 Web Authentication,是一個使用非對稱加密的方式,在 Web 應用上替代密碼 / 短信驗證碼等方式在 Web 應用中進行註冊、登陸、雙重認證(2FA) 的 API。能夠解決釣魚攻擊、數據泄漏的安全問題,同時也能提高應用的用戶體驗(不必記憶複雜的密碼)。

通過使用 WebAuthn 的 API,我們可以方便實現指紋、人臉等生物信息的認證或者是加密硬件如 USB Key(如網銀使用的 U 盾)、藍牙設備等來驗證身份信息,在方便的同時能夠保障安全和隱私。

兼容性

從 CanIUse 中可以看到,Web Authentication 在桌面端瀏覽器的兼容性較好,絕大多數的桌面端瀏覽器都能支持,但是在移動端的兼容性較差:iOS 僅在 14.5 以上的版本完全支持,安卓支持 Android5 以上版本,除此之外,由於依賴 TPM 模塊和指紋 / 人臉識別等傳感器,硬件配置也會影響 WebAuthn 的可用性。

需要注意的是,WebAuthn 只能在 HTTPS 或者 localhost 中使用,不支持 HTTP 環境,即只有在保證當前的環境是安全的前提下,WebAuthn 的這些實現纔是有意義的。

一些概念

2FA

2FA 的全稱爲 2 Factor Authentication,即雙因子認證,通常是結合密碼以及其他標誌(如 OTP、USB Key、短信驗證碼、指紋等生物特徵)進行認證的方式。現在 2FA 的使用已經比較普遍,許多廠家會結合風控系統在密碼驗證的同時結合其他認證方式,保證授權是可信的。比如說在新的設備上登錄社交 App 時,通常除了密碼之外,還會需要其他的認證方式。

OTP

OTP 即一次性密碼(One-time password),OTP 的計算通常是基於時間戳的,密碼的生成具有有效期(通常是 30s)。使用 OTP 能夠有效地避免 “重放攻擊”,一般的靜態密碼,在使用時如果設備上有可以監聽鍵盤輸入的木馬等程序,很容易造成密碼的泄露。OTP 解決了這樣的問題,即保證了泄露的密碼也是沒有用的(因爲只能使用一次,且在極短時間內有效)。通常會在安全性要求比較高的情況下使用:如網遊賬號(左圖,盛大密寶,我小時候玩盛大遊戲時,使用到的 OTP 密碼器)或企業認證(右圖,Seal VPN)。OTP 的缺點在於每次都要打開對應的軟件或者相關的 OTP 設備來獲取一次性密碼,使用的便捷程度上存在一定的不便。

因爲 OTP 的計算是基於時間戳的,之前我有遇到過一種情況,OTP 怎麼輸入都不對,後來發現是我本地設備的 NTP 存在問題,沒有和 NTP Server 進行同步導致客戶端獲取到的時間不對,因此計算得到的 OTP 也不對。如果下次大家有遇到類似的情況,記得先檢查一下本地設備的時間是否正確。

對稱加密

對稱加密算法的特點是,在加密和解密的過程中,使用的密鑰是相同的,因此通訊的雙方需要交換並持有相同的密鑰。常見的對稱加密算法有 AES/DES 等。

優點:

缺點:

非對稱加密

和對稱加密不同的是,在非對稱加密中,需要一組密鑰對來進行加密和解密。這一組密鑰對我們稱爲公鑰(可以公開的密鑰)和私鑰(需要保密的密鑰)。除了在 WebAuthn 中有用到之外,其他地方也非常常見,比如說 JWT/HTTPS(HTTPS 既有非對稱加密,也有對稱加密)/SSH 等。非對稱加密有這樣的一些特點:

常用的非對稱加密算法有 RSA/ECC 等。

優點

缺點

WebAuthn

術語

工作原理

FIDO 協議原理

我們在這裏所指的 FIDO 協議,通常指 UAF/U2F/FIDO2 這三種協議中的任意一種,因爲這三種協議的工作原理都是類似的,區別只在於結構的不同。FIDO 要實現在沒有其他信息的情況下證明使用者的身份,需要引入一種零知識證明的方式來解決。FIDO 基於一種挑戰應答方式(Challenge-Response)機制的方式進行工作:每次認證時,服務端給客戶端發送一個隨機的挑戰字符串,客戶端接收到這個挑戰後,做出相對應的應答。其工作流程如下:

依賴方(服務端)向客戶端發送 challenge 和憑證 ID,客戶端再附加依賴方的信息將其發送給身份認證器。身份認證器會首先檢查用戶是否存在,然後通過彈窗等請求用戶按下按鈕或者使用指紋傳感器等進行完整的用戶身份的認證。驗證完成後,身份認證器使用憑證 ID 所標識的對應私鑰對 challenge 進行加密,再將斷言返回給客戶端,客戶端將相關身份認證器的提供信息轉發給依賴方。然後依賴方會檢查返回的信息,確保響應包含符合預期的來源和 challenge,然後用服務端已經存儲的公鑰來驗證簽名。這個工作流中的任何一個環節失敗,即說明存在釣魚攻擊等場景,認證就會失敗。

Challenge-Response 的認證方式使用非常廣泛,很多協議如 Kerberos/SSH 也採用了類似的方式。

過程

WebAuthn 使用的工作流一般分爲兩種:註冊(Registration)和驗證(Authentication),註冊用來給系統添加用戶相關的身份認證信息,驗證用來校驗和檢查用戶的身份是否正確。

註冊

註冊的流程如下:

0⃣️ 瀏覽器向依賴方發送註冊請求:

如點擊一個 Button 開始註冊用戶,這裏的協議和格式都不在 WebAuthn 的標準範圍之內,可以由客戶端自行實現

  1. 依賴方向瀏覽器發送挑戰、依賴方信息和用戶信息:

這裏的協議和格式也不在 WebAuthn 的標準範圍內,可以是基於 HTTPS 的 XMLHttpRequest/Fetch 實現,也可以使用任意其他協議(如 ProtoBuf),只要客戶端能夠和服務端發送對應的請求消息即可,但是需要保證環境是安全的(HTTPS)

  1. 瀏覽器向認證器發送用戶信息、依賴方信息和客戶端信息的 Hash 值

在瀏覽器內部,瀏覽器將驗證參數並用默認值補全缺少的參數,然後這些參數會變爲 clientDataJSON。調用 create() 的參數與 clientDataJSON 的 SHA-256 哈希一起傳遞到認證器(只有哈希被髮送是因爲與認證器的連接可能是低帶寬的 NFC 或藍牙連接,之後認證器只需對哈希簽名以確保它不會被篡改)

  1. 認證器請求用戶確認,然後創建一對公私鑰,用私鑰創建證明

在進行這一步時,認證器通常會以某種形式要求用戶確認,如輸入 PIN、使用指紋、人臉掃描等,以證明用戶在場並同意註冊。之後,認證器將創建一個新的非對稱密鑰對,並安全地存儲私鑰以供將來驗證使用。公鑰則將成爲證明的一部分,被在製作過程中燒錄於認證器內的私鑰進行簽名。這個私鑰會具有可以被驗證的證書鏈。

  1. 認證器把簽名和公鑰以及憑證 ID 發送給瀏覽器

新的公鑰、全局唯一的憑證 ID 和其他的證明數據會被返回到瀏覽器,成爲 attestationObject。

  1. 瀏覽器將證明數據和客戶端信息發送給依賴方

這裏和前面一樣,可以使用任意的格式或協議,前提是需要保證環境安全

  1. 依賴方用公鑰驗證發送的 challenge,如果成功則將對應的公鑰與用戶綁定存儲在數據庫中,完成註冊流程

在這一步,服務器需要執行一系列檢查以確保註冊完成且數據未被篡改。步驟包括:

驗證

在驗證中,瀏覽器向依賴方發送某個用戶的驗證請求後的行爲如下:

1. 依賴方向瀏覽器發送挑戰

2. 瀏覽器向認證器發送依賴方 ID、挑戰和客戶端信息的 Hash 值,

3. 認證器請求用戶確認,然後通過依賴方 ID 找到對應的私鑰,使用私鑰簽名挑戰(斷言)

4. 認證器將斷言信息和簽名後發送給瀏覽器

5. 瀏覽器將簽名、驗證器數據以及客戶端信息發送給服務器

  1. 服務器使用用戶綁定的公鑰驗證挑戰是否與發送的一致,如果驗證通過則表示認證成功

區別

註冊和驗證之間的主要區別在於:

瀏覽器 API

要使用 WebAuthn,需要先了解 Credentials Management API,因爲 Web Authn API 繼承自 Credentials Management API。Credentials Management API 允許網站與用戶代理的密碼系統進行交互,以便網站能夠以統一的方式處理站點憑證,而用戶代理爲憑證管理提供更好的幫助。通常用來存儲和檢索用戶、聯合賬戶憑證(FederatedCredential,如 OpenID Connect)、和非對稱密鑰對憑證(WebAuthn 使用的就是非對稱密鑰對的憑證)。

通過這個 API,使用 publicKey 選項時, 創建一個新的憑據,無論是用於註冊新賬號還是將新的非對稱密鑰憑據與已有的賬號關聯。其中 options 的結構如下:

const options = {
  publicKey: {
    rp: {
        id: "example.com",
        name: "example.com",
    },
    user: {
      name: "username@example.com",
      id: userIdBuffer,
      displayName: "User Name",
    },
    pubKeyCredParams: [
        { type: "public-key", alg: -7 }
    ],
    challenge: challengeBuffer, 
    authenticatorSelection: { authenticatorAttachment: "platform" },
  },
}

對於 pubKeyCredParams,通常我們只需添加 ES256 (alg: -7) 算法即可兼容大部分外部認證器,此外,再添加 RS256 (alg: -257) 算法即可兼容大部分平臺內置認證器(如 Windows Hello/TouchID)。前端添加算法之後,後端也需要相應的算法支持進行簽名校驗。

const publicKeyCredential = await navigator.credentials.create(options);

調用完創建憑證的 API 後,瀏覽器會彈出一個認證器的選擇彈窗(具體取決於 authenticatorAttachment 這個字段的設置,如果指定了可能就不會有認證器選擇彈窗的這個環節),在這個彈窗中,用戶可以選擇使用哪個認證器來生成憑證:

以選擇 “本設備” 爲例,會調用系統內置的認證器(在 macOS 上即表現爲使用 Touch ID/Windows 上爲 Windows Hello)進行認證:

使用指紋認證成功後,WebAuthn API 會通過 Promise 返回 PublicKeyCredential 對象,包含其對應的公鑰憑證信息如下:

PublicKeyCredential 的結構如下:

CBOR 是一種提供良好壓縮性,擴展性強,不需要進行版本協商的二進制數據交換形式。

其字段結構定義如下:

認證器應實現簽名計數器功能。這些計數器在概念上認證器爲每個憑據存儲,或爲整個認證器全局存儲。憑據的簽名計數器的初始值在認證器註冊返回的認證器數據的值中指定。對於每個成功的認證器驗證操作,簽名計數器都會遞增一些正值,並且後續值將再次返回到認證器數據中的 WebAuthn 信賴方。簽名計數器的目的是幫助信賴方檢測克隆的認證器。克隆檢測對於保護措施有限的認證器更爲重要。

信賴方存儲最新認證器驗證操作的簽名計數器。(或者來自認證器的計數器註冊操作,如果沒有認證器驗證曾經對憑據執行過。在後續認證器驗證操作中,信賴方將存儲的簽名計數器值與斷言的認證器數據中返回的新值進行比較。如果任一值爲非零,並且新值小於或等於存儲的值,則可能存在克隆的認證器,或者認證器可能出現故障。

{
     "type""webauthn.create" | "webauthn.get"
     "challenge""把服務端傳輸過來的challenge字符串進行base64編碼"
     "origon""https://test.example.com", // 請求源,依賴方域名
     "crossOrigin":  false, //"是否爲跨源調用的信息"
 }

其中最重要的參數之一是 origin,它是 clientData 的一部分,同時服務器將能在稍後驗證它。

實際上在默認情況下,註冊時認證器並不會對挑戰進行簽名(首次使用時信任模型),attestationObject 並不會包含簽名後的挑戰。只有依賴方明確要求證明且用戶同意後認證器纔會對挑戰進行簽名(具體實現據情況會有所不同)。

得到上面的數據後,我們需要把上面的數據回傳給依賴方即後端進行校驗,後端的操作至少如下:

  1. 校驗rpIdHash

2. 檢查 UV 和 UP

3. 存儲證明的計數

4. 存儲公鑰

這個 API 也需要傳入一個 options 對象,options 的結構和註冊的時候比較類似:

const options = {
  publicKey: {
      challenge: challengeBuffer, // 
      rpId: '',
      userVerification: '',
  }
}

和 create 方法一樣,調用 get 這個方法也會返回一個 Promise,可以得到認證器返回 PublicKeyCredential 的對象,結構如下:

認證器的信息和上方的 attestedCredentialData 的數據結構類似,只是這裏不包含公鑰的信息了。

得到這些後,我們只要把 PublicKeyCredential 對象的內容發送給依賴方即服務端驗證即可,依賴方至少要做這樣一些的校驗:

  1. 驗證 rpIdHash

  2. 檢查 UV 和 UP

  3. 驗證簽名的計數,並更新數據庫中籤名的計數

  4. 公鑰校驗簽名

可發現憑據

實現完上述的流程後,我們就可以使用指紋等進行登錄了,但是這時我們還是會需要使用到用戶名,然後才能進行身份認證。如何在不輸入用戶名的情況下進行認證呢?許多驗證器提供了這個功能,我們稱爲可發現憑據(Discoverable Credentials)。

爲什麼普通的 WebAuthn 爲什麼不能實現無用戶名登錄?大部分認證器爲了實現無限對公私鑰,會將私鑰通過加密後包含在憑證 ID 中發送給依賴方,這樣認證器本身就不用存儲任何信息。不過,這就導致需要身份認證時,依賴方必須通過用戶名找到對應的憑證 ID,將其發送給認證器以供其算出私鑰。要不輸入用戶名,則需要認證器將私鑰在自己的存儲中也存儲一份。這樣,依賴方無需提供憑證 ID,認證器就可以通過依賴方 ID 找到所需的私鑰並簽名。因此這個特性需要認證器能夠儲存用戶 ID,即上面的 userHandle 字段。

爲了實現安全隔離,認證器的存儲通常會和平臺隔離,擁有獨立的存儲空間(如在 Mac 平臺下,TouchID 有獨立的安全芯片 T1/T2,運行獨立的操作系統 BridgeOS),一般情況下認證器能夠永久存儲的私鑰數量是有限的,所以只有在真正需要需要無用戶名登錄時才啓用這個特性

實現:

const options = {
    publicKey: {
        ...options.publicKey,
        authenticatorSelection: {
            ...options.publicKey.authenticatorSelection,
            residentKey: "required",     // 設置爲required
            userVerification: "required" // 設置爲required
        }
    }
}
const options = {
    publicKey: {
        ...options.publicKey,
        userVerification: "required", // 設置爲required
        allowCredentials: [],         // 設置爲空
    }
}

參考資料

[1] MDN: Web Authentication API

https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Authentication_API

[2] W3C 文檔:Web Authentication API Spec

https://www.w3.org/TR/webauthn-3/

[3] WebKit 文檔:Meet Face ID and Touch ID for the web

https://webkit.org/blog/11312/meet-face-id-and-touch-id-for-the-web/

[4] Medium: Introduction to WebAuthn API

https://medium.com/webauthnworks/introduction-to-webauthn-api-5fd1fb46c285

[5] 談談 WebAuthn

https://flyhigher.top/develop/2160.html

[6] WebAuthn 介紹與使用

https://obeta.me/posts/2019-03-01/WebAuthn%E4%BB%8B%E7%BB%8D%E4%B8%8E%E4%BD%BF%E7%94%A8

[7] WebAuthn Guide

https://webauthn.io/

[8] Apple 安全隔離介紹

https://support.apple.com/zh-cn/guide/security/sec59b0b31ff/web

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/6jkU_hXA8PZT5ism58q9mA