2 Commits

Author SHA1 Message Date
Unbreathable
6676f07705 feat(4): task 2c 2026-05-15 10:15:55 +02:00
Unbreathable
62fb088403 feat(4): task 2b 2026-05-15 10:15:55 +02:00
3 changed files with 156 additions and 1 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,4 @@
*.pdf
sheet01/a2/Hash.java
*.class
*.class
passwd

View File

@@ -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<String, byte[]> 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
}
}

5
sheet04/a2/c.txt Normal file
View File

@@ -0,0 +1,5 @@
Without the time interval entering the codes would be really difficult. Imagine having only one second to enter the code + the request having to travel to some server for it to verify (can take up to 200ms around the world) + the server having to process the request and verify the code.
There could be measures to make sure the code is still valid even when entering a little old code, but then you're really just introducing an interval. So why not do it from the start?
That's why you need a 30s interval. For user experience, to make sure the system can actually work even when grandma has to type it in and reopen the authenticator 10x because she forgot the code or typed something wrong.