隨著互聯網在生活方方面面的應用,日常少不了要登錄各個網站或者應用、或者是銀行轉賬等需要驗證自己身份的場景。從早期的輸入賬號密碼來登錄,到后來普遍開始通過手機驗證碼進行登錄、或者APP掃碼進行登錄,身份校驗的操作方式經歷了一輪又一輪的迭代演進。
近年來,有越來越多的網站開始推廣并引導用戶啟用所謂的2FA雙重登錄驗證,比如Github就早在2022年的時候就開始引導用戶啟用2FA,近期還發出警告若用戶在2024年10月22日前未啟用2FA將被限制部分功能。而科技巨頭Apple在最近發布的IOS18中,也亮相了全新的密碼APP并提供對于2FA場景的臨時鑒權碼(IOS中稱為驗證碼
)生成的能力支持。

這個2FA究竟是何方神圣?為什么越發得到各大公司的青睞、甚至是Apple也要親自入局?本篇文章我們就來一探究竟。
初識2FA
所謂2FA,也即雙因素認證(Two-Factor Authentication
,簡稱2FA),是一種身份校驗的策略。顧名思義,所謂雙因素認證,就是你需要同時提供2方面的證據、來證明你就是你。
想要驗證一個人是否為本人,用戶提供的證據(認證因素)大體可分為3類:
分類 | 說明 | 舉例 |
---|
私有秘密 | 一個私密的信息,僅本人可知曉的內容,知曉此內容即認為是合法的目標身份 | 最為常見,比如密碼、密鑰等 |
生理特征 | 基于人的各種獨一無二的生理特征,來判斷并確定是否為本人 | 比如人臉、指紋、聲紋、虹膜等 |
專有物件 | 一個私人專屬的物理設備或物件,持有此物件的人就是本人 | 身份證、手機、U盾等 |
任一認證因素都可以作為個人身份的認證,但又都有各自的缺點,無法做到100%可靠精準。
使用賬號密碼登錄: 存在密碼泄露的風險。尤其是很多人喜歡所有賬號都同一個密碼,一旦泄露,后果不堪設想。
使用人臉識別: 應用門檻高,需要硬件層面支持,web類應用難以應用。此外,生物特征屬于不可變更類型,如果生物特征泄露將無法變更,后果比密碼泄露更嚴重。
使用U盾等專有物件:成本高、便捷度差,不僅要攜帶設備,一旦丟失還很麻煩。
所以,為了盡可能的提升認證結果的可信度,彌補單一認證因素存在的弊端,2FA認證方案應用而生。通過同時使用2種認證因素進行綜合識別,來提升識別結果的可信度,保障身份認證的安全性。
話說到這里,既然單一認證有風險,2FA可以將風險降低,那為啥不直接搞個3FA呢?豈不是更加安全嗎?這其實是軟件實現中常見的一種取舍,畢竟軟件最終是要服務于用戶使用的,還是需要關注下用戶使用的便捷度與使用體驗,所以2FA相對而言,就是在安全和便捷之間取了個折中。
2FA的形態演進
2FA
并非是一個新鮮玩意,它很早就已經開始廣泛應用在各種場景中了。隨著時間的推移,其呈現形式也經歷了數次的演進,服務接入門檻降低、用戶使用的繁瑣度也大幅下降。
下面舉幾個2FA的實際應用,感受下這些年2FA技術的變革。
早些年的時候,開通網上銀行的時候,銀行會提供一個類似U盤形狀的U盾
,或者是一個密碼生成器
(估計很多年輕小朋友都沒見過,也算是時代的眼淚吧~)。在需要轉賬的時候,除了要輸入自己的銀行卡號和取款密碼,還需要將U盾插入到電腦上,或者用密碼生成器生成一串數字,并將數字填入到網頁中進行雙重校驗之后,才會允許轉賬操作。

這種2FA的應用場景中,分別使用了密碼和獨立物理設備進行綜合認證。有效的規避了密碼泄露或者U盾丟失帶來的風險(當然,U盾和密碼同時被另一個人拿到的話,就回天乏術了),保障個人資金的安全。
這種方式,雖然達到了賬號安全性的要求,但是弊端也很明顯:
實施門檻高,需要生產配套物理設備,所以僅在銀行這種財大氣粗的行業領域中使用,很難在各行業中普遍推廣。
用戶使用繁瑣,如果設備不在身邊或者丟失,則無法使用。而且,不同銀行之間、甚至同一個銀行的不同銀行卡之間都有配套獨立的設備,保存并區分也是一件很頭疼的事情。
也是由于上述的原因,現在幾乎已經看不到U盾的身影了。
這個是目前比較常見的一種2FA的應用形態。比如某度網盤,輸入賬號和密碼驗證通過后,還會要求向手機發送驗證碼,基于驗證碼進行二次身份認證通過之后,方可正常登錄到系統中。

這種形態,其實算是早期的密碼+U盾
設備的一種升級方案。前面也說過了基于U盾等物理設備進行認證的成本與使用繁瑣問題,而當前手機已經成為用戶必備且幾乎形影不離的物件,它便是U盾等設備的最佳替代品。
基于密碼+手機驗證碼的方式,在提升認證安全性的同時,打破了對特定配套物理設備的依賴,降低了2FA方案的落地成本與用戶使用體驗,被廣泛的應用到了各種在線身份認證的場景中,成為了當前最為主流的一種2FA認證方式。
但是利用手機驗證碼進行驗證,依舊會存在一個成本問題,畢竟發送短信也是要錢的,尤其是對于一些幾億用戶體量的系統而言,即使每個用戶1個月只發送一條短信,算下來也是一筆不菲的費用啊。
apple用戶如果登錄過網頁版iCloud,應該都有見過iCloud的2FA實現思路,它在驗證完用戶的賬號和密碼之后,并非是發送短信驗證碼,而是向登錄了此賬號的iphone設備推送了一條彈窗通知,里面顯示了一串隨機碼,用戶輸入iPhone接收到的隨機碼,完成身份驗證并進入到系統重。


蘋果的這種實現,借助自身iPhone設備的廣泛應用,構建了服務端與iPhone設備之間的專有推送通道,完美的省掉了短信驗證碼發送的費用。但,這種方案,就像網上很多人調侃蘋果的那句Only Apple Can Do
一樣,還真的只有Apple等手機設備廠商可以實現。對于普通的系統服務提供者,想要通過非短信途徑推送驗證碼給用戶,也至少得要用戶在手機中安裝個自己產品的APP應用,才有可能實現利用自己的通道進行點對點消息推送,但這一條件顯然限制了該方案的推行。
總體而言,iCloud的這種做法,是一種更加經濟的2FA方案,但是技術門檻與推廣門檻極高,不具備普遍性。
并不是所有公司都是手機廠商。所以是否有一種通用的、成本更低廉的2FA實現方案呢?在這個背景下,一種基于TOTP
協議的2FA方案進入大眾視野中。當前很多啟用2FA的網站,使用的都是這一方案。
看下GitHub的2FA登錄實現。在開啟2FA功能的時候,需要在提前在手機上安裝一個APP并綁定到GitHub賬號上。這樣后續在輸入賬號密碼之后,還需要打開APP并將APP中生成的鑒權碼填入到界面上進行二次驗證,驗證通過之后方可進入系統。


這里的手機上安裝的APP,也是基于TOTP協議進行開發的密鑰生成器。這一方案接入成本相對較低、更容易推廣,目前正在逐步被各類系統所支持。值得一提的是,正如本文開頭提及的消息,在9月中旬剛剛發布的IOS18系統中自帶了一款名為密碼的APP,其中提供了一個驗證碼功能,也正是基于TOTP協議的鑒權碼生成器,使用它生成的驗證碼也可以正常完成2FA認證。

基于TOTP的2FA
在上面介紹GitHub的2FA方案的時候,有提過其采用的是基于TOTP算法
的2FA方案。所謂TOTP,即基于時間的一次性密碼(Time-based One-Time Password
,簡稱TOTP),它是一種國際標準協議(RFC6238
)。其本質上就是取當前時間戳以及當前賬號的一個唯一標識(類似密鑰,服務端頒發、并提供給客戶端保存使用),通過固定算法加工生成一個6位數的鑒權碼,一定時間范圍內生成的鑒權碼是固定的(所以這個驗證碼會有個有效期的概念,一般是30s)。這樣只要服務端和APP端各自計算出一個驗證碼,然后比對下兩個驗證碼是否一致,即可完成校驗。
def generate_totp(secret, interval=30, digits=6, timestamp=None):
"""
生成基于時間的一次性密碼(TOTP)。
:param secret: 密鑰(base32 編碼的字符串)
:param interval: 時間間隔(秒)
:param digits: 生成的 OTP 的位數
:param timestamp: 當前時間戳(秒),默認為當前時間
:return: 生成的 OTP 字符串
"""
if timestamp is None:
timestamp = int(time.time())
counter = timestamp // interval
counter_bytes = struct.pack('>Q', counter)
hmac_result = hmac.new(base64.b32decode(secret), counter_bytes, hashlib.sha1).digest()
offset = hmac_result[-1] & 0xf
binary_otp = hmac_result[offset:offset + 4]
binary_otp = struct.unpack('>I', b'\x00' + binary_otp[1:3] + b'\x00')[0]
otp = str(binary_otp % 10**digits).zfill(digits)
return otp
def main():
secret = 'JBSWY3DPEHPK3PXP'
otp = generate_totp(secret)
print(f"Generated TOTP: {otp}")
verified_otp = input("Enter the OTP you received: ")
if otp == verified_otp:
print("Verification successful!")
else:
print("Verification failed!")
if __name__ == "__main__":
main()
基于上面介紹,可以看出,基于TOTP算法生成驗證碼有兩個輸入因子:時間和用戶密鑰。服務端和客戶端除了需使用相同的加密算法,還需要保證傳入相同的時間戳和用戶密鑰,才能保證生成的校驗碼相同。如何保證服務端和客戶端設備,可以獲取到相同的參數值呢?下面簡單介紹下。
服務端和客戶端在計算生成驗證碼的時候,各自取自身設備本地當前時間作為時間參數。因為如今的智能手機和服務器都支持基于網絡的時鐘校準能力,所以可以很輕松的保證手機終端與應用服務端本地時間的基本一致。

用戶密鑰因子是服務端為用戶生成的授權密鑰,計算生成驗證碼的時候,服務端和客戶端都要使用同一個密鑰進行計算,所以服務端為用戶生成密鑰后,除了服務端要保存該密鑰與用戶的綁定關系,還需要將此密鑰提供給用戶、由用戶將其綁定到手機上的TOTP軟件中(所謂綁定,本質上就是將服務端生成的密鑰存儲到手機的TOTP軟件中)。綁定完成后,服務端和客戶端就都擁有相同的用戶密鑰信息了。

至此,TOTP算法所需的2個關鍵參數都已具備,就可以使用TOTP應用生成的驗證碼進行身份二次認證咯。
講到這里,小伙伴們可能會有個疑惑,假如用戶更換了新手機,新手機上安裝的TOTP軟件并沒有綁定對應的用戶密鑰信息,那不就沒法登錄了嗎?
這就要再回到開啟2FA認證的時候,服務端除了會生成一個密鑰(類似公鑰)提供給客戶端進行綁定,還會同時提供一份Recovery Code
,會提示用戶將其可靠保存起來。

目前市面上主流的基于TOTP的客戶端軟件,主要有2個:
Google Authenticator
MicroSoft Authenticator
當然,現在又多了個Apple Password
,以后可能會形成三足鼎立的局面。
服務中集成2FA能力
如果需要在服務端開啟2FA能力,需要集成實現對應的TOTP密鑰算法即可。以JAVA為例,可以通過集成現有的第三方庫來快捷實現,常用的有com.warrenstrange.googleauth
庫:
<dependency>
<groupId>com.warrenstrange</groupId>
<artifactId>googleauth</artifactId>
<version>{version}</version>
</dependency>
代碼中直接使用其提供的api接口即可,下面代碼演示下api接口的使用方式:
import com.warrenstrange.googleauth.GoogleAuthenticator;
import com.warrenstrange.googleauth.IKey;
import com.warrenstrange.googleauth.KeyGenerator;
import java.time.Instant;
public class TOTPExample {
public static void main(String[] args) {
IKey key = KeyGenerator.getKey(20);
String secretKey = key.getKey();
long currentTime = Instant.now().getEpochSecond();
int timeStep = 30;
long timeWindow = currentTime / timeStep;
GoogleAuthenticator ga = new GoogleAuthenticator();
String totp = ga.getTotpPassword(secretKey, timeWindow);
System.out.println("Generated TOTP: " + totp);
boolean isValid = ga.authorize(totp, secretKey, timeWindow);
System.out.println("TOTP Validation: " + (isValid ? "Success!" : "Failed!"));
boolean isValidWithTolerance = false;
for (int i = -1; i <= 1; i++) {
long toleranceWindow = timeWindow + i;
if (ga.authorize(totp, secretKey, toleranceWindow)) {
isValidWithTolerance = true;
break;
}
}
System.out.println("TOTP Validation with Tolerance: " + (isValidWithTolerance ? "Success!" : "Failed!"));
}
}
?轉自https://www.cnblogs.com/softwarearch/p/18562876
該文章在 2025/2/14 10:54:46 編輯過