使用aerogear生成totp

本文主要講述一下如何使用aerogear-otp生成otp,以及相關源碼分析html

otp分類

全稱是one time password,一般用來支持雙因素認證。主要能夠分兩類java

  • HMAC-Based One-time Password (HOTP)

RFC 4226規範中android

  • Time-based One-time Password (TOTP)

RFC 6238規範中git

這裏主要講TOTP
  • 客戶端

其常見的手機客戶端有Google Authenticator APP以及阿里雲的身份寶。因爲google的軟件在國內被牆,所以可使用阿里雲的身份寶github

  • 服務端

服務端的話,google官方有c的代碼,java的話不少第三方都有實現,這裏選擇jboss提供的aerogear-otp-java,其maven以下api

<dependency>
            <groupId>org.jboss.aerogear</groupId>
            <artifactId>aerogear-otp-java</artifactId>
            <version>1.0.0</version>
        </dependency>

步驟

主要的步驟以下:服務器

綁定密鑰

  • 服務端爲每一個帳戶生成一個secret並保存下來
  • 服務端提供該密鑰的二維碼掃描功能,方便客戶端掃描綁定帳號
  • 用戶手機安裝Google Authenticator APP或阿里雲的身份寶,掃描二維碼綁定該帳號的secret

使用otp驗證

綁定secret以後,就可使用one time password進行驗證了。app

實例

生成客戶端密鑰的二維碼

String secret = Base32.random();
Totp totp = new Totp(secret);
String uri = totp.uri(account);
將這個uri做爲二維碼的信息,便可。
/**
     * Prover - To be used only on the client side
     * Retrieves the encoded URI to generated the QRCode required by Google Authenticator
     *
     * @param name Account name
     * @return Encoded URI
     */
    public String uri(String name) {
        try {
            return String.format("otpauth://totp/%s?secret=%s", URLEncoder.encode(name, "UTF-8"), secret);
        } catch (UnsupportedEncodingException e) {
            throw new IllegalArgumentException(e.getMessage(), e);
        }
    }
它的格式otpauth://totp/%s?secret=%s,Google Authenticator APP或阿里雲的身份寶均支持這種格式的識別。

校驗

boolean isValid = totp.verify(code);

其源碼以下dom

/**
     * Verifier - To be used only on the server side
     * <p/>
     * Taken from Google Authenticator with small modifications from
     * {@see <a href="http://code.google.com/p/google-authenticator/source/browse/src/com/google/android/apps/authenticator/PasscodeGenerator.java?repo=android#212">PasscodeGenerator.java</a>}
     * <p/>
     * Verify a timeout code. The timeout code will be valid for a time
     * determined by the interval period and the number of adjacent intervals
     * checked.
     *
     * @param otp Timeout code
     * @return True if the timeout code is valid
     *         <p/>
     *         Author: sweis@google.com (Steve Weis)
     */
    public boolean verify(String otp) {

        long code = Long.parseLong(otp);
        long currentInterval = clock.getCurrentInterval();

        int pastResponse = Math.max(DELAY_WINDOW, 0);

        for (int i = pastResponse; i >= 0; --i) {
            int candidate = generate(this.secret, currentInterval - i);
            if (candidate == code) {
                return true;
            }
        }
        return false;
    }
這裏有個DELAY_WINDOW參數,是爲了防止手機客戶端與服務器端的時差引入的。默認值是1,即容許那個code在手機端過時30秒以內到服務端驗證還有效。
  • clock

aerogear-otp-java-1.0.0-sources.jar!/org/jboss/aerogear/security/otp/api/Clock.javamaven

public class Clock {

    private final int interval;
    private Calendar calendar;

    public Clock() {
        interval = 30;
    }

    public Clock(int interval) {
        this.interval = interval;
    }

    public long getCurrentInterval() {
        calendar = GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"));
        long currentTimeSeconds = calendar.getTimeInMillis() / 1000;
        return currentTimeSeconds / interval;
    }
}
這個interval默認是30,固然你也能夠改成1分鐘那就是60.
另外這裏先把毫秒轉爲秒,而後再除去interval,因爲是使用/,所以是直接取整數部分,於是上面的DELAY_WINDOW的值=N,其實至關於容許過去的N個interval的code還能校驗成功

code生成源碼

aerogear-otp-java-1.0.0-sources.jar!/org/jboss/aerogear/security/otp/Totp.java

private int generate(String secret, long interval) {
        return hash(secret, interval);
    }

    private int hash(String secret, long interval) {
        byte[] hash = new byte[0];
        try {
            //Base32 encoding is just a requirement for google authenticator. We can remove it on the next releases.
            hash = new Hmac(Hash.SHA1, Base32.decode(secret), interval).digest();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (Base32.DecodingException e) {
            e.printStackTrace();
        }
        return bytesToInt(hash);
    }

    private int bytesToInt(byte[] hash) {
        // put selected bytes into result int
        int offset = hash[hash.length - 1] & 0xf;

        int binary = ((hash[offset] & 0x7f) << 24) |
                ((hash[offset + 1] & 0xff) << 16) |
                ((hash[offset + 2] & 0xff) << 8) |
                (hash[offset + 3] & 0xff);

        return binary % Digits.SIX.getValue();
    }

    /**
     * Retrieves the current OTP
     *
     * @return OTP
     */
    public String now() {
        return leftPadding(hash(secret, clock.getCurrentInterval()));
    }

    private String leftPadding(int otp) {
        return String.format("%06d", otp);
    }

小結

  • interval

默認值爲30,在Clock裏頭能夠經過構造器修改interval。不過因爲Google Authenticator APP或阿里雲的身份寶均爲30秒更換一次,所以這個參數能夠按默認的來。

  • DELAY_WINDOW

aerogear-otp-java自己不提供DELAY_WINDOW的修改,不過你能夠繼承Totp,本身擴展一下。

doc

相關文章
相關標籤/搜索