基於TOTP的雙向認證演算法

weixin_33670713發表於2017-08-09

基於otp演算法的雙向認證

先舉例一個應用場景吧,我們應該都用U盾,或者將軍令這種生成動態金鑰的工具,其實它內部就是基於OTP演算法來實現的。

谷歌也有開源這樣的開源專案,本文就是博主通讀了谷歌開源專案原始碼來實現的,專案地址:

Google Android端專案原始碼

Google 後臺專案原始碼

演算法概要

TOTP(基於時間的一次性密碼演算法)是支援時間作為動態因素基於HMAC一次性密碼演算法的擴充套件。

本演算法是一個對稱演算法,也就是說,後臺和移動端採用同樣的金鑰,同時這個演算法是依賴於當前的系統時間的,所以可以用於動態驗證。

TOTP = HMAC-SHA-1(K, (T - T0) / X)

  • K 共享金鑰

  • T 當前時間戳

  • T0 開始的時間戳

  • X 時間步長

多嘮叨幾句:我們整體演算法採用處理金鑰的方式,是要將金鑰轉換成byte陣列之後進行操作的,還有就是谷歌的這套雙向認證演算法中嚴格的採用了Base32字串的方式,Base32中只有A-Z和2-7這些字元。

程式碼效果:

2017/08/09 20:39:56
983452
2017/08/09 20:39:57
983452
2017/08/09 20:39:58
983452
2017/08/09 20:39:59
983452
2017/08/09 20:40:00
977560
2017/08/09 20:40:01
977560
2017/08/09 20:40:02
977560
2017/08/09 20:40:03
977560
2017/08/09 20:40:04
977560

應用在app中的效果圖:

2585384-0c294e43b715390f.jpg

應用在app中的效果圖:

核心程式碼演算法:

演算法GitHub地址:https://github.com/linsir6/TOTP

演算法的核心類:

/**
 * Created by linSir
 * date at 2017/8/8.
 * describe: 演算法的核心類
 */

public class PasscodeGenerator {
    private static final int MAX_PASSCODE_LENGTH = 9;

    private static final int[] DIGITS_POWER
            // 0 1  2   3    4     5      6       7        8         9
            = {1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};

    private final Signer signer;
    private final int codeLength;

    interface Signer {
        byte[] sign(byte[] data) throws GeneralSecurityException;
    }

    public PasscodeGenerator(Signer signer, int passCodeLength) {
        if ((passCodeLength < 0) || (passCodeLength > MAX_PASSCODE_LENGTH)) {
            throw new IllegalArgumentException(
                    "PassCodeLength must be between 1 and " + MAX_PASSCODE_LENGTH
                            + " digits.");
        }
        this.signer = signer;
        this.codeLength = passCodeLength;
    }

    private String padOutput(int value) {
        String result = Integer.toString(value);
        for (int i = result.length(); i < codeLength; i++) {
            result = "0" + result;
        }
        return result;
    }

    public String generateResponseCode(long state)
            throws GeneralSecurityException {
        byte[] value = ByteBuffer.allocate(8).putLong(state).array();
        return generateResponseCode(value);
    }

    public String generateResponseCode(byte[] challenge)
            throws GeneralSecurityException {
        byte[] hash = signer.sign(challenge);

        int offset = hash[hash.length - 1] & 0xF;
        int truncatedHash = hashToInt(hash, offset) & 0x7FFFFFFF;
        int pinValue = truncatedHash % DIGITS_POWER[codeLength];
        return padOutput(pinValue);
    }

    private int hashToInt(byte[] bytes, int start) {
        DataInput input = new DataInputStream(
                new ByteArrayInputStream(bytes, start, bytes.length - start));
        int val;
        try {
            val = input.readInt();
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
        return val;
    }
}


整體演算法的思想和程式碼實現大概就是這樣,如果有什麼問題,歡迎大家在GitHub上面提Issues

相關文章