From 62fb088403dc6c9dd95b3e6e20012dea071a87c2 Mon Sep 17 00:00:00 2001 From: Unbreathable <70802809+Unbreathable@users.noreply.github.com> Date: Fri, 15 May 2026 10:11:42 +0200 Subject: [PATCH] feat(4): task 2b --- .gitignore | 3 +- sheet04/a2/AuthWithTOTP.java | 149 +++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 sheet04/a2/AuthWithTOTP.java diff --git a/.gitignore b/.gitignore index 133b45b..8a88443 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.pdf sheet01/a2/Hash.java -*.class \ No newline at end of file +*.class +passwd diff --git a/sheet04/a2/AuthWithTOTP.java b/sheet04/a2/AuthWithTOTP.java new file mode 100644 index 0000000..91eb6e2 --- /dev/null +++ b/sheet04/a2/AuthWithTOTP.java @@ -0,0 +1,149 @@ +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.time.Instant; +import java.util.HexFormat; +import java.util.Map; +import java.util.Scanner; +import java.util.stream.Collectors; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +public class AuthWithTOTP { + + private static final byte[] INVALID_HASH = + "----------------------------------------------------------------".getBytes(); + + // hex-encoded: 3c2bc45f2de6568bb285aa1c6fcac1b6965cc770 + // base32-encoded: HQV4IXZN4ZLIXMUFVIOG7SWBW2LFZR3Q + private static final byte[] K = new byte[] { + 60, + 43, + -60, + 95, + 45, + -26, + 86, + -117, + -78, + -123, + -86, + 28, + 111, + -54, + -63, + -74, + -106, + 92, + -57, + 112, + }; + + public static void main(String[] args) { + // I changed it to a scanner cause my terminal had issues with the other thingy + try (Scanner sc = new Scanner(System.in)) { + Map passwd = Files.readAllLines(Path.of("passwd")) + .stream() + .filter(line -> line.indexOf(":") > 1 && line.length() > 3) + .collect( + Collectors.toMap( + line -> line.substring(0, line.indexOf(':')), + line -> + HexFormat.of().parseHex( + line.substring(line.indexOf(':') + 1) + ) + ) + ); + + System.out.println( + "Chocolate Factory SCADA Command Line Interface v2.2.144" + ); + System.out.println(); + System.out.println( + "Please, enter your authentication credentials." + ); + System.out.println(); + + String username; + String password; + String totpCode; + + long timeout = 500; + while (true) { + System.out.print("> Username: "); + username = sc.nextLine(); + System.out.print("> Password: "); + password = sc.nextLine(); + System.out.print("> TOTP Code: "); + totpCode = sc.nextLine(); + + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] encodedHash = digest.digest(password.getBytes()); + + // constant time comparison to prevent timing attacks + if ( + MessageDigest.isEqual( + passwd.getOrDefault(username, INVALID_HASH), + encodedHash + ) + ) { + // Get the counter from the unix seconds + final var counter = (int) Math.floor( + Instant.now().getEpochSecond() / 30.0 + ); + + // Compute the hmac + final var mac = Mac.getInstance("HmacSHA1"); + mac.init(new SecretKeySpec(K, "HmacSHA1")); + mac.update(ByteBuffer.allocate(8).putLong(counter).array()); + final var hmacResult = mac.doFinal(); + + // Do the truncating + modulo + int offset = hmacResult[19] & 0x0f; + int binaryCode = + ((hmacResult[offset] & 0x7f) << 24) | + ((hmacResult[offset + 1] & 0xff) << 16) | + ((hmacResult[offset + 2] & 0xff) << 8) | + (hmacResult[offset + 3] & 0xff); + binaryCode = binaryCode % 1000000; + + // Validate the code + padding + final var code = String.format("%06d", binaryCode); + if (!code.equals(totpCode)) { + System.out.println( + "Invalid username, password and/or TOTP code." + ); + Thread.sleep(timeout); + timeout *= 2; + continue; + } + + System.out.printf("Welcome %s!%n", username); + Thread.sleep(150); + break; + } else { + // exponential timeout to prevent brute force attacks + System.out.println( + "Invalid username, password and/or TOTP code." + ); + Thread.sleep(timeout); + timeout *= 2; + } + } + + printSystemStatus(); + printSecretRecipe(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void printSystemStatus() throws Exception { + // SECRET + } + + private static void printSecretRecipe() throws Exception { + // SECRET + } +}