All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nervousync.utils.OTPUtils Maven / Gradle / Ivy

There is a newer version: 1.2.1
Show newest version
/*
 * Licensed to the Nervousync Studio (NSYC) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.nervousync.utils;

import org.nervousync.commons.core.Globals;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

/**
 * OTP(One-time Password Algorithm) Utility
 *
 * @author Steven Wee	[email protected]
 * @version $Revision : 1.0 $ $Date: 2019-06-04 10:47 $
 */
public final class OTPUtils {

	private static final Logger LOGGER = LoggerFactory.getLogger(OTPUtils.class);

	//  Unit: Second
	private static final int DEFAULT_SYNC_COUNT = 30;

	//  Default 3, Maximum 17 (From Google Docs)
	private static final int DEFAULT_WINDOW_SIZE = 3;
	private static final int DEFAULT_SECRET_SIZE = 10;
	private static final String DEFAULT_SECRET_SEED = "TmVydm91c3luY0RlZmF1bHRTZWNyZXRTZWVk";
	private static final String DEFAULT_RANDOM_ALGORITHM = "SHA1PRNG";

	private OTPUtils() {
	}

	/**
	 * Calculate fixed time
	 *
	 * @param randomKey Random key
	 * @param authCode  Auth code
	 * @return Fixed time
	 */
	public static long calculateFixedTime(final String randomKey, final int authCode) {
		return calculateFixedTime(CalcType.HmacSHA1, randomKey, authCode, Globals.DEFAULT_VALUE_INT);
	}

	/**
	 * Calculate fixed time
	 *
	 * @param randomKey Random key
	 * @param authCode  Auth code
	 * @param syncCount Synchronize count
	 * @return Fixed time
	 */
	public static long calculateFixedTime(final String randomKey, final int authCode, final int syncCount) {
		return calculateFixedTime(CalcType.HmacSHA1, randomKey, authCode, syncCount);
	}

	/**
	 * Calculate fixed time
	 *
	 * @param calcType  Calculate type
	 * @param randomKey Random key
	 * @param authCode  Auth code
	 * @return Fixed time
	 */
	public static long calculateFixedTime(final CalcType calcType, final String randomKey, final int authCode) {
		return calculateFixedTime(calcType, randomKey, authCode, Globals.DEFAULT_VALUE_INT);
	}

	/**
	 * Calculate fixed time
	 *
	 * @param calcType  Calculate type
	 * @param randomKey Random key
	 * @param authCode  Auth code
	 * @param syncCount Synchronize count
	 * @return Fixed time
	 */
	public static long calculateFixedTime(final CalcType calcType, final String randomKey,
										  final int authCode, final int syncCount) {
		for (int i = -12 ; i <= 12 ; i++) {
			long fixedTime = i * 60 * 60 * 1000L;
			if (validateTOTPCode(authCode, calcType, randomKey, fixedTime, syncCount, Globals.INITIALIZE_INT_VALUE)) {
				return fixedTime;
			}
		}
		return Globals.DEFAULT_VALUE_INT;
	}

	/**
	 * Generate auth code
	 *
	 * @param secret    Secret string
	 * @return Auth code
	 */
	public static String generateTOTPCode(final String secret) {
		return generateTOTPCode(CalcType.HmacSHA1, secret, Globals.INITIALIZE_INT_VALUE, Globals.DEFAULT_VALUE_INT);
	}

	/**
	 * Generate auth code
	 *
	 * @param secret    Secret string
	 * @param fixedTime Fixed time
	 * @return Auth code
	 */
	public static String generateTOTPCode(final String secret, final long fixedTime) {
		return generateTOTPCode(CalcType.HmacSHA1, secret, fixedTime, Globals.DEFAULT_VALUE_INT);
	}

	/**
	 * Generate auth code
	 *
	 * @param secret    Secret string
	 * @param fixedTime Fixed time
	 * @param syncCount Synchronize count
	 * @return Auth code
	 */
	public static String generateTOTPCode(final String secret, final long fixedTime, final int syncCount) {
		return generateTOTPCode(CalcType.HmacSHA1, secret, fixedTime, syncCount);
	}

	/**
	 * Generate auth code
	 *
	 * @param calcType  Calculate type
	 * @param secret    Secret string
	 * @param fixedTime Fixed time
	 * @return Auth code
	 */
	public static String generateTOTPCode(final CalcType calcType, final String secret, final long fixedTime) {
		return generateTOTPCode(calcType, secret, fixedTime, Globals.DEFAULT_VALUE_INT);
	}

	/**
	 * Generate auth code
	 *
	 * @param calcType  Calculate type
	 * @param secret    Secret string
	 * @param fixedTime Fixed time
	 * @param syncCount Synchronize count
	 * @return Auth code
	 */
	public static String generateTOTPCode(final CalcType calcType, final String secret,
										  final long fixedTime, final int syncCount) {
		int authCode = OTPUtils.generateTOTPCode(calcType, secret,
				fixedTime, syncCount, Globals.INITIALIZE_INT_VALUE);
		if (authCode == Globals.DEFAULT_VALUE_INT) {
			return Globals.DEFAULT_VALUE_STRING;
		}

		StringBuilder returnCode = new StringBuilder(Integer.toString(authCode));
		while (returnCode.length() < 6) {
			returnCode.insert(0, "0");
		}
		return returnCode.toString();
	}

	/**
	 * Generate a random secret key using default algorithm, seed and seed size
	 *
	 * @return Random secret key
	 */
	public static String generateRandomKey() {
		return generateRandomKey(DEFAULT_RANDOM_ALGORITHM, DEFAULT_SECRET_SEED, Globals.DEFAULT_VALUE_INT);
	}

	/**
	 * Generate a random secret key using default algorithm and seed
	 *
	 * @param size seed size
	 * @return Random secret key
	 */
	public static String generateRandomKey(final int size) {
		return generateRandomKey(DEFAULT_RANDOM_ALGORITHM, DEFAULT_SECRET_SEED, size);
	}

	/**
	 * Generate random secret key by given algorithm, seed and seed size
	 *
	 * @param algorithm Secure algorithm
	 * @param seed      Secret seed
	 * @param size      Seed size
	 * @return Random secret key
	 */
	public static String generateRandomKey(final String algorithm, final String seed, final int size) {
		String randomKey = null;
		try {
			SecureRandom secureRandom = StringUtils.notBlank(algorithm)
					? SecureRandom.getInstance(algorithm)
					: new SecureRandom();
			if (StringUtils.notBlank(seed)) {
				secureRandom.setSeed(StringUtils.base64Decode(seed));
			}
			byte[] randomKeyBytes =
					secureRandom.generateSeed(size == Globals.DEFAULT_VALUE_INT ? DEFAULT_SECRET_SIZE : size);
			randomKey = StringUtils.base32Encode(randomKeyBytes, Boolean.FALSE);
		} catch (NoSuchAlgorithmException e) {
			LOGGER.error("Generate random key error!");
			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug("Stack message: ", e);
			}
		}
		return randomKey;
	}

	/**
	 * Generate HOTP auth code using default calculate type: HmacSHA1
	 *
	 * @param randomKey  random secret key
	 * @param randomCode random code
	 * @return generated code
	 */
	public static int generateHOTPCode(final String randomKey, final long randomCode) {
		return generateCode(CalcType.HmacSHA1, randomKey, randomCode);
	}

	/**
	 * Generate HOTP auth code
	 *
	 * @param calcType   Calculate type
	 * @param randomKey  random secret key
	 * @param randomCode random code
	 * @return generated code
	 */
	public static int generateHOTPCode(final CalcType calcType, final String randomKey, final long randomCode) {
		return generateCode(calcType, randomKey, randomCode);
	}

	/**
	 * Validate auth code by given secret key and fixed time using the default calculating type: HmacSHA1
	 *
	 * @param authCode  auth code
	 * @param randomKey random secret key
	 * @param fixedTime fixed time
	 * @return validate result
	 */
	public static boolean validateTOTPCode(final int authCode, final String randomKey, final long fixedTime) {
		return validateTOTPCode(authCode, CalcType.HmacSHA1, randomKey, fixedTime,
				Globals.DEFAULT_VALUE_INT, Globals.DEFAULT_VALUE_INT);
	}

	/**
	 * Validate auth code by given secret key, fixed time and fix windows using the default calculating type: HmacSHA1
	 *
	 * @param authCode  auth code
	 * @param randomKey random secret key
	 * @param fixedTime fixed time
	 * @param fixWindow fix window
	 * @return validate result
	 */
	public static boolean validateTOTPCode(final int authCode, final String randomKey,
	                                       final long fixedTime, final int fixWindow) {
		return validateTOTPCode(authCode, CalcType.HmacSHA1, randomKey, fixedTime,
				Globals.DEFAULT_VALUE_INT, fixWindow);
	}

	/**
	 * Validate auth code by given secret key, fixed time, synchronize count and fix window
	 *
	 * @param calcType  Calculate type
	 * @param authCode  auth code
	 * @param randomKey random secret key
	 * @param fixedTime fixed time
	 * @param syncCount synchronize count
	 * @param fixWindow fix window
	 * @return validate result
	 */
	public static boolean validateTOTPCode(final int authCode, final CalcType calcType, final String randomKey,
										   final long fixedTime, final int syncCount, final int fixWindow) {
		if (authCode > Globals.INITIALIZE_INT_VALUE) {
			int minWindow = fixWindow < 0 ? (-1 * DEFAULT_WINDOW_SIZE) : (-1 * fixWindow);
			int maxWindow = fixWindow < 0 ? DEFAULT_WINDOW_SIZE : fixWindow;
			for (int i = minWindow ; i <= maxWindow ; i++) {
				int generateCode = generateTOTPCode(calcType, randomKey, fixedTime, syncCount, i);
				if (generateCode == authCode) {
					return true;
				}
			}
		}
		return Boolean.FALSE;
	}

	/**
	 * Validate auth code by given secret key and fixed time using the default calculating type: HmacSHA1
	 *
	 * @param authCode   auth code
	 * @param randomKey  random secret key
	 * @param randomCode the random code
	 * @return validate result
	 */
	public static boolean validateHOTPCode(final int authCode, final String randomKey, final long randomCode) {
		return authCode > Globals.INITIALIZE_INT_VALUE
				? authCode == generateHOTPCode(CalcType.HmacSHA1, randomKey, randomCode)
				: Boolean.FALSE;
	}

	/**
	 * Validate auth code by given secret key and fixed time using the given calculating type
	 *
	 * @param authCode   auth code
	 * @param calcType   the calc type
	 * @param randomKey  random secret key
	 * @param randomCode the random code
	 * @return validate result
	 */
	public static boolean validateHOTPCode(final int authCode, final CalcType calcType, final String randomKey,
										   final long randomCode) {
		return authCode > Globals.INITIALIZE_INT_VALUE
				? authCode == generateHOTPCode(calcType, randomKey, randomCode)
				: Boolean.FALSE;
	}

	/**
	 * Generate auth code
	 * @param calcType      Calculate type
	 * @param randomKey     random secret key
	 * @param fixedTime     fixed time
	 * @param syncCount     synchronize count
	 * @param fixWindow     fix window
	 * @return      generated code
	 */
	private static int generateTOTPCode(final CalcType calcType, final String randomKey, final long fixedTime,
										final int syncCount, final int fixWindow) {
		long currentTime = DateTimeUtils.currentTimeMillis();
		long calcTime = (currentTime + fixedTime) / 1000L;
		if (syncCount > 0) {
			calcTime /= syncCount;
		} else {
			calcTime /= DEFAULT_SYNC_COUNT;
		}
		calcTime += fixWindow;
		return generateCode(calcType, randomKey, calcTime);
	}

	private static int generateCode(final CalcType calcType, final String randomKey, long calcTime) {
		byte[] signData = new byte[8];
		RawUtils.writeLong(signData, calcTime);
		byte[] secret = StringUtils.base32Decode(randomKey);
		byte[] hash;
		switch (calcType) {
			case HmacSHA1:
				hash = SecurityUtils.HmacSHA1(secret, signData);
				break;
			case HmacSHA256:
				hash = SecurityUtils.HmacSHA256(secret, signData);
				break;
			case HmacSHA512:
				hash = SecurityUtils.HmacSHA512(secret, signData);
				break;
			default:
				return Globals.DEFAULT_VALUE_INT;
		}
		int offset = hash[hash.length - 1] & 0xF;
		long resultCode = 0L;
		for (int i = 0 ; i < 4 ; ++i) {
			resultCode = (resultCode << 8) | (hash[offset + i] & 0xFF);
		}
		resultCode &= 0x7FFFFFFF;
		resultCode %= 1000000;
		return (int)resultCode;
	}

	/**
	 * The enum Calc type.
	 */
	public enum CalcType {
		/**
		 * Hmac sha 1 calc type.
		 */
		HmacSHA1,
		/**
		 * Hmac sha 256 calc type.
		 */
		HmacSHA256,
		/**
		 * Hmac sha 512 calc type.
		 */
		HmacSHA512
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy