Search code examples
javahmacsha1google-authenticatorone-time-password

Google OTP Generation Java


I am currently trying to recreate a Google One Time Password generator. I use a shared secret generated when I setup Google Authenticator. I tried looking into the Google Authenticator sources and all around the internet really and I find a lot of similarities with my code but I can't really find where i'm wrong. The first part seems correct. As for the hmac, I don't think I could mess up here but I might be wrong. The truncating part is still a bit blurry for me and I tried a lot of different implementations but I just cannot get a working OTP. (I'm using Google Authenticator to compare the results)

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

UPDATE : I tried copying and pasting the code from Andrew Rueckert's response's link as well as this one https://github.com/wstrange/GoogleAuth/blob/master/src/main/java/com/warrenstrange/googleauth/GoogleAuthenticator.java and the one from RFC 4226. Neither of these give me a correct OTP

Can anyone enlighten me please?


Solution

  • I solved my problem so I thought I would post it there in case someone needs it.
    It was partialy due to the Base32 class I was using which didn't return a correct key. The truncating wasn't correct either.

    It's compatible with Google Authenticator app.

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

    The Base32 I used is available here if needed along with the rest of the project :
    https://github.com/Poncholay/OTPGenerator/blob/master/src/main/java/com/requireris/app/web/rest/Base32.java