雙因子認證解決方案

什麼叫雙因子認證?

通俗的講,通常的認證方式都是用戶名/密碼的方式,也就是隻有密碼這一個因子來做認證,雙因子無非是增長一個因子,加強認證的安全性。java

常看法決方案

  • 短信方式
  • 郵件方式
  • 電話語音方式
  • TOTP解決方案

前三種方案,其實都大同小異。Server端經過某種算法生成一段隨機密碼,經過短信、郵件或者電話的方式傳遞給用戶,用戶把隨機密碼做爲登陸的憑證傳遞給Server,Server驗證經過以後,就完成了一次雙因子認證。可是短信和電話語音對於運營公司是有必定的成本的,除此以外有些非互聯網的應用可能並不通公網,這種狀況下,TOTP不失爲一種好的雙因子認證的解決方案。git

什麼是TOTP?

是Time-based One-Time Password的簡寫,表示基於時間戳算法的一次性密碼。github

若是你們玩過夢幻西遊的話,那麼對將軍令應該不陌生,這個就是基於TOTP的一個產物。算法

OTP

介紹TOTP以前,先介紹下OTP安全

One-Time Password的簡寫,表示一次性密碼。app

OTP(K,C) = Truncate(HMAC-SHA-1(K,C))函數

其中,K表明密鑰串;C是一個數字,表示隨機數;HMAC-SHA-1表示用SHA-1作HMAC;工具

Truncate是一個函數,用於截取加密後的串,並取加密後串的一些字段組成一個數字。ui

對HMAC-SHA-1方式加密來講,Truncate實現以下:google

  • HMAC-SHA-1加密後的長度獲得一個20字節的密串;
  • 取這個20字節的密串的最後一個字節,取這字節的低4位,做爲截取加密串的下標偏移量;
  • 按照下標偏移量開始,獲取4個字節,以大端(把高位字節放在低位地址)的方式組成一個整數;
  • 截取這個整數的後6位或者8位轉成字符串返回。
public static String generateOTP(String K,
                                      String C,
                                      String returnDigits,
                                      String crypto){
        int codeDigits = Integer.decode(returnDigits).intValue();
        String result = null;
 
        // K是密碼
        // C是產生的隨機數
        // crypto是加密算法 HMAC-SHA-1
        byte[] hash = hmac_sha(crypto, K, C);
        // hash爲20字節的字符串
 
        // put selected bytes into result int
        // 獲取hash最後一個字節的低4位,做爲選擇結果的開始下標偏移
        int offset = hash[hash.length - 1] & 0xf;
 
        // 獲取4個字節組成一個整數,其中第一個字節最高位爲符號位,不獲取,使用0x7f
        int binary =
                ((hash[offset] & 0x7f) << 24) |
                ((hash[offset + 1] & 0xff) << 16) |
                ((hash[offset + 2] & 0xff) << 8) |
                (hash[offset + 3] & 0xff);
        // 獲取這個整數的後6位(能夠根據須要取後8位)
        int otp = binary % 1000000;
        // 將數字轉成字符串,不夠6位前面補0
        result = Integer.toString(otp);
        while (result.length() < codeDigits) {
            result = "0" + result;
        }
        return result;
    }

返回的結果就是看到一個數字的動態密碼。

HOTP

知道了OTP的基本原理,HOTP只是將其中的參數C變成了隨機數

公式修改一下

HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))

HOTP: Generates the OTP for the given count

即:C做爲一個參數,獲取動態密碼。

通常規定HOTP的散列函數使用SHA2,即:基於SHA-256 or SHA-512 [SHA2] 的散列函數作事件同步驗證;

TOTP詳解

TOTP只是將其中的參數C變成了由時間戳產生的數字。

TOTP(K,C) = HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))

不一樣點是TOTP中的C是時間戳計算得出。

C = (T - T0) / X;

T 表示當前Unix時間戳

T0通常取值爲 0.

X 表示時間步數,也就是說多長時間產生一個動態密碼,這個時間間隔就是時間步數X,系統默認是30秒;

例如:

T0 = 0;

X = 30;

T = 30 ~ 59, C = 1; 表示30 ~ 59 這30秒內的動態密碼一致。

T = 60 ~ 89, C = 2; 表示30 ~ 59 這30秒內的動態密碼一致。

不一樣廠家使用的時間步數不一樣;

  • 阿里巴巴的身份寶使用的時間步數是60秒;
  • 寧盾令牌使用的時間步數是60秒;
  • Google的 身份驗證器的時間步數是30秒;
  • 騰訊的Token時間步數是60秒;

應用

客戶端的實現有不少,上面已經列出來了。而服務端的實現庫比較少,貌似也都是非官方的實現。這裏推薦一個JAVA的實現庫,這是一個私人的庫,介意的朋友只能本身擼輪子了。

這裏基於上述的實現庫,給出一段demo代碼,僅供參考。

package com.github.chenqimiao.util;

import java.text.MessageFormat;

import com.warrenstrange.googleauth.GoogleAuthenticator;
import com.warrenstrange.googleauth.GoogleAuthenticatorConfig;
import com.warrenstrange.googleauth.GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder;
import com.warrenstrange.googleauth.GoogleAuthenticatorKey;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

/**
 * @Auther: chenqimiao
 * @Date: 2019/8/26 22:58
 * @Description: refer https://github.com/wstrange/GoogleAuth
 */
@Slf4j
public class GoogleAuthenticatorUtils {
    // 前綴
    private static final String DEFAULT_USER_PREFIX = "TOTP_USER:";
    // 用戶名|密鑰|發行者
    public static final String QRCODE_TEMPLATE = "otpauth://totp/" + DEFAULT_USER_PREFIX + "{0}?secret={1}&issuer={2}";
    // 默認的發行者
    public static final String DEFAULT_ISSUER = "DAS_TOTP";

    private static final GoogleAuthenticatorConfig DEFAULT_CONFIG;

    static {
        GoogleAuthenticatorConfigBuilder builder = new GoogleAuthenticatorConfigBuilder();

        // Do something here if you want to set config for GoogleAuthenticator

        DEFAULT_CONFIG = builder.build();
    }


    public static String createQrCodeContent(String username, String secret) {
        return createQrCodeContent(username, secret, DEFAULT_ISSUER);
    }

    public static String createQrCodeContent(String username, String secret, String issuer) {
        return MessageFormat.format(QRCODE_TEMPLATE, username, secret, issuer);
    }

    public static String createSecret() {
        return createSecret(DEFAULT_CONFIG);
    }

    public static String createSecret(GoogleAuthenticatorConfig config) {
        GoogleAuthenticator gAuth = new GoogleAuthenticator(config);
        final GoogleAuthenticatorKey key = gAuth.createCredentials();
        return key.getKey();
    }

    public static boolean verify(Integer totpPwd, String secret) {

        return verify(totpPwd, secret, DEFAULT_CONFIG);
    }

    public static boolean verify(Integer totpPwd, String secret, GoogleAuthenticatorConfig config) {
        GoogleAuthenticator gAuth = new GoogleAuthenticator(config);
        return gAuth.authorize(secret, totpPwd);
    }

    public static Integer getTotpPassword(String secret) {
        return getTotpPassword(secret, DEFAULT_CONFIG);
    }

    public static Integer getTotpPassword(String secret, GoogleAuthenticatorConfig config) {
        GoogleAuthenticator gAuth = new GoogleAuthenticator(config);
        return gAuth.getTotpPassword(secret);
    }

    @SneakyThrows
    public static void main(String args[]) {
        String secret = createSecret();
        String qrcodeContent = createQrCodeContent("chenqimiao", secret);
        System.out.println("qrcodeContent is " + qrcodeContent);

        Integer totpPwd = getTotpPassword(secret);
        System.out.println("Current totp password is " + totpPwd);

        boolean result = verify(totpPwd, secret);
        System.out.println("result is " + result);

    }

qrcodeContent能夠經過二維碼工具生成二維碼,使用Google Authenticator掃描該二維碼以後,就至關於爲用戶綁定了一個認證器。

相關文章
相關標籤/搜索