Two Factor Authentication

What is two factor authentication?

two factor means utilizing two factor classes

  • Phone

  • Password

  • 指紋
  • ...

http://www.slideshare.net/markstanislav/twofactor-authentication-a-primer

Modern two factor authication

  • RSA SecurID

  • https://en.wikipedia.org/wiki/RSA_SecurID
  • TOTP

  • https://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm

特性

  • 方便 , Google 有提供totp app

  • 時間延遲短 , 比起簡訊來的快太多

  • 沒有每次產生費用

  • 需有硬體產生器 or 智慧型手機

  • RSA SecurID

  • RSA SecurID

TOTP Authentication 

TOTP Authentication 

  • Time-based One-time Password Algorithm
  • Is based on HOTP with a timestamp replacing the incrementing counter.
  • https://github.com/google/google-authenticator
  • RFC-6238(https://tools.ietf.org/html/rfc6238)

Demo

  • http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/
  • http://totp-authentication-demo.herokuapp.com/

使用原理

  • Server產生一組totp綁定碼 給User
  • User透過App 紀錄totp綁定碼
  • User每次登入時 , 輸入App根據綁定碼產生的totp code
  • Server驗證totp code、userId與資料庫根據userId撈出的totp綁定碼.

Code

public class TOTP {

	private static final int[] DIGITS_POWER
	= { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };

	private static byte[] hmacSha(String crypto, byte[] keyBytes, byte[] text) {
		try {
			Mac hmac = Mac.getInstance(crypto);
			SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
			hmac.init(macKey);
			return hmac.doFinal(text);
		} catch (GeneralSecurityException gse) {
			throw new UndeclaredThrowableException(gse);
		}
	}

	public static int generateTOTP(byte[] key, long time, int digits, String crypto) {
		byte[] msg = ByteBuffer.allocate(8).putLong(time).array();
		byte[] hash = hmacSha(crypto, key, msg);
		// 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);
		int otp = binary % DIGITS_POWER[digits];
		return otp;
	}

}

Code

public class TOTPUtils {
    private int SECRET_SIZE = 10;
    private int PASS_CODE_LENGTH = 6;
    private int WINDOW = 3;
    private int INTERVAL = 30;
    private String CRYPTO = "HmacSHA1";

    public String generateSecret() {
        // Allocating the buffer
        byte[] buffer = new byte[SECRET_SIZE];
        // Filling the buffer with random numbers.
        rand.nextBytes(buffer);
        byte[] secretKey = Arrays.copyOf(buffer, SECRET_SIZE);
        byte[] encodedKey = BaseEncoding.base32().encode(secretKey).getBytes();
        return new String(encodedKey);
    }
    public boolean checkCode(String secret, long code) throws NoSuchAlgorithmException, InvalidKeyException {
        byte[] decodedKey = BaseEncoding.base32().decode(secret);
        // Window is used to check codes generated in the near past.
        int window = WINDOW;
        long currentInterval = getCurrentInterval();
        for (int i = -window; i <= window; ++i) {
            long hash = TOTP.generateTOTP(decodedKey, currentInterval + i, PASS_CODE_LENGTH, CRYPTO);
            if (hash == code) {
                return true;
            }
        }
        return false;
    }
}

Code

public class TwoFactorCodeManager {

    @Autowired
    private TwoFactorCodeRepo repo;
    @Autowired
    private TOTPUtils totpUtils;

    public boolean hasLink(String userId) {
        return repo.hasLink(userId);
    }

    public String generatorTotpCode(String userId) {
        String totpCode = totpUtils.generateSecret();
        return totpCode;
    }

    public boolean linkCode(String totpCode, long inputCode, String userId) throws InvalidKeyException, NoSuchAlgorithmException {
        boolean verifySuccess = totpUtils.checkCode(totpCode, inputCode);
        if (verifySuccess) {
            repo.updateTotpCode(totpCode, userId);
        }
        return verifySuccess;
    }

    public boolean verifyCode(long inputCode, String userId) throws InvalidKeyException, NoSuchAlgorithmException {
        String totpCode = repo.getTotpCode(userId);
        return totpUtils.checkCode(totpCode, inputCode);
    }

}

跟Spring Security整合的問題

  • 如果要分兩頁登入,需改寫登入段相關程式
  • 同時間如果有跟SSO整合,需改寫取得SSO Token的時機

參考文章

  • http://wulfric.me/2014/09/design-of-sms-verification/

End

Two factor Authentication

By Mars Yang

Two factor Authentication

  • 772