Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ log.txt
secureData*.txt
logfile.txt
rmc_log.txt
logfileConf.txt
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## 2026-02-04 - Timing Attacks in Hash/Token Verification
**Vulnerability:** Use of `equalsIgnoreCase` or `equals` for comparing sensitive hashes and tokens (Passwords, session cookies, etc.) allows for timing attacks.
**Learning:** These side-channel vulnerabilities were widespread in the codebase's authentication and token management modules.
**Prevention:** Always use `java.security.MessageDigest.isEqual` for constant-time comparison of sensitive data.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import org.bouncycastle.crypto.digests.SHA256Digest;
import it.richkmeli.jframework.util.TypeConverter;

import java.nio.charset.StandardCharsets;

public class SHA256 {
public static byte[] hash(byte[] input) {
SHA256Digest digest = new SHA256Digest();
Expand All @@ -15,7 +17,7 @@ public static byte[] hash(byte[] input) {

// sha256: string to hex
public static String hash(String input) {
return TypeConverter.bytesToHex(hash(input.getBytes()));
return TypeConverter.bytesToHex(hash(input.getBytes(StandardCharsets.UTF_8)));
}

public static String hashToString(byte[] input) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import it.richkmeli.jframework.util.RandomStringGenerator;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;

public class PasswordManager {
Expand All @@ -26,20 +28,20 @@ public static String hashPassword(String password, boolean saltEnabled) {

//System.out.println("hashPassword, saltS: " + saltS + " " + saltS.length() + " | hashedPassword: " + hashedPassword + " " + hashedPassword.length());
String out = saltS + hashedPassword;
return Base64.getUrlEncoder().encodeToString(out.getBytes(Charset.defaultCharset()));
return Base64.getUrlEncoder().encodeToString(out.getBytes(StandardCharsets.UTF_8));
}

// hashedPassword = db password, hashedSaltPassword = login password
public static boolean verifyPassword(String hashedPassword, String hashedSaltPassword) {
String decodedHashedPassword = new String(Base64.getUrlDecoder().decode(hashedPassword));
String decodedHashedSaltPassword = new String(Base64.getUrlDecoder().decode(hashedSaltPassword));
String decodedHashedPassword = new String(Base64.getUrlDecoder().decode(hashedPassword), StandardCharsets.UTF_8);
String decodedHashedSaltPassword = new String(Base64.getUrlDecoder().decode(hashedSaltPassword), StandardCharsets.UTF_8);
String salt = decodedHashedSaltPassword.substring(0, 9);
String hashSP = decodedHashedSaltPassword.substring(9);
String hashP = decodedHashedPassword.substring(9);

//System.out.println("verifyPassword, saltS: " + salt + " " + salt.length() + " | hashedSaltPassword: " + hashSP + " " + hashSP.length());
String hp = SHA256.hash(hashP + salt);

return hashSP.equalsIgnoreCase(hp);
return MessageDigest.isEqual(hashSP.getBytes(StandardCharsets.UTF_8), hp.getBytes(StandardCharsets.UTF_8));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import it.richkmeli.jframework.crypto.exception.CryptoException;
import it.richkmeli.jframework.util.RandomStringGenerator;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand All @@ -29,7 +31,7 @@ public static boolean verifyNumericCompact(String numericCompactToken, String va
String extendedToken = generate(extractedNumericSalt + value, false);
// put extractedNumericSalt at first chars of extendedToken
extendedToken = reduceToNumeric(extractedNumericSalt + extendedToken, numericCompactToken.length());
return numericCompactToken.equalsIgnoreCase(extendedToken);
return MessageDigest.isEqual(numericCompactToken.getBytes(StandardCharsets.UTF_8), extendedToken.getBytes(StandardCharsets.UTF_8));
}

public static String generate(String value) {
Expand Down Expand Up @@ -109,7 +111,7 @@ public static boolean verifyTemporized(String token, String value, int minutesOf
hashed = token;
calculatedHash = SHA256.hash(value + (currentTimeMinutes * minutesOfValidity));
}
return hashed.equalsIgnoreCase(calculatedHash);
return MessageDigest.isEqual(hashed.getBytes(StandardCharsets.UTF_8), calculatedHash.getBytes(StandardCharsets.UTF_8));
}

private static String reduceToNumeric(String extendedToken, int length) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import it.richkmeli.jframework.util.log.Logger;
import org.json.JSONObject;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
Expand Down Expand Up @@ -128,12 +130,18 @@ public static void initSessionCookie(HttpServletRequest request, HttpServletResp
//Logger.info("Cookie JFRAMEWORKSESSIONID: already present");
cookie = getCookie(request, JFSESSIONID);
} else {
String id = request.getRemoteAddr()
String encryptionKey = "default_secret_salt";
try {
encryptionKey = ResourceBundle.getBundle("configuration").getString("encryptionkey");
} catch (Exception e) {
// encryptionkey not found, using default
}
String id = encryptionKey + "##" + request.getRemoteAddr()
//+ "##" + request.getRemoteUser()
+ "##" + getCookie(request, "JSESSIONID").getValue()
+ "##" + request.getHeader("User-Agent");
//Logger.info("Cookie JFRAMEWORKSESSIONID: " + id);
String hashed = SHA256.hashToString(id.getBytes());
String hashed = SHA256.hashToString(id.getBytes(StandardCharsets.UTF_8));
cookie = new Cookie(JFSESSIONID, hashed);
//cookie.setMaxAge(JFSESSIONCOOKIE_MAXAGE);
}
Expand All @@ -152,23 +160,29 @@ public static Cookie generateSessionCookie(HttpServletRequest request) {
}
String userAgent = request.getHeader("User-Agent");

String id = remoteAddr + "##" + jsessionid + "##" + userAgent;
String encryptionKey = "default_secret_salt";
try {
encryptionKey = ResourceBundle.getBundle("configuration").getString("encryptionkey");
} catch (Exception e) {
// encryptionkey not found, using default
}
String id = encryptionKey + "##" + remoteAddr + "##" + jsessionid + "##" + userAgent;
//Logger.info("Cookie JFRAMEWORKSESSIONID: " + id);
String hashed = SHA256.hashToString(id.getBytes());
String hashed = SHA256.hashToString(id.getBytes(StandardCharsets.UTF_8));
return new Cookie(JFSESSIONID, hashed);
}

public static void checkSessionCookie(HttpServletRequest request, HttpServletResponse response) throws JServletException {
Cookie extractedJframeworkSessionID = getCookie(request, JFSESSIONID);
if (!isCookiePresent(request, JFSESSIONID) || extractedJframeworkSessionID.getValue().equalsIgnoreCase("")) {
if (!isCookiePresent(request, JFSESSIONID) || extractedJframeworkSessionID.getValue().equals("")) {
String warning = "Cookie: " + JFSESSIONID + " is not present in HTTP cookies";
Logger.warning(warning);
// invalidate httpsession
//request.getSession().invalidate();
//response.reset();
throw new JServletException(new KoResponse(BaseStatusCode.JFRAMEWORK_SESSIONID_ERROR));
}
if (!extractedJframeworkSessionID.getValue().equalsIgnoreCase(generateSessionCookie(request).getValue())) {
if (!MessageDigest.isEqual(extractedJframeworkSessionID.getValue().getBytes(StandardCharsets.UTF_8), generateSessionCookie(request).getValue().getBytes(StandardCharsets.UTF_8))) {
String error = "JFRAMEWORKSESSIONID mismatch, possible Session Hijacking Attack";
Logger.error(error);
resetSession(request, response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
public class RandomStringGenerator {
public static final String ALPHANUMERIC_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
public static final String NUMERIC_ALPHABET = "0123456789";
private static final SecureRandom random = new SecureRandom();

public static String generateAlphanumericString(int length) {
String alphabet = ALPHANUMERIC_ALPHABET;
Expand All @@ -21,10 +22,9 @@ private static String generateString(int length, String alphabet) {
int alphabetLength = alphabet.length();

StringBuilder result = new StringBuilder();
SecureRandom secureRandom = new SecureRandom();

for (int i = 0; i < length; ++i) {
result.append(alphabet.charAt(secureRandom.nextInt(alphabetLength)));
result.append(alphabet.charAt(random.nextInt(alphabetLength)));
}

return result.toString();
Expand All @@ -42,11 +42,9 @@ public static String generateBoundedString(int targetStringLength, int leftLimit
//int leftLimit = 97; // letter 'a'
//int rightLimit = 122; // letter 'z'
//int targetStringLength = 10;
SecureRandom random = new SecureRandom();
StringBuilder buffer = new StringBuilder(targetStringLength);
for (int i = 0; i < targetStringLength; i++) {
int randomLimitedInt = leftLimit + (int)
(random.nextFloat() * (rightLimit - leftLimit + 1));
int randomLimitedInt = random.nextInt(rightLimit - leftLimit + 1) + leftLimit;
buffer.append((char) randomLimitedInt);
}
return buffer.toString();
Expand All @@ -55,19 +53,19 @@ public static String generateBoundedString(int targetStringLength, int leftLimit

public static String generateUtf8String(int length) {
byte[] array = new byte[length]; // length is bounded by 7
new SecureRandom().nextBytes(array);
random.nextBytes(array);
return new String(array, StandardCharsets.UTF_8);
}

public static String generateUtf16String(int length) {
byte[] array = new byte[length]; // length is bounded by 7
new SecureRandom().nextBytes(array);
random.nextBytes(array);
return new String(array, StandardCharsets.UTF_16);
}

public static String generateASCIItring(int length) {
byte[] array = new byte[length]; // length is bounded by 7
new SecureRandom().nextBytes(array);
random.nextBytes(array);
return new String(array, StandardCharsets.US_ASCII);
}

Expand Down