WINDOW_SIZE
만큼 현재 시각 앞뒤로 코드를 추가 검증한다. 0일 경우 현재 시각만을 기준으로 검사한다.SECRET_KEY_LENGTH
를 참조해 키의 길이를 결정한다. 20 이상으로 하는 것이 추천됨. // 양의 정수. 검증 실패 시 현재 시각 전후로 해당 변수로 지정된 횟수만큼 추가 검증
final int WINDOW_SIZE = 3;
// 길이 20 이상, 8의 배수인 양수로 설정하는 것을 추천함. 길이가 8의 배수 + 1이면 인증 제대로 안되는 문제 있음
final int SECRET_KEY_LENGTH = 32;
public int calculateOtpCodeFromKey(String otpKey, long t) throws NoSuchAlgorithmException, InvalidKeyException, EncoderException {
byte[] timeData = new byte[8];
long value = t;
for (int i = 8; i-- > 0; value >>>= 8){
timeData[i] = (byte) value;
}
Base32 codec = new Base32();
byte[] decodedKey = codec.decode(otpKey);
SecretKeySpec signKey = new SecretKeySpec(decodedKey, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signKey);
byte[] hashedOtpKey = mac.doFinal(timeData);
int offset = hashedOtpKey[20 - 1] & 0xF;
long truncatedHash = 0;
for (int i = 0; i < 4; ++i) {
truncatedHash <<= 8;
// we just keep the first byte
truncatedHash |= (hashedOtpKey[offset + i] & 0xFF);
}
truncatedHash &= 0x7FFFFFFF;
truncatedHash %= 1000000;
return (int) truncatedHash;
}
public Boolean verifyOtpCode(int otpCode, String otpKey) throws NoSuchAlgorithmException, InvalidKeyException, EncoderException {
long t = new Date().getTime() / 30000;
// 현재 시간 기준으로 우선 검사
if (otpCode == calculateOtpCodeFromKey(otpKey, t)){
return true;
}
// 최초 검사 실패 시 현재 시간 전후로 WINDOW_SIZE 만큼 추가 검사
for (int i = -1 * this.WINDOW_SIZE; i <= this.WINDOW_SIZE; i++){
if (i == 0){
continue;
}
if (otpCode == calculateOtpCodeFromKey(otpKey, t + i)){
return true;
}
}
// 검사 실패: 코드 불일치
return false;
}
public String generateRandomBase32Key(){
// Allocating the buffer
byte[] buffer = new byte[this.SECRET_KEY_LENGTH * 3];
// Filling the buffer with random numbers
new SecureRandom().nextBytes(buffer);
// Getting the key & converting it to Base32
Base32 codec = new Base32();
byte[] bEncodedKey = codec.encode(buffer) ;
return new String(bEncodedKey).substring(0, this.SECRET_KEY_LENGTH);
}
String generatedKey = otpUtil.generateRandomBase32Key();
long t = new Date().getTime() / 30000;
System.out.println("now: " + new Date());
int calculatedCode = otpUtil.calculateOtpCodeFromKey(generatedKey, t);
System.out.println("key: " + generatedKey);
System.out.println("code: " + calculatedCode);
assert otpUtil.verifyOtpCode(calculatedCode, generatedKey);
Google Authenticator: Using It With Your Own Java Authentication Server