6

我目前正在尝试重新创建 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);
}

更新:我尝试从 Andrew Rueckert 的响应链接以及这个https://github.com/wstrange/GoogleAuth/blob/master/src/main/java/com/warrenstrange/googleauth/GoogleAuthenticator.java和来自RFC 4226. 这些都没有给我一个正确的OTP

任何人都可以启发我吗?

4

2 回答 2

3

Your byte value[] needs to be the byte representation of the time as a long, and it looks like it's currently the byte representation of that number as a String of digit characters. Instead of

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);

You'd want something like:

// 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);

I managed to get a Google TOTP implementation set up by following this guide, if you want one more resource to look into.

于 2016-11-01T22:44:11.230 回答
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

于 2016-12-02T10:07:35.127 回答