package io.cos.cas.authentication.oath; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.lang.reflect.UndeclaredThrowableException; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; /** * TOTP implementation is copied from @{link https://github.com/parkghost/TOTP-authentication-demo}. * * @author Dmitriy Kopylenko * @author Unicon, inc. * @author Michael Haselton * @since 0.2 */ public final class Totp { private static final int[] DIGITS_POWER = { // 0 1 2 3 4 5 6 7 8 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 }; private static final int HASH_BUFFER_CAPACITY = 8; private static final int HASH_OFFSET_1 = 1; private static final int HASH_OFFSET_2 = 2; private static final int HASH_OFFSET_3 = 3; private static final int BITMASK_15 = 0xf; private static final int BITMASK_127 = 0x7f; private static final int BITMASK_255 = 0xff; private static final int BITWISE_AND_8 = 8; private static final int BITWISE_AND_16 = 16; private static final int BITWISE_AND_24 = 24; /** * Constructs a new instance of the time-based one time password class. */ private Totp() { } /** * This method uses the JCE to provide the crypto algorithm. HMAC computes a * Hashed Message Authentication Code with the crypto hash algorithm as a * parameter. * * @param crypto the crypto algorithm (HmacSHA1, HmacSHA256, HmacSHA512) * @param keyBytes the bytes to use for the HMAC key * @param text the message or text to be authenticated * @return the HMAC sha */ private static byte[] hmacSha(final String crypto, final byte[] keyBytes, final byte[] text) { try { final Mac hmac = Mac.getInstance(crypto); final SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW"); hmac.init(macKey); return hmac.doFinal(text); } catch (final GeneralSecurityException e) { throw new UndeclaredThrowableException(e); } } /** * This method generates a TOTP value for the given set of parameters. * * @param key the shared secret * @param time a value that reflects a time * @param digits number of digits to return * @param crypto the crypto function to use * * @return digits */ public static int generateTotp(final byte[] key, final long time, final int digits, final String crypto) { final byte[] msg = ByteBuffer.allocate(HASH_BUFFER_CAPACITY).putLong(time).array(); final byte[] hash = hmacSha(crypto, key, msg); // put selected bytes into result int final int offset = hash[hash.length - 1] & BITMASK_15; int binary = ((hash[offset] & BITMASK_127) << BITWISE_AND_24); binary = binary | ((hash[offset + HASH_OFFSET_1] & BITMASK_255) << BITWISE_AND_16); binary = binary | ((hash[offset + HASH_OFFSET_2] & BITMASK_255) << BITWISE_AND_8); binary = binary | (hash[offset + HASH_OFFSET_3] & BITMASK_255); return binary % DIGITS_POWER[digits]; } }