Google OTP Generation Java
В настоящее время я пытаюсь воссоздать генератор одноразовых паролей Google. Я использую общий секрет, сгенерированный при настройке Google Authenticator. Я попытался по-настоящему взглянуть на источники Google Authenticator и по всему Интернету, и я обнаружил много общего с моим кодом, но не могу найти, где я ошибаюсь. Первая часть кажется правильной. Что касается HMAC, я не думаю, что я мог бы здесь запутаться, но я могу ошибаться. Часть усечения все еще немного размыта для меня, и я пробовал много разных реализаций, но я просто не могу получить работающий OTP. (Я использую Google Authenticator для сравнения результатов)
private String truncateHash(byte[] hash) {
int offset = hash[hash.length - 1] & 0xF;
long truncatedHash = 0;
for (int i = 0; i < 4; ++i) {
truncatedHash <<= 8;
truncatedHash |= (hash[offset + i] & 0xFF);
}
truncatedHash &= 0x7FFFFFFF;
truncatedHash %= 1000000;
int code = (int) truncatedHash;
String result = Integer.toString(code);
for (int i = result.length(); i < 6; i++) {
result = "0" + result;
}
return result;
}
private byte[] hmacSha1(byte[] value, byte[] keyBytes) {
try {
Mac mac = HmacUtils.getHmacSha1(keyBytes);
byte[] rawHmac = mac.doFinal(value);
return new Hex().encode(rawHmac);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String GoogleAuthenticatorCode(String secret) throws UnsupportedEncodingException {
Base32 base = new Base32();
byte[] key = base.decode(secret);
//Update from Andrew Rueckert's response
long value = new Date().getTime() / TimeUnit.SECONDS.toMillis(30);
byte[] data = new byte[8];
for (int i = 8; i-- > 0; value >>>= 8) {
data[i] = (byte) value;
}
//
System.out.println("Time remaining : " + new Date().getTime() / 1000 % 30);
byte[] hash = hmacSha1(data, key);
return truncateHash(hash);
}
ОБНОВЛЕНИЕ: я попытался скопировать и вставить код из ссылки ответа Эндрю Рюккерта, а также этой https://github.com/wstrange/GoogleAuth/blob/master/src/main/java/com/warrenstrange/googleauth/GoogleAuthenticator.java
и один из RFC 4226
, Ни один из них не дает мне правильный OTP
Может ли кто-нибудь просветить меня, пожалуйста?
2 ответа
Я решил свою проблему, поэтому я решил опубликовать ее там на тот случай, если это кому-то понадобится.
Это было частично из-за класса Base32, который я использовал, который не возвращал правильный ключ. Усечение тоже не было правильным.
Он совместим с приложением Google Authenticator.
import org.apache.commons.codec.binary.Hex;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class Authentication {
Authentication() {};
private String truncateHash(byte[] hash) {
String hashString = new String(hash);
int offset = Integer.parseInt(hashString.substring(hashString.length() - 1, hashString.length()), 16);
String truncatedHash = hashString.substring(offset * 2, offset * 2 + 8);
int val = Integer.parseUnsignedInt(truncatedHash, 16) & 0x7FFFFFFF;
String finalHash = String.valueOf(val);
finalHash = finalHash.substring(finalHash.length() - 6, finalHash.length());
return finalHash;
}
private byte[] hmacSha1(byte[] value, byte[] keyBytes) {
SecretKeySpec signKey = new SecretKeySpec(keyBytes, "HmacSHA1");
try {
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signKey);
byte[] rawHmac = mac.doFinal(value);
return new Hex().encode(rawHmac);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String GoogleAuthenticatorCode(String secret) throws Exception {
if (secret == null || secret == "") {
throw new Exception("Secret key does not exist.");
}
long value = new Date().getTime() / TimeUnit.SECONDS.toMillis(30);
Base32 base = new Base32(Base32.Alphabet.BASE32, false, true);
byte[] key = base.fromString(secret);
byte[] data = new byte[8];
for (int i = 8; i-- > 0; value >>>= 8) {
data[i] = (byte) value;
}
byte[] hash = hmacSha1(data, key);
return truncateHash(hash);
}
}
Base32, который я использовал, доступен здесь, если необходимо, вместе с остальной частью проекта:
https://github.com/Poncholay/OTPGenerator/blob/master/src/main/java/com/requireris/app/web/rest/Base32.java
Ваш byte value[]
должно быть байтовым представлением времени в виде long, и похоже, что в настоящее время это байтовое представление этого числа в виде строки цифр. Вместо
Double time = floor(new Date().getTime() / 1000 / 30);
String message = String.valueOf(time.intValue());
byte[] value = message.getBytes("UTF-8");
byte[] hash = hmacSha1(value, key);
Вы хотели бы что-то вроде:
// decimal truncation is free when dealing with int/long
long value = new Date().getTime() / 1000 / 30;
byte[] data = new byte[8];
for (int i = 8; i-- > 0; value >>>= 8) {
data[i] = (byte) value;
}
byte[] hash = hmacSha1(data, key);
Мне удалось настроить реализацию TOTP Google, следуя этому руководству, если вы хотите, чтобы еще один ресурс был изучен.