고객사 중에 google otp 연동을 해달라는 곳이 있어서 찾아보고 입맛에 맞게 좀 변경하였다.



import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base32;

import lombok.Builder;

/**
 * @author nwkim
 *
 * @see https://github.com/tomsure/Exchange-V2.0/blob/master/chainExchange/java-exchange/gbx/Chain_English/src/com/ruizton/util/GoogleAuthenticator.java
 * @see http://code.google.com/p/google-authenticator 
 * @see http://tools.ietf.org/id/draft-mraihi-totp-timebased-06.txt 
 */
@Builder
public class GoogleOTPHelper {

    public static final int SECRET_SIZE = 10;
    public static final String SEED = "g8GjEvTbW5oVSV7avLBdw3HqGlUDNzKFI7izOF8GwLDVKs2m0QN7vxRs2im8MDaNCWGmcD2rvcZx";
    public static final String RANDOM_NUMBER_ALGORITHM = "SHA1PRNG";

    private String keyHeader;
    private String encodedKey;

    public String generate() throws NoSuchAlgorithmException {

        SecureRandom sr = null;
        sr = SecureRandom.getInstance(RANDOM_NUMBER_ALGORITHM);
        sr.setSeed(SEED.getBytes());
        byte[] buffer = sr.generateSeed(SECRET_SIZE);
        Base32 codec = new Base32();
        byte[] bEncodedKey = codec.encode(buffer);
        this.encodedKey = new String(bEncodedKey);
        return this.encodedKey;

    }

    public static boolean checkCode(String userCodeString encodedKey) {
        long otpnum = Integer.parseInt(userCode);
        // Google OTP의 주기는 30초, 라고 써있지만 이상하게 엄청 길다 체크해보니 3분인듯
        // 시간을 좀 조정해서 줄여보고 싶은데 생각처럼 안된다
        long wave = System.currentTimeMillis() / 30000
        System.out.println("@@@@wave       " + wave);
        boolean result = false;
        try {
            Base32 codec = new Base32();
            byte[] decodedKey = codec.decode(encodedKey);
            int window = 3;
            for (int i = -window; i <= window; ++i) {
                long hash = verify_code(decodedKey, wave + i);
                if (hash == otpnum)
                    result = true;
            }
        } catch (InvalidKeyException | NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return result;
    }

    private static int verify_code(byte[] keylong tthrows NoSuchAlgorithmExceptionInvalidKeyException {
        byte[] data = new byte[8];
        long value = t;
        for (int i = 8; i-- > 0; value >>>= 8) {
            data[i] = (byte) value;
        }

        SecretKeySpec signKey = new SecretKeySpec(key, "HmacSHA1");
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(signKey);
        byte[] hash = mac.doFinal(data);

        int offset = hash[20 - 1] & 0xF;

        // We're using a long because Java hasn't got unsigned int.
        long truncatedHash = 0;
        for (int i = 0; i < 4; ++i) {
            truncatedHash <<= 8;
            // We are dealing with signed bytes:
            // we just keep the first byte.
            truncatedHash |= (hash[offset + i] & 0xFF);
        }

        truncatedHash &= 0x7FFFFFFF;
        truncatedHash %= 1000000;

        return (int) truncatedHash;
    }

    public String getQRBarcodeURL() {
        // QR코드 주소 생성
        String format2 = "http://chart.apis.google.com/chart?cht=qr&chs=200x200&chl=otpauth://totp/%s%%3Fsecret%%3D%s&chld=H|0";
        return String.format(format2, this.keyHeaderthis.encodedKey);
    }

}


// 생성 방법
// seckey 별도로 db에 저장?
GoogleOTPHelper optHelper = GoogleOTPHelper.builder().keyHeader(empNo).build();
String seckey = optHelper.generate();
String url = optHelper.getQRBarcodeURL();

//검증 방법
String realOTP = otp.replaceAll(" """);
if(!GoogleOTPHelper.checkCode(realOTP, user.getOtpKey())) {
    // otp 틀림
    formAuthFailureHandler.onAuthenticationFailure(request, response, null);
    return;
}












Posted by motolies
,