/** * Calculates the verification code of the provided key at the specified instant of time using the * algorithm specified in RFC 6238. * * @param key the secret key in binary format. * @param tm the instant of time. * @return the validation code for the provided key at the specified instant of time. */ public static int calculateCode(byte[] key, long tm) { // Allocating an array of bytes to represent the specified instant // of time. byte[] data = new byte[8]; long value = tm; // Converting the instant of time from the long representation to a // big-endian array of bytes (RFC4226, 5.2. Description). for (int i = 8; i-- > 0; value >>>= 8) { data[i] = (byte) value; } // Building the secret key specification for the HmacSHA1 algorithm. SecretKeySpec signKey = new SecretKeySpec(key, HMAC_HASH_FUNCTION); try { // Getting an HmacSHA1 algorithm implementation from the JCE. Mac mac = Mac.getInstance(HMAC_HASH_FUNCTION); // Initializing the MAC algorithm. mac.init(signKey); // Processing the instant of time and getting the encrypted data. [!!!] byte[] hash = mac.doFinal(data); // Building the validation code performing dynamic truncation // (RFC4226, 5.3. Generating an HOTP value) int offset = hash[hash.length - 1] & 0xF; // System.out.println(offset); // We are using a long because Java hasn't got an unsigned integer type // and we need 32 unsigned bits). long truncatedHash = 0; for (int i = 0; i < 4; ++i) { truncatedHash <<= 8; // Java bytes are signed but we need an unsigned integer: // cleaning off all but the LSB. truncatedHash |= (hash[offset + i] & 0xFF); } // Cleaning bits higher than the 32nd and calculating the module with the // maximum validation code value. truncatedHash &= 0x7FFFFFFF; truncatedHash %= SECRET_KEY_MODULE; // Returning the validation code to the caller. return (int) truncatedHash; } catch (NoSuchAlgorithmException | InvalidKeyException ex) { // Logging the exception. LOGGER.log(Level.SEVERE, ex.getMessage(), ex); // We're not disclosing internal error details to our clients. throw new GoogleAuthenticatorException("The operation cannot be " + "performed now."); } }