OTP는 One Time Password의 약자이다. 일회용이라는 측면에서 보안에 더욱 강력하다고 알려져 있다.
자바에서 구현하기 위해 여러 링크를 둘러봤는데 아래 링크가 가장 크게 도움이 됐다.
medium.com/@ihorsokolyk/two-factor-authentication-with-java-and-google-authenticator-9d7ea15ffee6
build.gradle
// 최초 1회 개인키 발급에 사용 (16진수, base32로 변환)
// https://mvnrepository.com/artifact/commons-codec/commons-codec
compile group: 'commons-codec', name: 'commons-codec', version: '1.15'
// QR 이미지 생성에 사용
// https://mvnrepository.com/artifact/com.google.zxing/javase
compile group: 'com.google.zxing', name: 'javase', version: '3.4.1'
// 일회용 비밀번호 생성에 사용
// https://mvnrepository.com/artifact/de.taimos/totp
compile group: 'de.taimos', name: 'totp', version: '1.0'
OTPUtil.java
import com.google.zxing.BarcodeFormat;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import de.taimos.totp.TOTP;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Hex;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.SecureRandom;
public class OTPUtil {
// 최초 개인키 생성 시 사용하는 메소드
public String getSecretKey() {
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[20];
random.nextBytes(bytes);
Base32 base32 = new Base32();
return base32.encodeToString(bytes);
}
// OTP검증 요청 때마다 개인키로 OTP 생성하는 메소드
public String getTOTPCode(String secretKey) {
Base32 base32 = new Base32();
byte[] bytes = base32.decode(secretKey);
String hexKey = Hex.encodeHexString(bytes);
return TOTP.getOTP(hexKey);
}
// 개인키, 계정명(유저ID), 발급자(회사명)을 받아서 구글OTP 인증용 링크를 생성하는 메소드
public String getGoogleOTPAuthURL(String secretKey, String account, String issuer) {
try {
return "otpauth://totp/"
+ URLEncoder.encode(issuer + ":" + account, "UTF-8").replace("+", "%20")
+ "?secret=" + URLEncoder.encode(secretKey, "UTF-8").replace("+", "%20")
+ "&issuer=" + URLEncoder.encode(issuer, "UTF-8").replace("+", "%20");
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
}
// url, 파일생성할경로, 높이px, 폭px을 받아서 QR코드 이미지를 생성하는 메소드
public void getQRImage(String googleOTPAuthURL, String filePath, int height, int width) throws WriterException, IOException {
BitMatrix matrix = new MultiFormatWriter().encode(googleOTPAuthURL, BarcodeFormat.QR_CODE, width, height);
try (FileOutputStream out = new FileOutputStream(filePath)) {
MatrixToImageWriter.writeToStream(matrix, "png", out);
}
}
}
마무리
뭐지.. 삽질과 구글링은 꽤나 오래한 것 같은데 정리하려니까 내용이 없는 것 같다;;; 크흠...
생각 보다 아주 간단하게 구글 OTP 2차인증을 구현할 수 있어서 놀랐다.
학원 최종프로젝트 이후에 스프링 시큐리티에서 제공하는 PasswordEncoder의 작동원리를 알아보고자 한 적이 있다.
내가 이해한 것은 아래와 같다.
1. Salt와 횟수를 인코딩된 값에서 알아낸다
2. 지금 입력시도한 값에 1에서 알아낸 값을 대입해서 알고리즘을 돌려 인코딩한다.
3. 인코딩된 값과 지금 인코딩한 값을 비교한다.
인데 이 과정에서 비트 연산 부분 코드가 나오면서 막혔던 기억이 있다.. 크흠;
그때 끝까지 파내지는 못해서 결국 암호화 관련 포스팅을 하지 못했는데..
이번에도 totp에서 암호화 알고리즘이 나오는데.. 제대로 이해하질 못하니 참 답답하고 찝찝한 마음이 남는다.
어쨋든 기본적인 구글 OTP 구현을 위한 코드는 위 내용이 전부다.
1. 계정별로 비밀키를 생성해서 어딘가에 저장을 한다.
2. 비밀키와 id, issuer를 이용해서 url을 만들어서 qr이미지를 만들어서 사용자에게 OTP앱에 등록하게 한다.
3. 유저가 인증 시도시 해당 유저의 비밀키를 저장소에서 가져와서 totp 알고리즘을 돌려 일치여부를 확인한다.
아주 심플.
'Java & Spring' 카테고리의 다른 글
SpringBoot 로그 레벨 동적으로 변경하기 (runtime logging level change) (0) | 2021.06.12 |
---|---|
SpringBoot에서 오라클 클라우드 Autonomous Databases 접속하기 (10) | 2021.05.19 |
Head First Design Pattern - 1. 전략 패턴 (0) | 2021.01.24 |
Spring MVC 프로퍼티 파일 value 가져오기 (0) | 2020.09.29 |
Spring MVC 멀티 파일 업로드 (0) | 2020.09.29 |