고객사 중에 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 userCode, String 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[] 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 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.keyHeader, this.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;
}