IT정보사전

[JAVA] 구글 OTP 개발 본문

웹 프로그래밍

[JAVA] 구글 OTP 개발

작은나무0530 2018. 12. 6. 18:18
728x90
반응형

안녕하세요~ 작은나무입니다.
이번에 프로젝트를 진행하면서 OTP를 적용해야 하는 상황이 되어서 여기저기 알아보고 아래와 같이 구현하였습니다.

OTP?
일회용 비밀번호이며, 보안을 위해 요즘 2차 인증을 진행하는 곳이 있는대, OTP를 적용하여 사용한 곳도 눈에 띈다.
일회성이라 보안수준이 높아 금융권에서 많이 활용하고 있습니다.

참고사이트 : http://zero-gravity.tistory.com/221

generate를 통해 OTP키를 생성하고, 생성된 키로 OTP검증을 진행합니다.
검증은 checkCode에서 이루어 집니다.
OTP를 이용하기 위해서는 OTP APP을 설치하셔서 사용하시면 됩니다.

검증 시 hash값을 찍어보면 1개의 OTP 번호가 아닌 여러개의 번호중 입력된 번화와 일치하는지 비교합니다.
약간의 시간차가 있어서 앞,뒤의 OTP검증 번호도 1~2분 내에서는 인증이 가능 한 것 같습니다.

[소스구현 - Class]

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class OTPUtil
{
    public static HashMap<String, String> generate(String name, String host) {
        HashMap<String, String> map = new HashMap<String, String>();
        byte[] buffer = new byte[5 + 5 * 5];        //버퍼에 할당
        new Random().nextBytes(buffer);            //버퍼를 난수로 셋팅
        Base32 codec = new Base32();
        byte[] secretKey = Arrays.copyOf(buffer, 10);
        byte[] bEncodedKey = codec.encode(secretKey);

        String encodedKey = new String(bEncodedKey);        //생성된 Key (해당키로 OTP확인)

        map.put("encodedKey", encodedKey);
        map.put("url", url);

        return map;
    }

    //OTP코드 6자리 Check
    public static boolean checkCode(String userCode, String otpkey) {
        long otpnum = Integer.parseInt(userCode); // OTP 앱에 표시되는 6자리 숫자
        long wave = new Date().getTime() / 30000; // OTP의 주기(초)
        boolean result = false;
        try {
             Base32 codec = new Base32();
             byte[] decodedKey = codec.decode(otpkey);
             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[] key, long t) throws NoSuchAlgorithmException, InvalidKeyException {
        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;
    }
}


위처럼 별도로 Class를 생성하고. 호출하여 사용하면 됩니다. 아래와 같이 검증 하시면 될 것 같습니다.
예) OTPUtil.checkCode("입력한 OTP번호", "발급된 OTPKEY");

OTP KEY의 발급은 OTPUtil.generate("사용자", "도메인");
감사합니다.

728x90
반응형
그리드형
Comments