From def350d1dabf307b922d0c59261e85984b4a8cea Mon Sep 17 00:00:00 2001 From: TheArrayser Date: Sat, 17 Jun 2023 17:07:19 +0200 Subject: [PATCH] Added weird shit... idk if it works, Tests don't work yet --- .gitignore | 2 +- .../cringe_authenticator_library/OTP.java | 41 +++++++---- .../OTPAlgorithm.java | 34 +++++++-- .../cringe_authenticator_library/OTPType.java | 18 +++-- .../impl/HOTP.java | 58 +++++++++++++-- .../impl/HOTPChecksumProvider.java | 28 ++++++++ .../impl/TOTP.java | 70 ++++++++++++++++--- 7 files changed, 212 insertions(+), 39 deletions(-) create mode 100644 src/com/cringe_studios/cringe_authenticator_library/impl/HOTPChecksumProvider.java diff --git a/.gitignore b/.gitignore index 23dda6d..f1e732c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ /target/ .classpath /.settings - +/src/test/ diff --git a/src/com/cringe_studios/cringe_authenticator_library/OTP.java b/src/com/cringe_studios/cringe_authenticator_library/OTP.java index 4111f5e..d36ae1d 100644 --- a/src/com/cringe_studios/cringe_authenticator_library/OTP.java +++ b/src/com/cringe_studios/cringe_authenticator_library/OTP.java @@ -1,15 +1,26 @@ package com.cringe_studios.cringe_authenticator_library; -public interface OTP { +public abstract class OTP { //TOTP //HOTP protected String secret; protected OTPType type; protected OTPAlgorithm algorithm; - protected long digits; + protected int digits; protected long counter; protected long period; + protected boolean checksum; + + protected OTP(OTPType nType, String nSecret, OTPAlgorithm nAlgorithm, int nDigits, long nCounter, long nPeriodInSeconds, boolean nChecksum) { + this.secret = nSecret; + type = nType; + algorithm = nAlgorithm; + digits = nDigits; + counter = nCounter; + period = nPeriodInSeconds; + checksum = nChecksum; + } //secret: required //algorithm: optional (default SHA1) @@ -17,37 +28,39 @@ public interface OTP { //OTPType: required //period: optional (default 30) //counter: required if hotp - public static OTP createNewOTP(OTPType type, String secret, OTPAlgorithm algorithm, long digits, long counter, long periodInSeconds) { - return null; + public static OTP createNewOTP(OTPType type, String secret, OTPAlgorithm algorithm, int digits, long counter, long periodInSeconds, boolean checksum) { + return type.instance(secret, algorithm, digits, counter, periodInSeconds, checksum); } - public long getNextDueTime(); + public abstract long getNextDueTime(); - public String getPin(); + public abstract String getPin(); - public String updatePin(); - - default public String getSecret() { + public String getSecret() { return this.secret; } - default public OTPType getOTPType() { + public OTPType getOTPType() { return this.type; } - default public OTPAlgorithm getAlgorithm() { + public OTPAlgorithm getAlgorithm() { return algorithm; } - default public long getDigits() { + public int getDigits() { return this.digits; } - default public long getCounter() { + public long getCounter() { return this.counter; } - default public long getPeriod() { + public long getPeriod() { return this.period; } + + public boolean hasChecksum() { + return checksum; + } } \ No newline at end of file diff --git a/src/com/cringe_studios/cringe_authenticator_library/OTPAlgorithm.java b/src/com/cringe_studios/cringe_authenticator_library/OTPAlgorithm.java index f2ea61b..964a087 100644 --- a/src/com/cringe_studios/cringe_authenticator_library/OTPAlgorithm.java +++ b/src/com/cringe_studios/cringe_authenticator_library/OTPAlgorithm.java @@ -1,13 +1,35 @@ package com.cringe_studios.cringe_authenticator_library; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + public enum OTPAlgorithm{ - SHA1, - SHA224, - SHA256, - SHA384, - SHA512; + SHA1("HmacSHA1"), + SHA224("HmacSHA224"), + SHA256("HmacSHA256"), + SHA384("HmacSHA384"), + SHA512("HmacSHA512"); + + private String javaName; + + private OTPAlgorithm(String nJavaName) { + javaName = nJavaName; + } public String getJavaName() { - return this.name(); + return javaName; + } + + public byte[] hash(byte[] keyBytes, byte[] text) throws InvalidKeyException, NoSuchAlgorithmException { + Mac hmac; + hmac = Mac.getInstance(this.getJavaName()); + + SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW"); + + hmac.init(macKey); + return hmac.doFinal(text); } } \ No newline at end of file diff --git a/src/com/cringe_studios/cringe_authenticator_library/OTPType.java b/src/com/cringe_studios/cringe_authenticator_library/OTPType.java index 4153455..7100ed5 100644 --- a/src/com/cringe_studios/cringe_authenticator_library/OTPType.java +++ b/src/com/cringe_studios/cringe_authenticator_library/OTPType.java @@ -3,14 +3,24 @@ package com.cringe_studios.cringe_authenticator_library; public enum OTPType { HOTP("HMAC-based One-Time Password"), TOTP("Time-based One-Time Password"); - + private String friendlyName; - - private OTPType(String nFriendlyName){ + + private OTPType(String nFriendlyName) { friendlyName = nFriendlyName; } - + public String getFriendlyName() { return friendlyName; } + + public OTP instance(String secret, OTPAlgorithm algorithm, int digits, long counter, long periodInSeconds, boolean checksum) { + switch(this) { + case HOTP: + return new com.cringe_studios.cringe_authenticator_library.impl.HOTP(secret, algorithm, digits, counter, periodInSeconds, checksum); + case TOTP: + return new com.cringe_studios.cringe_authenticator_library.impl.TOTP(secret, algorithm, digits, counter, periodInSeconds, checksum); + default: return null; + } + } } diff --git a/src/com/cringe_studios/cringe_authenticator_library/impl/HOTP.java b/src/com/cringe_studios/cringe_authenticator_library/impl/HOTP.java index e86813c..5e13bc0 100644 --- a/src/com/cringe_studios/cringe_authenticator_library/impl/HOTP.java +++ b/src/com/cringe_studios/cringe_authenticator_library/impl/HOTP.java @@ -1,13 +1,28 @@ package com.cringe_studios.cringe_authenticator_library.impl; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + import javax.crypto.Mac; import javax.crypto.MacSpi; +import javax.crypto.spec.SecretKeySpec; import javax.xml.crypto.dsig.spec.HMACParameterSpec; import com.cringe_studios.cringe_authenticator_library.OTP; +import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm; +import com.cringe_studios.cringe_authenticator_library.OTPType; //https://datatracker.ietf.org/doc/html/rfc4226 -public class HOTP implements OTP{ +public class HOTP extends OTP { + + protected static final int[] DIGITS_POWER + // 0 1 2 3 4 5 6 7 8 + = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 }; + + public HOTP(String secret, OTPAlgorithm algorithm, int digits, long counter, long periodInSeconds, boolean checksum) { + super(OTPType.HOTP, secret, algorithm, digits, counter, periodInSeconds, checksum); + } @Override public long getNextDueTime() { @@ -20,10 +35,41 @@ public class HOTP implements OTP{ return null; } - @Override - public String updatePin() { - // TODO Auto-generated method stub - return null; + public String generateOTP(int truncationOffset) throws NoSuchAlgorithmException, InvalidKeyException { + boolean addChecksum = this.hasChecksum(); + byte[] secret = this.getSecret().getBytes(StandardCharsets.US_ASCII); + int codeDigits = this.getDigits(); + long movingFactor = this.getCounter(); + + // 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 = this.getAlgorithm().hash(secret, text); + + // 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) + HOTPChecksumProvider.calcChecksum(otp, codeDigits); + } + result = Integer.toString(otp); + while (result.length() < digits) { + result = "0" + result; + } + return result; } - + } diff --git a/src/com/cringe_studios/cringe_authenticator_library/impl/HOTPChecksumProvider.java b/src/com/cringe_studios/cringe_authenticator_library/impl/HOTPChecksumProvider.java new file mode 100644 index 0000000..b4801ef --- /dev/null +++ b/src/com/cringe_studios/cringe_authenticator_library/impl/HOTPChecksumProvider.java @@ -0,0 +1,28 @@ +package com.cringe_studios.cringe_authenticator_library.impl; + +public final class HOTPChecksumProvider { + // These are used to calculate the check-sum digits. 0 1 2 3 4 5 6 7 8 9 + private static final int[] DOUBLE_DIGITS = { 0, 2, 4, 6, 8, 1, 3, 5, 7, 9 }; + + private HOTPChecksumProvider() {} + + 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 = DOUBLE_DIGITS[digit]; + } + total += digit; + doubleDigit = !doubleDigit; + } + int result = total % 10; + if (result > 0) { + result = 10 - result; + } + return result; + } + +} diff --git a/src/com/cringe_studios/cringe_authenticator_library/impl/TOTP.java b/src/com/cringe_studios/cringe_authenticator_library/impl/TOTP.java index 573a009..a34ee29 100644 --- a/src/com/cringe_studios/cringe_authenticator_library/impl/TOTP.java +++ b/src/com/cringe_studios/cringe_authenticator_library/impl/TOTP.java @@ -1,16 +1,30 @@ package com.cringe_studios.cringe_authenticator_library.impl; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.time.Instant; + import com.cringe_studios.cringe_authenticator_library.OTP; +import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm; import com.cringe_studios.cringe_authenticator_library.OTPType; //https://datatracker.ietf.org/doc/html/rfc6238 -public class TOTP implements OTP{ - +public class TOTP extends HOTP { + + public TOTP(String secret, OTPAlgorithm algorithm, int digits, long counter, long periodInSeconds, boolean checksum) { + super(secret, algorithm, digits, counter, periodInSeconds, checksum); + this.type = OTPType.TOTP; + } + @Override public long getNextDueTime() { // TODO Auto-generated method stub - //return this.period System.time; - return 0; + // e.g. current time: 20 seconds & current period: 30 seconds -> next due time: + // 30 seconds + return (this.getCounter() + 1) * this.getPeriod(); } @Override @@ -20,9 +34,49 @@ public class TOTP implements OTP{ } @Override - public String updatePin() { - // TODO Auto-generated method stub - return null; + public long getCounter() { + return Instant.now().getEpochSecond() / this.getPeriod(); + + } + + 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 String getPinAt(String key, String time) throws InvalidKeyException, NoSuchAlgorithmException { + int codeDigits = this.getDigits(); + + // 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 = this.getAlgorithm().hash(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; } - } \ No newline at end of file