Implementing TOTP in Java: A Simple Example
TOTP (Time-based One-Time Password) is commonly used for two-factor authentication. This implementation demonstrates how to generate TOTP using the HMAC algorithm with SHA1, SHA256, or SHA512.
Key Concepts:
- HMAC (Hash-based Message Authentication Code): A cryptographic algorithm that uses a secret key and a hash function to ensure data integrity.
- TOTP: A method for generating one-time passwords based on the current time, making it time-sensitive.
Code Breakdown:
HMAC Calculation:
hmac_sha()
: Computes HMAC using the given algorithm (HmacSHA1
,HmacSHA256
, orHmacSHA512
).- Example:
byte[] hmacResult = hmac_sha("HmacSHA1", key, message);
2. Hex to Byte Conversion:
hexStr2Bytes()
: Converts a hexadecimal string to a byte array for processing.- Example:
byte[] bytes = hexStr2Bytes("1234567890ABCDEF");
3. TOTP Generation:
generateTOTP()
: Generates a TOTP based on the key, time, and the desired length of the OTP.- Example:
String otp = generateTOTP("HSBSHEUUEHHEHQEW", "1234567890", "6", "HmacSHA1");
4. How it works:
- Step 1: Time is divided into intervals (
X = 30 seconds
in the example). - Step 2: The current time is hashed using HMAC with the secret key.
- Step 3: Extract a portion of the hash and apply modulo to get a number with the required number of digits (e.g., 6 digits).
5. Output Example:
- The result is a dynamically generated OTP valid for a short time window.
- Example output
| Time(sec) | Time (UTC format) | Value of T(Hex) | TOTP | Mode |
| 1609459200 | 2024-01-01 00:00:00 | 0X000000000000 | 123456 | SHA1 |
Full sample code
import java.lang.reflect.UndeclaredThrowableException;
import java.security.GeneralSecurityException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Date;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.util.TimeZone;
import org.apache.commons.codec.binary.Base32;
/**
* This is an example implementation of the OATH
* TOTP algorithm.
* Visit www.openauthentication.org for more information.
*
* @author Johan Rydell, PortWise, Inc.
*/
public class TOTP {
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
*/
private static byte[] hmac_sha(String crypto, byte[] keyBytes,
byte[] text) {
try {
Mac hmac;
hmac = Mac.getInstance(crypto);
SecretKeySpec macKey =
new SecretKeySpec(keyBytes, "RAW");
hmac.init(macKey);
return hmac.doFinal(text);
} catch (GeneralSecurityException gse) {
throw new UndeclaredThrowableException(gse);
}
}
/**
* This method converts a HEX string to Byte[]
*
* @param hex: the HEX string
* @return: a byte array
*/
private static byte[] hexStr2Bytes(String hex) {
// Adding one byte to get the right conversion
// Values starting with "0" can be converted
byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();
// Copy all the REAL bytes, not the "first"
byte[] ret = new byte[bArray.length - 1];
for (int i = 0; i < ret.length; i++)
ret[i] = bArray[i + 1];
return ret;
}
private static byte[] decodeBase32(String base32) {
Base32 base32Codec = new Base32();
return base32Codec.decode(base32);
}
private static final int[] DIGITS_POWER
// 0 1 2 3 4 5 6 7 8
= {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
public static String generateTOTP(String key,
String time,
String returnDigits) {
return generateTOTP(key, time, returnDigits, "HmacSHA1");
}
public static String generateTOTP256(String key,
String time,
String returnDigits) {
return generateTOTP(key, time, returnDigits, "HmacSHA256");
}
public static String generateTOTP512(String key,
String time,
String returnDigits) {
return generateTOTP(key, time, returnDigits, "HmacSHA512");
}
public static String generateTOTP(String key,
String time,
String returnDigits,
String crypto) {
int codeDigits = Integer.decode(returnDigits).intValue();
String result = null;
// Using the counter
// First 8 bytes are for the movingFactor
// Compliant with base RFC 4226 (HOTP)
while (time.length() < 16)
time = "0" + time;
// Get the HEX in a Byte[]
byte[] msg = hexStr2Bytes(time);
byte[] k = decodeBase32(key);
byte[] hash = hmac_sha(crypto, k, msg);
// put selected bytes into result int
int offset = hash[hash.length - 1] & 0xf;
int binary =
((hash[offset] & 0x7f) << 24) |
((hash[offset + 1] & 0xff) << 16) |
((hash[offset + 2] & 0xff) << 8) |
(hash[offset + 3] & 0xff);
int otp = binary % DIGITS_POWER[codeDigits];
result = Integer.toString(otp);
while (result.length() < codeDigits) {
result = "0" + result;
}
return result;
}
public static void main(String[] args) {
// Seed for HMAC-SHA1 - 20 bytes
String seed = "HSBSHEUUEHHEHQEW";
// Seed for HMAC-SHA256 - 32 bytes
String seed32 = "3132333435363738393031323334353637383930" +
"313233343536373839303132";
// Seed for HMAC-SHA512 - 64 bytes
String seed64 = "3132333435363738393031323334353637383930" +
"3132333435363738393031323334353637383930" +
"3132333435363738393031323334353637383930" +
"31323334";
long T0 = 0;
long X = 30;
String steps = "0";
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
df.setTimeZone(TimeZone.getTimeZone("UTC"));
try {
System.out.println(
"+---------------+-----------------------+" +
"------------------+--------+--------+");
System.out.println(
"| Time(sec) | Time (UTC format) " +
"| Value of T(Hex) | TOTP | Mode |");
System.out.println(
"+---------------+-----------------------+" +
"------------------+--------+--------+");
Instant now = Instant.now();
for (int i = 0; i < 100; i++) {
Instant timeTest = now.plusSeconds(i);
long T = (timeTest.getEpochSecond()) / X;
steps = Long.toHexString(T).toUpperCase();
while (steps.length() < 16) steps = "0" + steps;
String fmtTime = String.format("%1$-11s", timeTest.getEpochSecond());
String utcTime = df.format(new Date(timeTest.getEpochSecond()));
System.out.print("| " + timeTest + " | " + utcTime +
" | " + steps + " |");
System.out.println(generateTOTP(seed, steps, "6",
"HmacSHA1") + "| SHA1 |");
// System.out.print("| " + fmtTime + " | " + utcTime +
// " | " + steps + " |");
// System.out.println(generateTOTP(seed32, steps, "6",
// "HmacSHA256") + "| SHA256 |");
// System.out.print("| " + fmtTime + " | " + utcTime +
// " | " + steps + " |");
// System.out.println(generateTOTP(seed64, steps, "6",
// "HmacSHA512") + "| SHA512 |");
System.out.println(
"+---------------+-----------------------+" +
"------------------+--------+--------+");
}
} catch (final Exception e) {
System.out.println("Error : " + e);
}
}
}
Conclusion:
This implementation of TOTP in Java is useful for securing applications that require two-factor authentication. By using HMAC with time-based intervals, you can generate reliable and temporary OTPs for users.
Hashtags: #JavaSecurity #TOTP #TwoFactorAuthentication
https://datatracker.ietf.org/doc/html/rfc6238
Comments
Post a Comment