최근 비트코인, 이더리움, 리플 등의 가상화폐 거래소가 인기를 끌고 있다.
그리고 대부분의 사이트에서 보안을 위해 구글OTP 등의 2차인증을 이용한다.
최근 구글OTP 연동을 해야할 일이 생겨서 검색을 통해 방법을 찾았다.
처음에는 API 문서같은게 잘 없어서 당황스러웠다.
참고사이트 : http://zero-gravity.tistory.com/221
위의 소스코드를 거의 그대로 이용하여 그냥 내가 알아보기 쉽게 정리한 것에 불과하다.
generate를 통해 키값, QR코드 주소를 생성한다.
생성된 키값은 회원목록을 저장하는 데이터베이스에 저장하는 것이 좋을 것 같다.
otp 검증은 checkCode를 통해서 이루어진다.
위에서 생성한 QR코드를 통해 구글OTP앱에 등록을 하고 표시되는 번호와 키값을 매개변수로 넣어서 검증한다.
참고로 테스트를 해봤는데 이미 지난 코드를 넣어도 통과하는 것을 볼 수 있다.
대략 1~2분 전에 생성된 코드도 입력이 가능하다는 얘기다.
2분정도 지나면 안된다.
OTP 앱
안드로이드: https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=ko
iOS: https://itunes.apple.com/kr/app/google-authenticator/id388497605?mt=8
import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.Random; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base32; public class GoogleOTP { public static void main(String[] args) { GoogleOTP otp = new GoogleOTP(); HashMap<String, String> map = otp.generate("name", "host"); String otpkey = map.get("encodedKey"); String url = map.get("url"); System.out.println(otpkey); // 아래의 결과는 당연히 압도적인 확률로 false가 나온다. // 우선 위의 과정으로 생성된 키/url을 otp앱에 등록하고나서 표시되는 번호와 생성된 키를 넣어주면 true가 나올 것이다. boolean check = otp.checkCode("123123", otpkey); System.out.println(check); } public HashMap<String, String> generate(String userName, String hostName) { 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); String url = getQRBarcodeURL(userName, hostName, encodedKey); // Google OTP 앱에 userName@hostName 으로 저장됨 // key를 입력하거나 생성된 QR코드를 바코드 스캔하여 등록 map.put("encodedKey", encodedKey); map.put("url", url); return map; } public boolean checkCode(String userCode, String otpkey) { long otpnum = Integer.parseInt(userCode); // Google OTP 앱에 표시되는 6자리 숫자 long wave = new Date().getTime() / 30000; // Google OTP의 주기는 30초 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; } public static String getQRBarcodeURL(String user, String host, String secret) { // QR코드 주소 생성 String format2 = "http://chart.apis.google.com/chart?cht=qr&chs=200x200&chl=otpauth://totp/%s@%s%%3Fsecret%%3D%s&chld=H|0"; return String.format(format2, user, host, secret); } }
'dev' 카테고리의 다른 글
[Spring] 컨트롤러에서 PathVariable 사용하기 (ex: youtube/v/{videoid}) (0) | 2018.01.13 |
---|---|
[HTML] input text 자동완성 되지 않도록 하기 (0) | 2018.01.08 |
[Spring] 컨트롤러에서 @ResponseBody 리턴시 한글 깨짐 현상 해결 (0) | 2018.01.08 |
[Java] 유튜브 검색결과 json으로 받기 (YouTube Data API) (0) | 2018.01.07 |
[JSP] base 태그로 경로 잡기 (0) | 2017.12.27 |
PhantomJS를 이용하여 웹페이지 스크린샷 생성하기 (0) | 2017.12.26 |
[Spring] Spring Framework 프로젝트 초기 설정 (0) | 2017.12.25 |
[Java/Spring] 메일 보내기 (Gmail via TLS) (0) | 2017.12.23 |