diff --git a/.gitignore b/.gitignore index f1e732c..485c242 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,3 @@ /target/ .classpath /.settings -/src/test/ diff --git a/src/test/java/test/OTPTest.java b/src/test/java/test/OTPTest.java new file mode 100644 index 0000000..c850198 --- /dev/null +++ b/src/test/java/test/OTPTest.java @@ -0,0 +1,108 @@ +package test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.math.BigInteger; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import org.junit.jupiter.api.Test; + +import com.cringe_studios.cringe_authenticator_library.OTP; +import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm; +import com.cringe_studios.cringe_authenticator_library.OTPType; +import com.cringe_studios.cringe_authenticator_library.impl.Base32; +import com.cringe_studios.cringe_authenticator_library.impl.TOTP; + +public class OTPTest { + public static final boolean VERBOSE = true; + + @Test + public void hotpTest() { + String secret = "3132333435363738393031323334353637383930"; // Secret = + // 0x3132333435363738393031323334353637383930 + + String base32secret = Base32.encode(hexStr2Bytes(secret)); + + String[] expectedValues = { "755224", "287082", "359152", "969429", "338314", "254676", "287922", "162583", + "399871", "520489" }; + + for (int i = 0; i < 10; i++) { + OTP testOTP1 = OTPType.HOTP.instance(base32secret, OTPAlgorithm.SHA1, 6, i, 30, false); + String calculatedPin = testOTP1.getPin(); + + if(VERBOSE) { + System.out.println(calculatedPin + " == " + expectedValues[i]); + } + + assertEquals(calculatedPin, expectedValues[i]); + } + } + + 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; + } + + public static void totpTest() { + // Seed for HMAC-SHA1 - 20 bytes + String base16seed = "3132333435363738393031323334353637383930"; + // Seed for HMAC-SHA256 - 32 bytes + String base16seed32 = "3132333435363738393031323334353637383930313233343536373839303132"; + // Seed for HMAC-SHA512 - 64 bytes + String base16seed64 = "31323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334"; + + String seed = Base32.encode(hexStr2Bytes(base16seed)); + String seed32 = Base32.encode(hexStr2Bytes(base16seed32)); + String seed64 = Base32.encode(hexStr2Bytes(base16seed64)); + + long testTime[] = { 59L, 1111111109L, 1111111111L, 1234567890L, 2000000000L, 20000000000L }; + + TOTP testOTP1 = (TOTP) OTPType.TOTP.instance(seed, OTPAlgorithm.SHA1, 8, 0, 30, false); + TOTP testOTP256 = (TOTP) OTPType.TOTP.instance(seed32, OTPAlgorithm.SHA256, 8, 0, 30, false); + TOTP testOTP512 = (TOTP) OTPType.TOTP.instance(seed64, OTPAlgorithm.SHA512, 8, 0, 30, false); + + 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) | TOTP | Mode |"); + System.out.println("+---------------+-----------------------+--------+--------+"); + + for (int i = 0; i < testTime.length; i++) { + String fmtTime = String.format("%1$-11s", testTime[i]); + String utcTime = df.format(new Date(testTime[i] * 1000)); + + System.out.println( + "| " + fmtTime + " | " + utcTime + " |" + testOTP1.getPinAt(testTime[i]) + "| SHA1 |"); + System.out.println( + "| " + fmtTime + " | " + utcTime + " |" + testOTP256.getPinAt(testTime[i]) + "| SHA256 |"); + System.out.println( + "| " + fmtTime + " | " + utcTime + " |" + testOTP512.getPinAt(testTime[i]) + "| SHA512 |"); + System.out.println("+---------------+-----------------------+--------+--------+"); + } + } catch (final Exception e) { + e.printStackTrace(); + } + } + + // @org.junit.jupiter.api.Test + // public void test() { + // hotpTest(); + // totpTest(); + // assertEquals(1, 1); + // } +} diff --git a/src/test/java/test/ogHOTP.java b/src/test/java/test/ogHOTP.java new file mode 100644 index 0000000..029f7f7 --- /dev/null +++ b/src/test/java/test/ogHOTP.java @@ -0,0 +1,209 @@ +/* + * OneTimePasswordAlgorithm.java + * OATH Initiative, + * HOTP one-time password algorithm + * + */ + +/* Copyright (C) 2004, OATH. All rights reserved. + * + * License to copy and use this software is granted provided that it + * is identified as the "OATH HOTP Algorithm" in all material + * mentioning or referencing this software or this function. + * + * License is also granted to make and use derivative works provided + * that such works are identified as + * "derived from OATH HOTP algorithm" + * in all material mentioning or referencing the derived work. + * + * OATH (Open AuTHentication) and its members make no + * representations concerning either the merchantability of this + * software or the suitability of this software for any particular + * purpose. + * + * It is provided "as is" without express or implied warranty + * of any kind and OATH AND ITS MEMBERS EXPRESSaLY DISCLAIMS + * ANY WARRANTY OR LIABILITY OF ANY KIND relating to this software. + * + * These notices must be retained in any copies of any part of this + * documentation and/or software. + */ + +package test; + +import java.io.IOException; +import java.io.File; +import java.io.DataInputStream; +import java.io.FileInputStream; +import java.lang.reflect.UndeclaredThrowableException; + +import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.security.InvalidKeyException; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +/** + * This class contains static methods that are used to calculate the One-Time + * Password (OTP) using JCE to provide the HMAC-SHA-1. + * + * @author Loren Hart + * @version 1.0 + */ +public class ogHOTP { + private ogHOTP() {} + + // These are used to calculate the check-sum digits. + // 0 1 2 3 4 5 6 7 8 9 + private static final int[] doubleDigits = { 0, 2, 4, 6, 8, 1, 3, 5, 7, 9 }; + + /** + * Calculates the checksum using the credit card algorithm. This algorithm has + * the advantage that it detects any single mistyped digit and any single + * transposition of adjacent digits. + * + * @param num the number to calculate the checksum for + * @param digits number of significant places in the number + * + * @return the checksum of num + */ + public static int calcChecksum(long num, int digits) { + boolean doubleDigit = true; + int total = 0; + while (0 < digits--) { + int digit = (int) (num % 10); + num /= 10; + if (doubleDigit) { + digit = doubleDigits[digit]; + } + total += digit; + doubleDigit = !doubleDigit; + } + int result = total % 10; + if (result > 0) { + result = 10 - result; + } + return result; + } + + /** + * This method uses the JCE to provide the HMAC-SHA-1 algorithm. HMAC computes a + * Hashed Message Authentication Code and in this case SHA1 is the hash + * algorithm used. + * + * @param keyBytes the bytes to use for the HMAC-SHA-1 key + * @param text the message or text to be authenticated. + * + * @throws NoSuchAlgorithmException if no provider makes either HmacSHA1 or + * HMAC-SHA-1 digest algorithms available. + * @throws InvalidKeyException The secret provided was not a valid + * HMAC-SHA-1 key. + * + */ + + public static byte[] hmac_sha1(byte[] keyBytes, byte[] text) throws NoSuchAlgorithmException, InvalidKeyException { + // try { + Mac hmacSha1; + try { + hmacSha1 = Mac.getInstance("HmacSHA1"); + } catch (NoSuchAlgorithmException nsae) { + hmacSha1 = Mac.getInstance("HMAC-SHA-1"); + } + SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW"); + hmacSha1.init(macKey); + return hmacSha1.doFinal(text); + // } catch (GeneralSecurityException gse) { + // throw new UndeclaredThrowableException(gse); + // } + } + + private static final int[] DIGITS_POWER + // 0 1 2 3 4 5 6 7 8 + = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 }; + + /** + * This method generates an OTP value for the given set of parameters. + * + * @param secret the shared secret + * @param movingFactor the counter, time, or other value that changes on a + * per use basis. + * @param codeDigits the number of digits in the OTP, not including the + * checksum, if any. + * @param addChecksum a flag that indicates if a checksum digit + * should be appended to the OTP. + * @param truncationOffset the offset into the MAC result to begin truncation. + * If this value is out of the range of 0 ... 15, then + * dynamic truncation will be used. Dynamic truncation + * is when the last 4 bits of the last byte of the MAC + * are used to determine the start offset. + * @throws NoSuchAlgorithmException if no provider makes either HmacSHA1 or + * HMAC-SHA-1 digest algorithms available. + * @throws InvalidKeyException The secret provided was not a valid + * HMAC-SHA-1 key. + * + * @return A numeric String in base 10 that includes {@link codeDigits} digits + * plus the optional checksum digit if requested. + */ + static public String generateOTP(byte[] secret, long movingFactor, int codeDigits, boolean addChecksum, + int truncationOffset) throws NoSuchAlgorithmException, InvalidKeyException { + // put movingFactor value into text byte array + String result = null; + int digits = addChecksum ? (codeDigits + 1) : codeDigits; + byte[] text = new byte[8]; + for (int i = text.length - 1; i >= 0; i--) { + text[i] = (byte) (movingFactor & 0xff); + movingFactor >>= 8; + } + + // compute hmac hash + byte[] hash = hmac_sha1(secret, text); + + System.out.print(Arrays.toString(hash) + " "); + + // put selected bytes into result int + int offset = hash[hash.length - 1] & 0xf; + if ((0 <= truncationOffset) && (truncationOffset < (hash.length - 4))) { + offset = truncationOffset; + } + 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]; + if (addChecksum) { + otp = (otp * 10) + calcChecksum(otp, codeDigits); + } + result = Integer.toString(otp); + while (result.length() < digits) { + result = "0" + result; + } + return result; + } + + public static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i+1), 16)); + } + return data; + } + + public static void main(String[] args) { + String Secret = "3132333435363738393031323334353637383930"; // Secret = 0x3132333435363738393031323334353637383930 + + byte[] byteSecret = hexStringToByteArray(Secret); + for(int i = 0; i < 10; i++) { + try { + System.out.println("" + i + generateOTP(byteSecret, i, 6, false, -1)); + } catch (InvalidKeyException e) { + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } + + } +} diff --git a/src/test/java/test/ogTOTP.java b/src/test/java/test/ogTOTP.java new file mode 100644 index 0000000..c9cc627 --- /dev/null +++ b/src/test/java/test/ogTOTP.java @@ -0,0 +1,212 @@ +package test; + +/** + Copyright (c) 2011 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, is permitted pursuant to, and subject to the license + terms contained in, the Simplified BSD License set forth in Section + 4.c of the IETF Trust's Legal Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info). + */ + +import java.lang.reflect.UndeclaredThrowableException; +import java.security.GeneralSecurityException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.math.BigInteger; +import java.util.TimeZone; + +/** + * This is an example implementation of the OATH TOTP algorithm. Visit + * www.openauthentication.org for more information. + * + * @author Johan Rydell, PortWise, Inc. + */ + +public class ogTOTP { + + private ogTOTP() { + } + + /** + * 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 final int[] DIGITS_POWER + // 0 1 2 3 4 5 6 7 8 + = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 }; + + /** + * This method generates a TOTP value for the given set of parameters. + * + * @param key: the shared secret, HEX encoded + * @param time: a value that reflects a time + * @param returnDigits: number of digits to return + * + * @return: a numeric String in base 10 that includes {@link truncationDigits} + * digits + */ + + public static String generateTOTP(String key, String time, String returnDigits) { + return generateTOTP(key, time, returnDigits, "HmacSHA1"); + } + + /** + * This method generates a TOTP value for the given set of parameters. + * + * @param key: the shared secret, HEX encoded + * @param time: a value that reflects a time + * @param returnDigits: number of digits to return + * + * @return: a numeric String in base 10 that includes {@link truncationDigits} + * digits + */ + + public static String generateTOTP256(String key, String time, String returnDigits) { + return generateTOTP(key, time, returnDigits, "HmacSHA256"); + } + + /** + * This method generates a TOTP value for the given set of parameters. + * + * @param key: the shared secret, HEX encoded + * @param time: a value that reflects a time + * @param returnDigits: number of digits to return + * + * @return: a numeric String in base 10 that includes {@link truncationDigits} + * digits + */ + + public static String generateTOTP512(String key, String time, String returnDigits) { + return generateTOTP(key, time, returnDigits, "HmacSHA512"); + } + + /** + * This method generates a TOTP value for the given set of parameters. + * + * @param key: the shared secret, HEX encoded + * @param time: a value that reflects a time + * @param returnDigits: number of digits to return + * @param crypto: the crypto function to use + * + * @return: a numeric String in base 10 that includes {@link truncationDigits} + * digits + */ + + 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 = hexStr2Bytes(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 = "3132333435363738393031323334353637383930"; + // 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; + long testTime[] = { 59L, 1111111109L, 1111111111L, 1234567890L, 2000000000L, 20000000000L }; + + 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("+---------------+-----------------------+" + "------------------+--------+--------+"); + + for (int i = 0; i < testTime.length; i++) { + long T = (testTime[i] - T0) / X; + steps = Long.toHexString(T).toUpperCase(); + while (steps.length() < 16) + steps = "0" + steps; + String fmtTime = String.format("%1$-11s", testTime[i]); + String utcTime = df.format(new Date(testTime[i] * 1000)); + System.out.print("| " + fmtTime + " | " + utcTime + " | " + steps + " |"); + System.out.println(generateTOTP(seed, steps, "8", "HmacSHA1") + "| SHA1 |"); + System.out.print("| " + fmtTime + " | " + utcTime + " | " + steps + " |"); + System.out.println(generateTOTP(seed32, steps, "8", "HmacSHA256") + "| SHA256 |"); + System.out.print("| " + fmtTime + " | " + utcTime + " | " + steps + " |"); + System.out.println(generateTOTP(seed64, steps, "8", "HmacSHA512") + "| SHA512 |"); + + System.out + .println("+---------------+-----------------------+" + "------------------+--------+--------+"); + } + } catch (final Exception e) { + System.out.println("Error : " + e); + } + } +} \ No newline at end of file diff --git a/src/test/java/test/ogTOTPmod1.java b/src/test/java/test/ogTOTPmod1.java new file mode 100644 index 0000000..bbcfee4 --- /dev/null +++ b/src/test/java/test/ogTOTPmod1.java @@ -0,0 +1,142 @@ +package test; + +import java.lang.reflect.UndeclaredThrowableException; +import java.security.GeneralSecurityException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.TimeZone; + +public class ogTOTPmod1 { + + private ogTOTPmod1() { + } + + /** + * 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 final int[] DIGITS_POWER = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 }; + + /** + * This method generates a TOTP value for the given set of parameters. + * + * @param key: the shared secret, HEX encoded + * @param time: a value that reflects a time + * @param returnDigits: number of digits to return + * @param crypto: the crypto function to use + * + * @return: a numeric String in base 10 that includes {@link truncationDigits} + * digits + */ + + public static String generateTOTP(String key, long timein, String crypto) { + int codeDigits = 8; + + long T = timein / 30; + + // Get the HEX in a Byte[] + byte[] msg = ByteBuffer.allocate(Long.BYTES).putLong(T).array(); + byte[] k = hexStr2Bytes(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]; + + String 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 = "3132333435363738393031323334353637383930"; + // Seed for HMAC-SHA256 - 32 bytes + String seed32 = "3132333435363738393031323334353637383930313233343536373839303132"; + // Seed for HMAC-SHA512 - 64 bytes + String seed64 = "31323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334"; + long X = 30; + long testTime[] = { 59L, 1111111109L, 1111111111L, 1234567890L, 2000000000L, 20000000000L }; + + // 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("+---------------+-----------------------+------------------+--------+--------+"); + + for (int i = 0; i < testTime.length; i++) { + + long steps = testTime[i]; + + String fmtTime = String.format("%1$-11s", testTime[i]); + String utcTime = df.format(new Date(testTime[i] * 1000)); + + System.out.println("| " + fmtTime + " | " + utcTime + " | " + steps + " |" + + generateTOTP(seed, steps, "HmacSHA1") + "| SHA1 |"); + System.out.println("| " + fmtTime + " | " + utcTime + " | " + steps + " |" + + generateTOTP(seed32, steps, "HmacSHA256") + "| SHA256 |"); + System.out.println("| " + fmtTime + " | " + utcTime + " | " + steps + " |" + + generateTOTP(seed64, steps, "HmacSHA512") + "| SHA512 |"); + + System.out + .println("+---------------+-----------------------+" + "------------------+--------+--------+"); + } + } catch (final Exception e) { + System.out.println("Error : " + e); + } + } +} \ No newline at end of file